Во-первых и всегда, вы можете сделать это. Для моего проекта 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: &#9733; &#9733; &#9733; &#9733; &#9734;</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 до размера food json, а затем вызовите единственный файл 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 в качестве бэкенда. И последний по порядку но не по значимости:

Вы вполне можете это сделать.