Опубликован: 27.01.2016 | Доступ: свободный | Студентов: 914 / 58 | Длительность: 23:07:00
Лекция 11:

Слежение за сообщениями пользователей

< Лекция 10 || Лекция 11: 12345678

Реализация кнопки "читать" (follow) с Ajax

Хотя наша реализация слежения за сообщениями пользователей является законченной и в своем нынешнем виде, нам осталось совсем немного подправить ее прежде чем заняться потоком сообщений. Вы могли заметить в Разделе 11.2.4 что оба create и destroy действия в контроллере Relationships просто перенаправляют обратно к исходному профилю. Другими словами, пользователь начинает на странице профиля, подписывается на сообщения пользователя и немедленно перенаправляется на исходную страницу. Резонный вопрос - почему пользователь вообще должен покидать эту страницу?

Именно эту проблему решает Ajax, который позволяет веб страницам отправлять асинхронные запросы на сервер не покидая страницы.7Так как номинально это является акронимом asynchronous JavaScript and XML, Ajax иногда ошибочно пишут как "AJAX", хотя на протяжении всей оригинальной Ajax статьи используется написание "Ajax". Поскольку практика добавления Ajax в веб формы является довольно распространенной, Rails делает реализацию Ajax легкой. Действительно, обновление партиалов формы follow/unfollow тривиально: просто заменим

form_for

на

form_for ..., remote: true

и Rails автомагически будет использовать Ajax.8Это работает только если JavaScript включен в браузере, но изящно деградирует, работая в точности как в Разделе 11.2.4 если JavaScript отключен. Обновленные партиалы представлены в Листинге 11.35 и Листинге 11.36.

<%= form_for(current_user.relationships.build(followed_id: @user.id),
             remote: true) do |f| %>
  <div><%= f.hidden_field :followed_id %></div>
  <%= f.submit "Follow", class: "btn btn-large btn-primary" %>
<% end %>
Листинг 11.35. Форма для чтения сообщений пользователя использующая Ajax. app/views/users/_follow.html.erb
<%= form_for(current_user.relationships.find_by(followed_id: @user),
             html: { method: :delete },
             remote: true) do |f| %>
  <%= f.submit "Unfollow", class: "btn btn-large" %>
<% end %>
Листинг 11.36. Форма для прекращения чтения сообщений пользователя использующая Ajax. app/views/users/_unfollow.html.erb

HTML сгенерированный этим ERb не особенно относится к делу, но вам может быть любопытно, так что взгляните:

<form action="/relationships/117" class="edit_relationship" data-remote="true"
      id="edit_relationship_117" method="post">
  .
  .
  .
</form>

Это устанавливает переменную data-remote="true" внутри тега формы, что говорит Rails о том, что форма будет обрабатываться JavaScript. Используя простое свойство HTML вместо вставки полного JavaScript кода (как в предыдущих версиях Rails), Rails 3 следует философии ненавязчивого JavaScript.

После обновления формы нам нужно уговорить контроллер Relationships отвечать на Ajax запросы. Тестирование Ajax является довольно сложным, и тщательное делание этого является большой темой со своими собственными правилами, но мы можем начать с кодом в Листинге 11.37. Это использует xhr метод (от "XmlHttpRequest") для выдачи Ajax запроса; сравните с get, post, patch и delete методами в предыдущих тестах. Затем мы проверяем что create и destroy действия делают правильные вещи когда вызываются Ajax запросом. (Для написания более основательного набора тестов для насыщенных Ajax-ом приложений, взгляните на Selenium и Watir.)

require 'spec_helper'

describe RelationshipsController do

  let(:user) { FactoryGirl.create(:user) }
  let(:other_user) { FactoryGirl.create(:user) }

  before { sign_in user, no_capybara: true }

  describe "creating a relationship with Ajax" do

    it "should increment the Relationship count" do
      expect do
        xhr :post, :create, relationship: { followed_id: other_user.id }
      end.to change(Relationship, :count).by(1)
    end

    it "should respond with success" do
      xhr :post, :create, relationship: { followed_id: other_user.id }
      expect(response).to be_success
    end
  end

  describe "destroying a relationship with Ajax" do

    before { user.follow!(other_user) }
    let(:relationship) { user.relationships.find_by(followed_id: other_user) }

    it "should decrement the Relationship count" do
      expect do
        xhr :delete, :destroy, id: relationship.id
      end.to change(Relationship, :count).by(-1)
    end

    it "should respond with success" do
      xhr :delete, :destroy, id: relationship.id
      expect(response).to be_success
    end
  end
end
Листинг 11.37. Тесты для ответов контроллера Relationships на Ajax запросы. spec/controllers/relationships_controller_spec.rb

Код в Листинге 11.37 это наш первый пример тестов контроллера, которыми я ранее интенсивно пользовался (например в предыдущем издании этого учебника), но которым я сейчас предпочитаю интеграционные тесты. Однако в данном случае, метод xhr (по непонятным мне причинам) не доступен в интеграционных тестах. Хотя мы впервые используем xhr, в этой точке учебника вы, вероятно, уже можете понять из контекста чем занимается этот код:

xhr :post, :create, relationship: { followed_id: other_user.id }

Мы видим что xhr принимает в качестве аргумента символ для соответствующего метода HTTP, символ для действия и хэш представляющий собой содержимое params в самом контроллере. Как и в предыдущих примерах, мы используем expect для того чтобы обернуть операцию в блок и протестировать увеличение или уменьшение соответсвующего количества.

Как следует из тестов, код приложения использует те же create и delete действия для ответа на Ajax запросы которые он выдает чтобы ответить на обычные POST и DELETE HTTP запросы. Все что нам нужно сделать это ответить на обычный HTTP запрос с переадресацией (как в Разделе 11.2.4) и ответить на Ajax запрос с JavaScript. Код контроллера представлен в Листинге 11.38. (См. в Разделе 11.5 пример показывающий более компактный способ выполнить то же самое.)

class RelationshipsController < ApplicationController
  before_action :signed_in_user

  def create
    @user = User.find(params[:relationship][:followed_id])
    current_user.follow!(@user)
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end

  def destroy
    @user = Relationship.find(params[:id]).followed
    current_user.unfollow!(@user)
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end
end
Листинг 11.38. Ответ на Ajax запросы в контроллере Relationships. app/controllers/relationships_controller.rb

Этот код использует respond_to чтобы принять соответствующее действие в зависимости от вида запроса. (Это respond_to никак не связано с respond_to используемым в примерах RSpec.) Синтаксис может запутать и важно понимать, что в

respond_to do |format|
  format.html { redirect_to @user }
  format.js
end	

выполняется только одна из строк (в зависимости от характера запроса).

В случае Ajax запроса, Rails автоматически вызывает JavaScript Embedded Ruby (.js.erb) файл с тем же именем что и действие, т.е., create.js.erb или destroy.js.erb. Как вы можете догадаться, эти файлы позволяют смешивать JavaScript и Embedded Ruby для выполнения действий на текущей странице. Именно эти файлы нам нужны для создания и редактирования страницы профиля пользователя после начала слежения за сообщениями пользователя или после его прекращения.

Внутри JS-ERb файла, Rails автоматически обеспечивает jQuery JavaScript хелперы для манипуляции страницей при помощи Document Object Model (DOM). Библиотека jQuery предоставляет большое количество методов для манипуляции DOM, но здесь нам понадобятся только два. Во первых мы должны знать о синтаксисе знака доллара, используемого для доступа к DOM элементу опираясь на его уникальный CSS id. Например, для манипуляции элементом follow_form, мы используем синтаксис

$("#follow_form")

(Вспомните из Листинг 11.23 что это div который обертывает форму, а не сама форма.) Этот синтаксис, вдохновленный CSS, использует символ # для указания CSS id. Как вы можете догадаться, jQuery, как и CSS, использует точку . для манипуляций с CSS классами.

Второй метод который нам потребуется это html, который обновляет HTML внутри соответствующего элемента содержимым своего аргумента. Например, чтобы полностью заменить follow form на строку "foobar", мы можем написать

$("#follow_form").html("foobar")

В отличие от простых JavaScript файлов, JS-ERb файлы позволяют также использовать Embedded Ruby, что мы применяем в create.js.erb файле для замены формы на партиал unfollow (это то, что должно быть видно после успешной подписки на сообщения пользователя) и обновления количества читаемых. Результат представлен в Листинг 11.39. Здесь используется функция escape_javascript, которая необходима для маскирования результатов при вставке HTML в файл JavaScript.

$("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>")
$("#followers").html('<%= @user.followers.count %>')
Листинг 11.39. JavaScript Embedded Ruby для создания взаимоотношения при чтении сообщений другого пользователя. app/views/relationships/create.js.erb

Файл destroy.js.erb аналогичен (Листинг 11.40).

$("#follow_form").html("<%= escape_javascript(render('users/follow')) %>")
$("#followers").html('<%= @user.followers.count %>')
Листинг 11.40. Ruby JavaScript (RJS) для уничтожения взаимоотношения при отказе от чтения сообщений другого пользователя. app/views/relationships/destroy.js.erb

Теперь вам следует перейти на страницу профиля пользователя и проверить, что вы можете следить и не следить за сообщениями пользователей без обновления страницы, а набор тестов должен позеленеть:

$ bundle exec rspec spec/

Использование Ajax в Rails является большой и стремительно развивающейся темой, и здесь мы лишь слегка коснулись ее, но (как и с остальными материалами в этом учебнике) наш подход дает вам хорошую основу для изучения более продвинутых ресурсов.

< Лекция 10 || Лекция 11: 12345678
Вадим Обозин
Вадим Обозин

Здравствуйте, записался на курс. При этом ставил галочку на "обучаться с тьютором". На email пришло письмо, о том, что записался на самостоятельное изучение курса. Как выбрать тьютора?