Опубликован: 27.01.2016 | Уровень: для всех | Доступ: платный
Лекция 10:

Микросообщения пользователей

< Лекция 9 || Лекция 10: 123456 || Лекция 11 >

Манипулирование микросообщениями

Мы закончили моделирование данных и шаблоны для отображения микросообщений, теперь мы обратим наше внимание на интерфейс для их создания через веб. Результатом будет наш третий пример использования формы HTML для создания ресурса — в данном случае, ресурса Microposts.7Другими двумя ресурсами являются Users в Разделе 7.2 и Sessions в Разделе 8.1. В этом разделе мы также увидим первый намек на поток сообщений — понятие, полной реализацией которого мы займемся в Главе 11. Наконец, как и с пользователями, мы сделаем возможным уничтожение микросообщений через веб.

Существует один разрыв с предыдущими соглашениями который стоит отметить: интерфейс ресурса Microposts будет работать главным образом за счет контроллеров Users и StaticPages, так что нам не понадобятся действия вроде new или edit в контроллере Microposts; единственное что нам пригодится это create и destroy. Это означает, что маршруты для ресурса Microposts необычайно просты, как это показано в Листинге 10.22. Код в Листинге 10.22 в свою очередь приводит к RESTful маршрутам показанным в Таблице 10.2, которые являются сокращенным вариантом полного набора маршрутов виденного нами в Таблице 2.3. Конечно, эта простота является признаком того, что они более продвинутые — мы прошли долгий путь со времени нашей зависимости от scaffolding в Главе 2 и нам более не нужна бОльшая часть его (scaffolding) сложности.

SampleApp::Application.routes.draw do
  resources :users
  resources :sessions,   only: [:new, :create, :destroy]
  resources :microposts, only: [:create, :destroy]
  .
  .
  .
end
Листинг 10.22. Маршруты для ресурса Microposts. config/routes.rb
Таблица 10.2. RESTful маршруты обеспеченные ресурсом Microposts в Листинге 10.22.
HTTP запрос URL Действие Назначение
POST /microposts create создание нового микросообщения
DELETE /microposts/1 destroy удаление микросообщения с id 1

Контроль доступа

Мы начнем нашу разработку ресурса Microposts с контроля доступа на уровне контроллера Microposts. Идея проста: как create так и destroy действия должны требовать чтобы пользователи вошли в систему. Код RSpec для тестирования этого представлен в Листинге 10.23. (Мы протестируем и добавим третью защиту — обеспечение того, что только пользователь создавший микросообщение может удалить его — в Разделе 10.3.4.)

require 'spec_helper'

describe "Authentication" do
  .
  .
  .
  describe "authorization" do

    describe "for non-signed-in users" do
      let(:user) { FactoryGirl.create(:user) }
      .
      .
      .
      describe "in the Microposts controller" do

        describe "submitting to the create action" do
          before { post microposts_path }
          specify { expect(response).to redirect_to(signin_path) }
        end

        describe "submitting to the destroy action" do
          before { delete micropost_path(FactoryGirl.create(:micropost)) }
          specify { expect(response).to redirect_to(signin_path) }
        end
      end
      .
      .
      .
    end
  end
end
Листинг 10.23. Тесты контроля доступа для контроллера Microposts. spec/requests/authentication_pages_spec.rb

Прежде чем использовать (пока-не-построенный) веб-интерфейс для микросообщений, код в Листинге 10.23 действует на уровне отдельных действий микросообщений - стратегия которую мы впервые видели в Листинге 9.13. В данном случае, не вошедшие пользователи не могут отправить POST запрос на /microposts (post microposts_path, который вызывает create действие) или отправить DELETE запрос на /microposts/1 (delete micropost_path(micropost), который вызывает действие destroy).

Прежде чем начать писать код приложения, необходимый для прохождения тестов из Листинга 10.23 требуется произвести небольшой рефакторинг. Вспомним из Раздела 9.2.1 что мы внедрили требование входа используя предфильтр который назывался signed_in_user (Листинг 9.12). Тогда этот метод нужен был нам только в контроллере Users, но теперь мы обнаружили, что он также необходим нам и в контроллере Microposts, так что мы переместим его в Sessions хелпер, как это показано в Листинге 10.24.8Мы отмечали в Разделе 8.2.1, что вспомогательные методы по умолчанию доступны только в представлениях, но мы сделали вспомогательный метод Sessions доступным также и в контроллерах, добавив sionsHelper в контроллер Application (Листинг 8.14).

module SessionsHelper
  .
  .
  .
  def current_user?(user)
    user == current_user
  end

  def signed_in_user
    unless signed_in?
      store_location
      redirect_to signin_url, notice: "Please sign in."
    end
  end
  .
  .
  .
end
Листинг 10.24. Перемещение метода signed_in_user в Sessions хелпер. app/helpers/sessions_helper.rb

Для того чтобы избежать повторяющегося кода, вам сейчас также следует удалить signed_in_user из контроллера Users.

С кодом в Листинге 10.24, метод signed_in_user теперь стал доступным в контроллере Microposts, что означает что мы можем ограничить доступ к действиям create и destroy с помощью предфильтра показанного в Листинге 10.25. (Поскольку мы не генерировали его в командной строке, вам следует создать файл контроллера Microposts вручную.)

class MicropostsController < ApplicationController
  before_action :signed_in_user

  def create
  end

  def destroy
  end
end
Листинг 10.25. Добавление аутентификации для действий контроллера Microposts. app/controllers/microposts_controller.rb

Обратите внимание на то, что мы не ограничили действия к которым применяется предфильтр, поскольку он в настоящее время должен применяться ко всем действиям контроллера. Если бы мы добавили, скажем, index действие, доступное даже для не вошедших пользователей, мы должны были бы указать защищаемые действия в явном виде:

class MicropostsController < ApplicationController
  before_action :signed_in_user, only: [:create, :destroy]

  def index
  end

  def create
  end

  def destroy
  end
end

В этой точке тесты должны пройти:

$ bundle exec rspec spec/requests/authentication_pages_spec.rb

Создание микросообщений

В Главе 7 мы реализовали регистрацию пользователей, сделав HTML форму которая выдавала HTTP запрос POST в create действие контроллера Users. Реализация создания микросообщения аналогична; основное отличие заключается в том, что вместо использования отдельной страницы с адресом /microposts/new, мы (следуя Twitter конвенции) поместим форму на самой Home странице (т.е., root path /), как показано на рис. 10.10.

Набросок страницы Home с формой для создания микросообщений.

Рис. 10.10. Набросок страницы Home с формой для создания микросообщений.

Когда мы последний раз видели Home страницу, она выглядела как на рис. 10.5 — то есть, у нее была большая жирная "Sign up now!" кнопка посередине. Так как форма для создания микросообщения имеет смысл только в контексте конкретного, вошедшего в систему пользователя, одной из целей данного раздела будет предоставление различных версий Home страницы в зависимости от статуса посетителя. Мы осуществим это в Листинге 10.28 ниже, а пока мы можем заняться написанием тестов. Как и в случае с ресурсом Users, мы будем использовать интеграционные тесты:

$ rails generate integration_test micropost_pages

Тесты создания микросообщения очень похожи на тесты для создания пользователя из Листинга 7.16; результат представлен в Листинге 10.26.

require 'spec_helper'

describe "Micropost pages" do

  subject { page }

  let(:user) { FactoryGirl.create(:user) }
  before { sign_in user }

  describe "micropost creation" do
    before { visit root_path }

    describe "with invalid information" do

      it "should not create a micropost" do
        expect { click_button "Post" }.not_to change(Micropost, :count)
      end

      describe "error messages" do
        before { click_button "Post" }
        it { should have_content('error') }
      end
    end

    describe "with valid information" do

      before { fill_in 'micropost_content', with: "Lorem ipsum" }
      it "should create a micropost" do
        expect { click_button "Post" }.to change(Micropost, :count).by(1)
      end
    end
  end
end
Листинг 10.26. Тесты для создания микросообщений. spec/requests/micropost_pages_spec.rb

Мы начнем с create действия для микросообщений, которое очень похоже на свой аналог для контроллера Users (Листинг 7.26); принципиальное отличие заключается в использовании пользователь/микросообщения ассоциации для build нового микросообщения, как это видно в Листинге 10.27. Обратите внимание на использование строгих параметров через micropost_params, который открывает для редактирования через веб только контент микросообщения.

class MicropostsController < ApplicationController
  before_action :signed_in_user

  def create
    @micropost = current_user.microposts.build(micropost_params)
    if @micropost.save
      flash[:success] = "Micropost created!"
      redirect_to root_url
    else
      render 'static_pages/home'
    end
  end

  def destroy
  end

  private

    def micropost_params
      params.require(:micropost).permit(:content)
    end
end
Листинг 10.27. Действие create контроллера Microposts. app/controllers/microposts_controller.rb

Для того чтобы построить форму для создания микросообщений мы воспользуемся кодом из Листинга 10.28, который предоставляет различный HTML в зависимости от того, является ли посетитель вошедшим.

<% if signed_in? %>
  <div class="row">
    <aside class="span4">
      <section>
        <%= render 'shared/user_info' %>
      </section>
      <section>
        <%= render 'shared/micropost_form' %>
      </section>
    </aside>
  </div>
<% else %>
  <div class="center hero-unit">
    <h1>Welcome to the Sample App</h1>

    <h2>
      This is the home page for the
      <a href="http://railstutorial.org/">Ruby on Rails Tutorial</a>
      sample application.
    </h2>

    <%= link_to "Sign up now!", signup_path,
                                class: "btn btn-large btn-primary" %>
  </div>

  <%= link_to image_tag("rails.png", alt: "Rails"), 'http://rubyonrails.org/' %>
<% end %>
Листинг 10.28. Добавление создания микросообщений к Home странице (/). app/views/static_pages/home.html.erb

Наличие большого количества кода в каждой ветке условного оператора if-else это немного грязно и его очистка с помощью партиалов остается в качестве упражнения (Раздел 10.5). Однако заполнение необходимых партиалов из Листинга 10.28 не является упражнением; мы заполним сайдбар новой Home страницы в Листинге 10.29, а партиал формы микросообщений в Листинге 10.30.

<%= link_to gravatar_for(current_user, size: 52), current_user %>
<h1>
  <%= current_user.name %>
</h1>
<span>
  <%= link_to "view my profile", current_user %>
</span>
<span>
  <%= pluralize(current_user.microposts.count, "micropost") %>
</span>
Листинг 10.29. Партиал для сайдбара с информацией о пользователе. app/views/shared/_user_info.html.erb

Как и в Листинге 9.24, код в Листинге 10.29 использует версию gravatar_for хелпера определенную в Листинге 7.30.

Отметим, что, как и в сайдбаре профиля (Листинг 10.17), информация о пользователе в Листинге 10.29 отображает общее число микросообщений пользователя. Хотя есть небольшое отличие; в сайдбаре профиля, "Microposts" это метка, и отображаемое Microposts 1 имет смысл. Однако в данном случае выражение "1 microposts" является безграмотным, поэтому мы организуем отображение "1 micropost" (но "2 microposts") используя удобный вспомогателный метод pluralize.

Затем мы определим форму для создания микросообщений (Листинг 10.30), которая аналогична форме регистрации из Листинга 7.17.

<%= form_for(@micropost) do |f| %>
  <%= render 'shared/error_messages', object: f.object %>
  <div class="field">
    <%= f.text_area :content, placeholder: "Compose new micropost..." %>
  </div>
  <%= f.submit "Post", class: "btn btn-large btn-primary" %>
<% end %>
Листинг 10.30. Партиал формы для создания микросообщений. app/views/shared/_micropost_form.html.erb

Нам нужно сделать два изменения прежде чем форма в Листинге 10.30 заработает. Во-первых, нам нужно определить @micropost, что (как и раньше) мы сделаем через ассоциацию

@micropost = current_user.microposts.build

Результат представлен в Листинге 10.31.

class StaticPagesController < ApplicationController

  def home
    @micropost = current_user.microposts.build if signed_in?
  end
  .
  .
  .
end
Листинг 10.31. Добавление переменной экземпляра в home действие. app/controllers/static_pages_controller.rb

У кода в Листинге 10.31 есть одно весомое достоинство: он сломает наши тесты если мы забудем потребовать входа пользователя.

Второе изменение, необходимое для того чтобы заставить работать Листинг 10.30 это переопределение партиала сообщений об ошибках таким образом чтобы

<%= render 'shared/error_messages', object: f.object %>

работало. Вы можете вспомнить из Листинга 7.23 что партиал сообщений об ошибках явно ссылается на @user переменную, но в данном случае мы имеем вместо нее переменную @micropost. Мы должны определить партиал сообщений об ошибках который будет работать независимо от вида объекта который будет ему передан. К счастью, переменная формы f может иметь доступ к связанному объекту через f.object, так что в

form_for(@user) do |f|

f.object является @user, а в

form_for(@micropost) do |f|

f.object это @micropost.

Для того, чтобы передать объект в партиал, мы используем хэш со значением равным объекту и ключом равным выбранному имени переменной, именно этого мы и достигаем с помощью этого кода:

<%= render 'shared/error_messages', object: f.object %>

Другими словами, object: f.object создает переменную с именем object в партиале error_messages. Мы можем применить этот объект для построения кастомизированного сообщения об ошибке, как показано в Листинге 10.32.

<% if object.errors.any? %>
  <div id="error_explanation">
    <div class="alert alert-error">
      The form contains <%= pluralize(object.errors.count, "error") %>.
    </div>
    <ul>
    <% object.errors.full_messages.each do |msg| %>
      <li>* <%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>
Листинг 10.32. Обновление партиала сообщений об ошибках из Листинга 7.24 для работы с другими объектами. app/views/shared/_error_messages.html.erb

В этой точке тесты из Листинга 10.26 должны пройти:

$ bundle exec rspec spec/requests/micropost_pages_spec.rb

К сожалению, теперь рухнули интеграционные тесты пользователя - поскольку формы редактирования и регистрации используют старую версию партиала сообщений об ошибках. Для того чтобы их исправить, мы обновим их как показано в Листинге 10.33 и Листинге 10.34. (Примечаение: ваш код будет отличаться если вы реализовали Листинг 9.49 и Листинг 9.50 из упражнений Раздела 9.6. Mutatis mutandis.)

<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>

<div class="row">
  <div class="span6 offset3">
    <%= form_for(@user) do |f| %>
      <%= render 'shared/error_messages', object: f.object %>
      .
      .
      .
    <% end %>
  </div>
</div>
Листинг 10.33. Обновление рендеринга ошибок регистрации пользователей. app/views/users/new.html.erb
<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>

<div class="row">
  <div class="span6 offset3">
    <%= form_for(@user) do |f| %>
      <%= render 'shared/error_messages', object: f.object %>
      .
      .
      .
    <% end %>

    <%= gravatar_for(@user) %>
    <a href="http://gravatar.com/emails">change</a>
  </div>
</div>
Листинг 10.34. Обновление ошибок для редактирования пользователей. app/views/users/edit.html.erb

В этой точке все тесты должны проходить:

$ bundle exec rspec spec/

К тому же, весь HTML этого раздела должен рендериться правильно, показывая форму как на рис. 10.11 и форму с ошибкой как на рис. 10.12. Приглашаю вас создать свое микросообщение и убедиться, что все работает — но вам, вероятно, все же следует повременить с этим делом до Раздела 10.3.3.

Страница Home (/) с формой создания нового микросообщения.

Рис. 10.11. Страница Home (/) с формой создания нового микросообщения.
Home страница с ошибками отправки формы.

Рис. 10.12. Home страница с ошибками отправки формы.
< Лекция 9 || Лекция 10: 123456 || Лекция 11 >
Вадим Обозин
Вадим Обозин

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

Акбар Ахвердов
Акбар Ахвердов
Россия, г. Москва
Артём Зайцев
Артём Зайцев
Украина, ДНР