Здравствуйте, записался на курс. При этом ставил галочку на "обучаться с тьютором". На email пришло письмо, о том, что записался на самостоятельное изучение курса. Как выбрать тьютора? |
Микросообщения пользователей
Манипулирование микросообщениями
Мы закончили моделирование данных и шаблоны для отображения микросообщений, теперь мы обратим наше внимание на интерфейс для их создания через веб. Результатом будет наш третий пример использования формы 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
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.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.