Во-первых и всегда, вы можете сделать это. Для моего проекта Javascript-Rails Flatiron School я решил взять мое последнее приложение Rails и провести рефакторинг с помощью Javascript. Я, вероятно, должен был начать с чистого листа, потому что потребовалось немного больше времени, чем ожидалось, чтобы реорганизовать все в серверную часть Rails с новыми требованиями, реализованными через Javascript. Я расскажу о рефакторинге поддержки Rails для отправки ответов JSON, рефакторинге внешнего интерфейса, чтобы позволить Javascript находить элементы, а также о элементах Javascript, показанных в этом прототипе проекта. Посмотреть проект на Github.
1. Краткий обзор
MealsFor.Me — это приложение, которое может соединять поваров и едоков. Вы можете зайти в приложение и посмотреть, какие продукты доступны, и вы можете приготовить еду, которую кто-то может приготовить для вас. Как повар, вы можете ввести блюдо с изображением и описанием, которое будет доступно для любого едока. Приложение позволяет вводить комментарии для любого блюда, которое было создано. Ниже показана функциональность прототипа после Javascript.
2. Рефакторинг бэкенда Rails
Чтобы иметь возможность использовать поддержку Javascript, необходимо было внести некоторые изменения в серверную часть. Ниже приведены методы index и show для контроллера Meals:
def index if current_user_not_cook @meals = Meal.where(user_id: session[:user_id]) respond_to do |format| format.html { render :index } format.json { render json: @meals } end else @meals = Meal.where(cook_id: session[:user_id]) respond_to do |format| format.html { render :index } format.json { render json: @meals } end end end def create @meal = Meal.create(meal_params) @food = Food.find(@meal[:food_id]) @eater = User.find(@meal[:user_id]) @cook = User.find(@food.cook_id) redirect_to user_meal_path(@eater, @meal) end def show if Meal.where(id: params[:id]).exists? @meal = Meal.find(params[:id]) @food = Food.find(@meal[:food_id]) @eater = User.find(@meal[:user_id]) @cook = User.find(@food.cook_id) @comment = Comment.new @meal_comments = @meal.comments respond_to do |format| format.html { render :show } format.json { render json: @meal } end else redirect_to root_path, alert: 'Meal does not exist' end end
В зависимости от запроса (по умолчанию meals.html
или meals.json
) контроллер ответит соответствующим образом. Ответ html
является ответом по умолчанию, но ответ json
использует сериализатор еды:
class MealSerializer < ActiveModel::Serializer attributes :id, :meal_name, :created_at belongs_to :user belongs_to :food has_many :comments def created_at date = object.created_at.strftime("%d %b %Y") end end
Добавленный метод created_at
включен для форматирования даты для пользовательского интерфейса с Javacript. В результате json
выглядит так:
{ "id": 4, "meal_name": "Better Bacon Meal", "created_at": "11 Nov 2019", "user": { "id": 5, "name": "Jose P" }, "food": { "id": 6, "name": "better bacon", "picture": "https://cached.dakinfarm.com/RS/SR/Product/88/860_R_1ba464e5.jpg", "description": "Heat a cast-iron or other heavy skillet over medium heat. When hot, add bacon strips in a single layer. Flip bacon, using tongs, and cook until browned on both sides, about 2 minutes. Drain and reserve fat for another use, and repeat with remaining bacon. Drain bacon on paper towels.", "cook": { "id": 3, "name": "Sasha Turner" } }, "comments": [ { "id": 2, "content": "First comment", "created_at": "11 Nov 2019 at 1:19 PM" }, { "id": 3, "content": "try this", "created_at": "11 Nov 2019 at 1:23 PM" } ] }
3. Рефакторинг внешнего документа, чтобы он был готов для Javascript
Ниже находится страница «Показать пользователей». Эта страница нуждается в рефакторинге, чтобы использовать Javascript для создания индекса или собственных блюд, а также для случайного выбора еды.
<div class="center"> <div class="login-box"> <% if @user[:is_cook] %> <h3 align="center"><strong>COOK PROFILE</strong></h3> <% end %> <h4><strong>Name:</strong> <%= @user.name %></h4> <h4><strong>E-mail:</strong> <%= @user.email %></h4> <h4><strong>Rating: ★ ★ ★ ★ ☆</strong> </h4> <% if @user[:is_cook] %> <a class="btn btn-success" id="meals-i-have-created" href="/meals" role="button">My Meals Cooked</a> <% else %> <a class="btn btn-primary" id="food-available-this-week" href="/foods" role="button">Pick a Random Food</a> <a class="btn btn-success" id="meals-i-have-created" href="/users/<%=@user.id%>" role="button" data="<%=@user.id%>">Meals I have created</a> <% end %> </div><br> <div id="food-created"> </div> </div>
Основными дополнениями были идентификаторы для кнопок id="food-available-this-week"
и id="meals-i-have-created"
, а также <div id="food-created">
, поэтому после запуска прослушивателя событий эти элементы можно выбирать и изменять по мере необходимости.
4. Добавление функциональности Javascript
Мы рассмотрим добавленную функциональность Javascript и некоторые подводные камни, с которыми я столкнулся.
- Турбоссылки. Поскольку для облегчения загрузки страниц использовались турбоссылки, готовый документ должен был искать загрузку по турбоссылкам.
$(document).on('turbolinks:load', function(){ listenMealsIHaveCreatedButtonClick(); listenCommentButtonClick(); listenNextFoodClick(); listenPreviousFoodClick(); listenFoodAvailableThisWeek(); });
- Загружается и всплывает. Заметил, что лучше создавать новые функции, вложенные в другие, чтобы обеспечить необходимый порядок операций. Несколько раз код выглядел правильно, но не работал. Ниже приведен пример того, как после запуска прослушивателя событий
click
он сначала очищает элемент, а затем показывает еду через javascript.
function listenFoodAvailableThisWeek(){ $('#food-available-this-week').on('click', function(event) { event.preventDefault(); clearFoodCreatedBox(); showFood(); }); }
- Показ всех блюд пользователей с помощью Javascript. После того, как документ загружен и прослушиватель событий ожидает нажатия кнопки, я использовал команду get AJAX, чтобы получить json из бэкенда Rails, а затем отобразить через команду добавления к элементу документа, который был идентифицирован ранее.
json
, который мы сериализовали ранее, позволяет получить всю информацию через javascript.
function getUserMeals(json){ let meals = json $('#food-created').empty(); meals.forEach(function(meal){ $('#food-created').append( '<div class="index-box">' + '<h5>' + meal.created_at + '</h5><h3>' + meal.meal_name + '</h3>' + '<a href="/meals/' + meal.id + '"><img src="' + meal.food.picture + '" width="75%"></a>' + '<h5>Cooked by: ' + meal.food.cook.name + '</h5><h6>Total Comments: <strong>' + meal.comments.length + '</strong></h6></div>'); }); }
- Отображение одного продукта с помощью Javascript. Чтобы сделать другой способ отображения одного продукта с помощью
json
и Javascript, я ввел случайный выбор продуктов, который будет выбирать случайное число от 1 до размера foodjson
, а затем вызовите единственный файлjson
из бэкенда Rails. После того, как у нас естьjson
, мы используем тот же метод для добавления к ранее идентифицированному документу.
function getUserMeals(json){ let meals = json $('#food-created').empty(); meals.forEach(function(meal){ $('#food-created').append( '<div class="index-box">' + '<h5>' + meal.created_at + '</h5><h3>' + meal.meal_name + '</h3>' + '<a href="/meals/' + meal.id + '"><img src="' + meal.food.picture + '" width="75%"></a>' + '<h5>Cooked by: ' + meal.food.cook.name + '</h5><h6>Total Comments: <strong>' + meal.comments.length + '</strong></h6></div>'); }); }
- Отключение данных Rails Forms. Пришлось отключить функцию
data-disable
, которая по умолчанию используется сform-for
, поскольку она не позволяла вводить несколько комментариев при загрузке одной страницы. Функция отключения данных предназначена для предотвращения двойной отправки, но приложение делает это, проверяя наличие контента в комментарии, прежде чем создавать новый комментарий. Другая проверка комментариев заключается в том, что пользователь вошел в систему, просматриваяsession[:user_id]
, который будет присутствовать только в том случае, если пользователь вошел в систему.
<%= form_for(@comment) do |f| %> <%= f.hidden_field :meal_id, :value => @meal.id %> <%= f.hidden_field :user_id, :value => session[:user_id] %> <%= f.text_area :content, class: "form-control" %> <%= f.submit "Comment", class: "btn btn-primary btn-sm", id: "new_comment", data: { disable_with: false }%> <% end %>
- Конструктор и прототип. Я также использовал конструктор, чтобы иметь возможность отображать комментарии, которые вводятся в реальном времени после того, как они были отправлены с помощью почтовой команды AJAX. Прототип в разделе комментариев использовался для того, чтобы можно было применить один и тот же формат ко всем комментариям.
function addComment(attributes){ attributes.done(function(data){ let comment = new Comment(data); $("#print_comments").show() comment.addCommentPrototype() }); } function clearCommentBox(){ $("#comment_content").val('') } function Comment(attr){ this.id = attr.id; this.user = attr.user.name; this.content = attr.content; this.time = attr.created_at; this.meal_id = attr.meal.id; } Comment.prototype.addCommentPrototype = function(){ let html = '<p>On ' + this.time + ', ' + this.user + ' said: "' + this.content + '"</p>' $("#print_comments").prepend(html) $("#no_meal_comments").replaceWith('') }
5. Заключение
Это был отличный проект, чтобы окунуться в функциональность, которую Javascript может привнести в проект. Веб-приложение MealsFor.me по-прежнему нуждается в нескольких дополнительных функциях, таких как планирование и платежи, но медленные и устойчивые побеждают в гонке. Я думаю, что следующие уроки по React позволят больше абстрагироваться, но было здорово увидеть, как мы продвинулись вперед и что мы можем делать с Javascript и Rails в качестве бэкенда. И последний по порядку но не по значимости:
Вы вполне можете это сделать.