Глава 8 Регистрация
Теперь у нас есть рабочая модель User, пришло время добавить возможность без которой некоторые сайты просто жить не могут: позволить пользователям регистрироваться на сайте; таким образом выполнив обещание подразумевавшееся в Разделе 5.3, “Регистрация пользователей: Первый шаг”. Мы будем использовать HTML форму для предоставления пользователями регистрационной информации нашему приложению в Разделе 8.1, которая затем будет использована для создания нового пользователя и сохранения его атрибутов в базе данных в Разделе 8.3. Как обычно, мы будем писать тесты по мере разработки и в Разделе 8.4 мы будем использовать поддержку RSpec синтаксиса веб навигации для написания кратких и выразительных интеграционных тестов.Так как мы будем создавать новых пользователей в этой главе, вы можете сбросить базу данных, чтобы удалить всех пользователей, созданных в консоли (например, в Разделе 7.3.2), чтобы ваши результаты соответствовали тому, что показано в учебнике. вы можете сделать это следующим образом:
$ bundle exec rake db:reset
$ git checkout master
$ git checkout -b signing-up
8.1 Регистрационная форма
Напомним из Раздела 5.3.1 что у нас уже есть тесты для new users (signup) страницы, первоначально рассматривавшиеся в Листинге 5.26 и воспроизведенные в Листинге 8.1. (Как и было обещано в Разделе 7.3.1, мы переключились сget ’new’ на get :new потому что это то, что мои пальцы хотят печатать.) Кроме того, мы видели в Рис. 5.10 (еще раз показано в Рис. 8.1)
что эта страница регистрации в настоящее время чиста: совершенно
бесполезна для регистрации новых пользователей. Цель этого раздела -
положить начало исправлению этого печального положения дел, сделав форму
регистрации, макет которой показан в Рис. 8.2.
Листинг 8.1. Тесты для страницы new users (впервые показанные в Листинг 5.26).
spec/controllers/users_controller_spec.rb
require 'spec_helper'
describe UsersController do
render_views
.
.
.
describe "GET 'new'" do
it "should be successful" do
get :new
response.should be_success
end
it "should have the right title" do
get :new
response.should have_selector("title", :content => "Sign up")
end
end
.
.
.
end
Рисунок 8.1: Страница регистрации на данный момент /signup. (полный размер)
Рисунок 8.2: Макет страницы для регистрации пользователей. (полный размер)
8.1.1 Использование form_for
Элемент HTML, необходимый для предоставления информации на удаленный сайт, это форма,
что предполагает хороший первый шаг к регистрации пользователей -
создание формы для приема регистрационной информации. Мы можем сделать
это в Rails с помощью вспомогательного метода form_for; результат представлен в Листинге 8.2. (Читатели, знакомые с Rails 2.x должны отметить, что form_for теперь использует “процент-равно” ERb синтаксис для вставки контента; то есть, где Rails 2.x использовал <% form_for ... %>, Rails 3 использует <%= form_for ... %>.)
Листинг 8.2. Форма для регистрации новых пользователей.
app/views/users/new.html.erb
<h1>Sign up</h1>
<%= form_for(@user) do |f| %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :email %><br />
<%= f.text_field :email %>
</div>
<div class="field">
<%= f.label :password %><br />
<%= f.password_field :password %>
</div>
<div class="field">
<%= f.label :password_confirmation, "Confirmation" %><br />
<%= f.password_field :password_confirmation %>
</div>
<div class="actions">
<%= f.submit "Sign up" %>
</div>
<% end %>
do указывает, что form_for принимает блок (Раздел 4.3.2), который имеет одну переменную, которую мы назвали f от “form”. Внутри form_for хелпера, f это объект, который представляет форму; как это обычно бывает с Rails хелперами, нам не нужно знать деталей реализации, но нам нужно знать что делает объект f: при вызове с методом, соответствующим HTML элементу form — таким
как текстовое поле, радио кнопки, или поле пароля, он возвращает код
для этого элемента, специально разработанный для установки атрибута
объекта @user. Другими словами,<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
name атрибута модели User.Чтобы увидеть это в действии, нам нужно добыть и посмотреть на реальный HTML производимый с помощью этой формы, но здесь у нас есть проблема: сейчас страница неисправна, потому что мы не установили переменную
@user, как и все неопределенные переменные экземпляра (Раздел 4.2.3), @user в настоящее время равна nil.
Соответственно, если вы сейчас запустите свой набор тестов, вы увидите,
что тесты страницы регистрации не проходят. Чтобы заставить их пройти и
получить визуализацию нашей формы, мы должны определить переменную @user в действии контроллера, соответствующего new.html.erb, т.е., в new действии контроллера Users. Хелпер form_for ожидает, что @user будет объектом User, а так как мы создаем нового пользователя, мы просто используем User.new, как видно в Листинге 8.3.
Листинг 8.3. Добавление
@user переменной в new действие. app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def new
@user = User.new
@title = "Sign up"
end
end
@user переменной, тесты вновь должны проходить,1 и теперь форма (чуть-чуть отстиленная Листингом 8.4) представлена на Рис. 8.3.
Листинг 8.4. Капелька CSS для формы регистрации.
public/stylesheets/custom.css
.
.
.
div.field, div.actions {
margin-bottom: 10px;
}
Рисунок 8.3: Регистрационная форма /signup для новых пользователей. (полный размер)
8.1.2 HTML формы
Как видно на Рис. 8.3, страница регистрации теперь правильно отображается, указывая на то, чтоform_for код в Листинге 8.2 производит валидный HTML. Если вы посмотрите на HTML генерируемый формой (используя Firebug или опцию “Исходный код страницы” вашего браузера), вы должны увидеть разметку как в Листинге 8.5.
Хотя многие детали не имеют значения для наших целей, давайте уделим
минуту, чтобы осветить наиболее важные части ее структуры.
Листинг 8.5. HTML формы из Рис. 8.3.
<form action="/users" class="new_user" id="new_user" method="post">
<div style="margin:0;padding:0;display:inline">
<input name="authenticity_token" type="hidden"
value="rB82sI7Qw5J9J1UMILG/VQL411vH5putR+JwlxLScMQ=" />
</div>
<div class="field">
<label for="user_name">Name</label><br />
<input id="user_name" name="user[name]" size="30" type="text" />
</div>
<div class="field">
<label for="user_email">Email</label><br />
<input id="user_email" name="user[email]" size="30" type="text" />
</div>
<div class="field">
<label for="user_password">Password</label><br />
<input id="user_password" name="user[password]" size="30" type="password" />
</div>
<div class="field">
<label for="user_password_confirmation">Confirmation</label><br />
<input id="user_password_confirmation" name="user[password_confirmation]"
size="30" type="password" />
</div>
<div class="actions">
<input id="user_submit" name="commit" type="submit" value="Sign up" />
</div>
</form>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<label for="user_name">Name</label><br />
<input id="user_name" name="user[name]" size="30" type="text" />
</div>
<div class="field">
<%= f.label :password %><br />
<%= f.password_field :password %>
</div>
<div class="field">
<label for="user_password">Password</label><br />
<input id="user_password" name="user[password]" size="30" type="password" />
</div>
type="text") просто отображает их содержимое, в то время как поле пароля (type="password") скрывает ввод в целях безопасности, как показано на Рис. 8.4.
Рисунок 8.4: Заполненная форма, показывающая разницу между
text и password полями. (полный размер)name в каждом input:<input id="user_name" name="user[name]" - - - />
.
.
.
<input id="user_password" name="user[password]" - - - />
name позволяют Rails построить хэш инициализации (через переменную params впервые показанную в Разделе 6.3.2) для создания пользователей, используя значения, введенные пользователями, как мы увидим в Разделе 8.2.Вторым важным элементом является сам тег
form. Rails создает form тег используя объект @user: потому что каждый объект Ruby знает свой собственный класс (Раздел 4.4.1), Rails выводит, что @user принадлежит классу User; причем, так как @user это новый пользователь, Rails знает, что нужно построить форму с post методом, который является надлежащим глаголом для создания нового объекта (Box 3.1):<form action="/users" class="new_user" id="new_user" method="post">
class и id не играют большой роли; куда более важны action="/users" и method="post". Вместе они составляют инструкцию выдавать HTTP запрос POST к /users URL. Мы увидим в ближайших двух разделах какие это имеет последствия.Наконец, отметим, весьма туманный код для “authenticity token”:
<div style="margin:0;padding:0;display:inline">
<input name="authenticity_token" type="hidden"
value="rB82sI7Qw5J9J1UMILG/VQL411vH5putR+JwlxLScMQ=" />
</div>
hidden
(скрытый), так что вы не должны дважды думать о нем, но его видно,
когда вы смотрите исходный код формы, поэтому я хотел по крайней мере
упомянуть об этом.8.2 Сбой регистрации
Хотя мы кратко рассмотрели HTML формы, показанной в Рис. 8.3 (Листинг 8.5), он (HTML) лучше всего понимается в контексте сбоя регистрации. Просто получить регистрационную форму, которая принимает недействительные данные и вновь показывает страницу регистрации (как на макете в Рис. 8.5) является значительным достижением, и это будет целью данного раздела.
Рисунок 8.5: Макет страницы неудавшейся регистрации. (полный размер)
8.2.1 Тестирование сбоя
Напомним, из Раздела 6.3.3 что добавлениеresources :users в файл routes.rb (Листинг 6.26) автоматически гарантирует, что наше Rails приложение отвечает на RESTful URL из Таблицы 6.2. В частности, это гарантирует, что POST запрос в /users обрабатывается create действием. Нашей стратегией для create действия будет использование формы регистрации, для создания нового объекта пользователь, используя User.new,
попробовать (безуспешно) сохранить этого пользователя, а затем сделать
страницу регистрации для возможной повторной попытки. Наша задача
заключается в написании тестов для этого действия, а затем добавлении create в контроллер Users, чтобы заставить тесты пройти.Давайте начнем с обзора кода для формы регистрации:
<form action="/users" class="new_user" id="new_user" method="post">
get, который выдает GET запрос внутри теста, мы используем метод post чтобы выдать запрос POST к create действию. Как мы вскоре увидим, create принимает хэш соответствующий типу создаваемого объекта; так как это тест для сбоя регистрации, мы просто передадим хэш @attr с пустыми записями, как показано в Листинге 8.6. Это по сути эквивалентно посещению страницы регистрации и нажатии на кнопку с любым незаполненным полем.
Листинг 8.6. Тест для сбоя регистрации пользователя.
spec/controllers/users_controller_spec.rb
require 'spec_helper'
describe UsersController do
render_views
.
.
.
describe "POST 'create'" do
describe "failure" do
before(:each) do
@attr = { :name => "", :email => "", :password => "",
:password_confirmation => "" }
end
it "should not create a user" do
lambda do
post :create, :user => @attr
end.should_not change(User, :count)
end
it "should have the right title" do
post :create, :user => @attr
response.should have_selector("title", :content => "Sign up")
end
it "should render the 'new' page" do
post :create, :user => @attr
response.should render_template('new')
end
end
end
end
render_template). Первый тест немного более сложен.Целью теста
it "should not create a user" do
lambda do
post :create, :user => @attr
end.should_not change(User, :count)
end
create действие не создает пользователя в базе данных. Для этого он вводит два новых элемента. Во-первых, мы используем RSpec метод change чтобы вернуть изменение количества пользователей в базе данных:change(User, :count)
count (количество) метод,
который просто возвращает количество записей данного типа, находящихся в
базе данных. Например, если вы очистили development database базу
данных в начале главы, это количество сейчас должно быть 0:$ rails console
>> User.count
=> 0
post :create шага в package (пакет), используя Ruby конструкцию называемую lambda,2 которая позволяет нам проверить что он не меняет количество User:lambda do
post :create, :user => @attr
end.should_not change(User, :count)
lambda в данный момент может показаться странной, в следующих тестах будет много примеров и картина быстро прояснится.8.2.2 Работающая форма
Мы можем получить прохождение тестов из Раздела 8.2.1 с кодом из Листинга 8.7. Этот листинг включает в себя второй вариант использованияrender метода, который мы впервые увидели в контексте партиалов (частичных шаблонов) (Раздел 5.1.3); как вы можете видеть, render также работает в контроллере действий. Обратите внимание, что мы воспользовались этой возможностью, чтобы представить if-else ветвящиеся структуры, которые позволяют нам обрабатывать случаи сбоя и успеха раздельно, в зависимости от значения @user.save.
Листинг 8.7.
create действие которое может обрабатывать сбой регистрации (но не успех). app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def create
@user = User.new(params[:user])
if @user.save
# Обработка успешного сохранения.
else
@title = "Sign up"
render 'new'
end
end
end
Рисунок 8.6: Сбой регистрации с хэшем
params (полный размер)params в отладочной информации, отображаемой в нижней части страницы Рис. 8.6:--- !map:ActiveSupport::HashWithIndifferentAccess
commit: Sign up
authenticity_token: rB82sI7Qw5J9J1UMILG/VQL411vH5puR+Jw1xL5cMQ=
action: create
controller: users
user: !map:ActiveSupport::HashWithIndifferentAccess
name: Foo Bar
password_confirmation: dude
password: dude
email: foo@invalid
params содержит информацию о каждом запросе; в случае URL типа /users/1, значение params[:id] это id соответствующего пользователя (1 в этом примере). В случае отправки регистрационной формы, params вместо этого содержит хэш хэшей, конструкцию, которую мы впервые видели в Разделе 4.3.3, который представил стратегически названную params переменную в консольной сессии. Эта отладочная информация показывает, что предоставление (отправка) формы дает в результате user хэш с атрибутами, соответствующими предоставленным значениям, где ключи происходят от name атрибутов тега input который мы видели в Листинге 8.2; например, значение<input id="user_email" name="user[email]" size="30" type="text" />
"user[email]" это именно email атрибут хэша user.Хотя хэш-ключи отображаются как строки в отладочном выводе, внутренне Rails использует символы, так что
params[:user] на самом деле является хэшем атрибутов пользователей, именно тех атрибутов, что необходимы в качестве аргумента для User.new, как мы впервые видели в Разделе 4.4.5 и как представлено в Листинге 8.7. Это означает, что строка@user = User.new(params[:user])
@user = User.new(:name => "Foo Bar", :email => "foo@invalid",
:password => "dude", :password_confirmation => "dude")
Конечно, такой экземпляр переменной имеет значение для успешной регистрации, как мы увидим в Разделе 8.3, как только
@user правильно определена, вызов @user.save
это все, что необходимо для завершения регистрации — но это имеет свои
последствия даже в случае сбоя регистрации, рассматриваемого здесь.
Обратите внимание на то, что на Рис. 8.6 поля предварительно заполнены данными из провальной отправки. Это происходит потому что form_for автоматически заполняет поля атрибутами объекта @user, так что, например, если @user.name это "Foo", то<%= form_for(@user) do |f| %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
.
.
.
<form action="/users" class="new_user" id="new_user" method="post">
<div class="field">
<label for="user_name">Name</label><br />
<input id="user_name" name="user[name]" size="30" type="text" value="Foo"/>
</div>
.
.
.
value тега input это "Foo", что и появляется в текстовом поле.8.2.3 Сообщения об ошибках при регистрации
Хотя это и не является строго обязательным, все же полезно выводить сообщения об ошибках при сбое регистрации, чтобы указать на проблемы, которые помешали успешной регистрации пользователя. Rails предоставляет именно такие сообщения, основанные на валидациях модели User. Рассмотрим, например, попытку сохранения пользователя с неправильным адресом электронной почты и коротким паролем:$ rails console
>> user = User.new(:name => "Foo Bar", :email => "foo@invalid",
?> :password => "dude", :password_confirmation => "dude")
>> user.save
=> false
>> user.errors.full_messages
=> ["Email is invalid", "Password is too short (minimum is 6 characters)"]
errors.full_messages (который мы видели кратко в Разделе 6.2.1) содержит массив сообщений об ошибках.Как и в консольной сессии выше, сбой сохранения в Листинге 8.7 генерирует список сообщений об ошибках, связанных с объектом
@user. Для отображения сообщения в браузере, мы render партиал error_messages в user new страницу (Листинг 8.8).3
Листинг 8.8. Код для отображения сообщений об ошибках в форме регистрации.
app/views/users/new.html.erb
<h1>Sign up</h1>
<%= form_for(@user) do |f| %>
<%= render 'shared/error_messages' %>
.
.
.
<% end %>
render партиал ’shared/error_messages’;
что отражает общую конвенцию Rails, которая помещает частичные шаблоны,
которые мы планируем использовать во многих контроллерах, в специально
отведенный каталог shared/. (Мы увидим что эти планы реализованы в Разделе 10.1.1.) Это означает, что мы должны создать этот новый каталог вместе с файлом партиала _error_messages.html.erb. Сам партиал представлен в Листинге 8.9.
Листинг 8.9. Партиал для отображения сообщений об ошибках отправки формы регистрации.
app/views/shared/_error_messages.html.erb
<% if @user.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@user.errors.count, "error") %>
prohibited this user from being saved:</h2>
<p>There were problems with the following fields:</p>
<ul>
<% @user.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
Array. Давайте откроем сеанс консоли, чтобы увидеть, как они работают. Первый метод это count, который просто возвращает количество элементов в объекте:$ rails console
>> a = [1, 2, 3]
=> [1, 2, 3]
>> a.count
=> 3
any?, который (совместно с empty?) является одним из пары комплиментарных методов:>> [].empty?
=> true
>> [].any?
=> false
>> a.empty?
=> false
>> a.any?
=> true
empty? метод, который мы впервые увидели в Разделе 4.2.3 в контексте строк, также работает с массивами, возвращая true для пустого массива и false в противном случае. Метод any? это просто противоположность empty?, он возвращает true если в массиве есть какие-нибудь элементы и false в противном случае.Другая новая идея это текстовый хелпер
pluralize. Он не доступен в консоли, но мы можем включить его явно через модуль ActionView::Helpers::TextHelper:4>> include ActionView::Helpers::TextHelper
=> Object
>> pluralize(1, "error")
=> "1 error"
>> pluralize(5, "error")
=> "5 errors"
pluralize принимает целочисленный
аргумент и возвращает число с правильной версией множественного числа
его второго аргумента. В основе этого метода лежит мощный инфлектор,
который знает как преобразовать во множественное число огромное
количество слов (в том числе, многие с неправильным множественным
числом):>> pluralize(2, "woman")
=> "2 women"
>> pluralize(3, "erratum")
=> "3 errata"
<%= pluralize(@user.errors.count, "error") %>
"1 error" или "2 errors" (и т.д.) в зависимости от количества ошибок.Обратите внимание, что Листинг 8.9 включает CSS id
error_explanation для использования в стилизации сообщений об ошибках. (Напомним, из Раздела 5.1.2 что CSS использует знак решетки # для стилизации id.) Кроме того, Rails автоматически помещает поля с ошибками в divы с CSS классом field_with_errors. Эти метки затем позволят нам отредактироваь стиль сообщений об ошибках с CSS показанным в Листинге 8.10. Результат сбоя регистрации с сообщениями об ошибке представлен в Рис. 8.7.
Поскольку сообщения генерируются валидациями модели, они автоматически
изменятся, если вы когда-нибудь поменяете свое мнение о, скажем, формате
адресов электронной почты, или минимальной длине паролей.
Листинг 8.10. CSS для оформления сообщений об ошибках.
public/stylesheets/custom.css
.
.
.
.field_with_errors {
margin-top: 10px;
padding: 2px;
background-color: red;
display: table;
}
.field_with_errors label {
color: #fff;
}
#error_explanation {
width: 400px;
border: 2px solid red;
padding: 7px;
padding-bottom: 12px;
margin-bottom: 20px;
background-color: #f0f0f0;
}
#error_explanation h2 {
text-align: left;
font-weight: bold;
padding: 5px 5px 5px 15px;
font-size: 12px;
margin: -7px;
background-color: #c00;
color: #fff;
}
#error_explanation p {
color: #333;
margin-bottom: 0;
padding: 5px;
}
#error_explanation ul li {
font-size: 12px;
list-style: square;
}
Рисунок 8.7: Сбой регистрации с сообщениями об ошибках. (полный размер)
8.2.4 Фильтрация логгируемых параметров
Прежде чем перейти к успешной регистрации, нужно довести до конца одно дело. Вы могли заметить, что, хотя приложили большие усилия, чтобы зашифровать пароль в Главе 7, как пароль так и его подтверждение появляются в виде чистого текста в отладочной информации. Само по себе это не проблема, вспомните из Листинга 6.23 что эта информация отображается только для приложений, работающих в режимеdevelopment,
поэтому реальные пользователи никогда не увидят этого, но это намек на
потенциальную проблему: пароли могут также появиться в незашифрованном
виде в log файле который Rails использует для записи информации
о выполнении приложения. Действительно, в предыдущих версиях Rails,
development log файл в этом случае мог содержать строки, подобные
показанным в Листинге 8.11.
Листинг 8.11. Development log предыдущих (до 3) версий Rails с видимыми паролями.
log/development.log
Parameters: {"commit"=>"Sign up", "action"=>"create",
"authenticity_token"=>"K1HchFF8uYE8ZaQKz5DVG9vF2KGoXJu4JGp/VE3NMjA=",
"controller"=>"users",
"user"=>{"name"=>"Foo Bar", "password_confirmation"=>"dude",
"password"=>"dude", "email"=>"foo@invalid"}}
password атрибуты фильтруются автоматически, как показано в Листинге 8.12. Мы видим, что строка "[FILTERED]" появляется на месте пароля и его подтверждения. (В production, лог-файлом будет log/production.log, и фильтрация будет работать аналогичным образом.)
Листинг 8.12. Лог-файл с фильтруемыми паролями.
log/development.log
Parameters: {"commit"=>"Sign up", "action"=>"create",
"authenticity_token"=>"K1HchFF8uYE8ZaQKz5DVG9vF2KGoXJu4JGp/VE3NMjA=",
"controller"=>"users",
"user"=>{"name"=>"Foo Bar", "password_confirmation"=>"[FILTERED]",
"password"=>"[FILTERED]", "email"=>"foo@invalid"}}
application.rb (Листинг 8.13).
Листинг 8.13. Дефолтная фильтрация паролей.
config/application.rb
require File.expand_path('../boot', __FILE__)
require 'rails/all'
# If you have a Gemfile, require the gems listed there, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(:default, Rails.env) if defined?(Bundler)
module SampleApp
class Application < Rails::Application
.
.
.
# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters += [:password]
end
end
password,
вы должны добавить его в массив фильтруемых параметров. Например, если у
вас включен секретный код как часть процесса регистрации, вы можете
включить строку<div class="field">
<%= f.label :secret_code %><br />
<%= f.password_field :secret_code %>
</div>
:secret_code в application.rb следующим образом:config.filter_parameters += [:password, :secret_code]
8.3 Успешная регистрация
Получив обработку недействительной предоставляемой формы, теперь пришло время для завершения регистрационной формы, на самом деле сохранив нового пользователя (если валидный) в базу данных. Во-первых, мы попробуем сохранить пользователя; если сохранение пройдет успешно, информация пользователя будет записана в базу данных автоматически, а затем мы перенаправим браузер на страницу профиля пользователя (с дружеским приветствием), как показано в макете на Рис. 8.8. Если это не удастся, мы просто отступим к сценарию, разработанному в Разделе 8.2.
Рисунок 8.8: Макет успешной регистрации. (полный размер)
8.3.1 Тестирование успешной регистрации
Тесты для успешной регистрации следуют примеру тестов для сбоя регистрации из Листинга 8.6. Давайте посмотрим на результат, показанный в Листинге 8.14.
Листинг 8.14. Тесты для успешной регистрации.
spec/controllers/users_controller_spec.rb
require 'spec_helper'
describe UsersController do
render_views
.
.
.
describe "POST 'create'" do
.
.
.
describe "success" do
before(:each) do
@attr = { :name => "New User", :email => "user@example.com",
:password => "foobar", :password_confirmation => "foobar" }
end
it "should create a user" do
lambda do
post :create, :user => @attr
end.should change(User, :count).by(1)
end
it "should redirect to the user show page" do
post :create, :user => @attr
response.should redirect_to(user_path(assigns(:user)))
end
end
end
end
post :create для обращения к create действию с HTTP запросом POST. Как и в тестах для провального создания Листинг 8.6, первый тест сворачивает создание пользователя в lambda и использует count метод для проверки того, что база данных изменилась соответствующим образом:it "should create a user" do
lambda do
post :create, :user => @attr
end.should change(User, :count).by(1)
end
should_not change(User, :count) как в случае провального создания пользователя, мы имеем should change(User, :count).by(1), которое утверждает, что lambda блок должен изменить количество User на 1.Второй тест использует
assigns метод, впервые показанный в Листинге 7.17 чтобы проверить, что действие create перенаправляется на вновь созданную страницу user’s show:it "should redirect to the user show page" do
post :create, :user => @attr
response.should redirect_to(user_path(assigns(:user)))
end
user_path, показанного в Таблице 7.1.8.3.2 Завершение формы регистрации
Чтобы получить прохождение этих тестов, и, тем самым, завершить рабочую регистрационную форму, заполните закомментированный раздел в Листинге 8.7 переадресацией, как показано в Листинге 8.15.
Листинг 8.15. Действие user
create с сохранением и перадресацией. app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def create
@user = User.new(params[:user])
if @user.save
redirect_to @user
else
@title = "Sign up"
render 'new'
end
end
end
user_path в переадресации, написав просто redirect_to @user для перенаправления на страницу показывающую пользователя, в соответствии с конвенцией, которую мы видели ранее в контексте link_to в Листинге 7.25. Этот синтаксис приятно краток, но, к сожалению RSpec не понимает его, поэтому в тестах мы должны использовать более подробный user_path(@user).8.3.3 Флэш
Перед отправкой валидной регистрации в браузер, мы собираемся немного отполировать ее, в соответствии с общепринятой в веб приложениях идеей: добавив сообщение, которое временно появляется, а затем исчезает при перезагрузке страницы. (Если сейчас что-то непонятно, будьте терпеливы; Конкретный пример появится в ближайшее время) Rails-способ сделать это состоит в использовании специальной переменной flash, которая работает как флэш-память в том, что она хранит свои данные временно. Переменнаяflash это на самом деле хэш; и вы можете даже вспомнить консольный пример в Разделе 4.3.3, где мы видели, как для перебора хэша использовался стратегически именованный хэш flash. Чтобы вспомнить, попробуйте эту консольную сессию:$ rails console
>> flash = { :success => "It worked!", :error => "It failed. :-(" }
=> {:success=>"It worked!", :error => "It failed. :-("}
>> flash.each do |key, value|
?> puts "#{key}"
?> puts "#{value}"
>> end
success
It worked!
error
It failed. :-(
Листинг 8.16. Добавление содержимого
flash переменной в макет сайта. app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
.
.
.
<%= render 'layouts/header' %>
<section class="round">
<% flash.each do |key, value| %>
<div class="flash <%= key %>"><%= value %></div>
<% end %>
<%= yield %>
</section>
.
.
.
</html>
div тег, с CSS классом, указывающим на тип сообщения. Например, если flash[:success] = "Welcome to the Sample App!", то код<% flash.each do |key, value| %>
<div class="flash <%= key %>"><%= value %></div>
<% end %>
<div class="flash success">Welcome to the Sample App!</div>
flash[:error] используемое для индикации неудавшегося входа на сайт.7Давайте протестируем флэш сообщение, чтобы убедиться в его правильности и в том, что оно появляется под ключом
:success (Листинг 8.17).
Листинг 8.17. Тест для флэш сообщения об успешной регистрации.
spec/controllers/users_controller_spec.rb
require 'spec_helper'
describe UsersController do
render_views
.
.
.
describe "POST 'create'" do
.
.
describe "success" do
.
.
.
it "should have a welcome message" do
post :create, :user => @attr
flash[:success].should =~ /welcome to the sample app/i
end
end
end
end
email_regex Листинга 6.17).
Вместо того, чтобы тестировать полное флэш сообщение, мы просто
тестируем на существование “welcome to the sample app”. (Обратите
внимание, что у нас еще нет теста для внешнего вида реального HTML флэш
сообщений; мы исправим это тестированием фактического тега Раздел 8.4.3.)Если вы много программировали ранее, скорее всего, вы уже знакомы с регулярными выражениями, но вот быстрая
console сессия в случае если вам необходимо введение:>> "foo bar" =~ /Foo/ # Regex сравнение по умолчанию чувствительно к регистру.
=> nil
>> "foo bar" =~ /foo/
=> 0
nil; для совпадения оно возвращает index (позицию, место) в строке где началось совпадение.8 Обычно точный индекс не имеет значения, так как сравнение, как правило, используется в булевом контексте: вспомните из Раздела 4.2.3 что nil это false в булевом контексте и что все остальное (даже 0) является истиной. Таким образом, мы можем написать код, подобный этому:>> success = "Welcome to the Sample App!"
=> "Welcome to the Sample App!"
>> "It's a match!" if success =~ /welcome to the sample app/
=> nil
/.../i чтобы добиться нечувствительного к регистру совпадения:>> "It's a match!" if success =~ /welcome to the sample app/i
=> "It's a match!"
flash[:success] в действии create как в Листинге 8.18. Сообщения используют различный регистр, но тесты пройдут в любом случае так как в конце регулярного выражения стоит i. Таким образом, мы не испортим тест, если напишем, например, sample app вместо Sample App.
Листинг 8.18. Добавление флэш сообщения в регистрацию пользователя.
app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def create
@user = User.new(params[:user])
if @user.save
flash[:success] = "Welcome to the Sample App!"
redirect_to @user
else
@title = "Sign up"
render 'new'
end
end
end
8.3.4 Первая регистрация
Вы можете увидеть результат всей этой работы, зарегистрировав нашего первого пользователя (под именем “Rails Tutorial” и с адресом электронной почты “example@railstutorial.org”), которые показывают дружеское послание после успешной регистрации, как показано на Рис. 8.9. (приятный зеленый дизайн дляsuccess класса был включен с помощью Blueprint CSS фреймворка из Раздела 4.1.2.) Затем, после перезагрузки страницы показывающей пользователя, флэш сообщение исчезает, как и было обещано (Рис. 8.10).
Рисунок 8.9: Результат успешной регистрации, с флеш сообщением. (полный размер)
Рисунок 8.10: Страница профиля пользователя с исчезнувшим после перезагрузки браузера флэш сообщением. (полный размер)
$ rails console
>> user = User.first
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org",
created_at: "2010-02-17 03:07:53", updated_at: "2010-02-17 03:07:53",
encrypted_password: "48aa8f4444b71f3f713d87d051819b0d44cd89f4a963949f201...",
salt: "f52924ba502d4f92a634d4f9647622ccce26205176cceca2adc...">
8.4 RSpec интеграционные тесты
В принципе, мы закончили с регистрацией пользователей, но вы могли заметить, что мы не протестировали структуру формы регистрации, также мы не проверили, что предоставление информации действительно работает. Конечно, мы должны проверить это, просмотрев страницы в нашем браузере, но весь смысл автоматизированного тестирования в том, чтобы убедиться, что раз уж вещи работают, то они и останутся таковыми. Создание таких тестов - цель этого раздела и результаты довольно приятны.Один метод тестирования мог бы проверять структуру HTML формы (с использованием
render_views и have_selector метода), и это действительно хороший способ для тест-драйва представлений. (Раздел 8.6
имеет упражнения на этот счет.) Но я предпочитаю не тестировать
детальную структуру HTML представлений—я не вижу никаких причин, почему
мы должны знать, что Rails реализует предоставление пользовательского
email используя name="user[email]", к тому же, любые тесты
этой структуры будут сломаны, если будущие версии Rails изменят эту
конвенцию. Кроме того, было бы неплохо иметь тесты для всего процесса
регистрации: посещение страницы регистрации, заполнение формы, нажатие
на кнопку, и проверка (если предоставленные данные валидны) того, что
новый пользователь создается в (тестовой) базе данных.Хотя это не единственный способ (см. Блок 8.1), я предпочитаю решать эту проблему используя RSpec интеграционные тесты, которые мы впервые использовали в Разделе 5.2.1 для тестирования пользовательских маршрутов (например /about для About страницы). В том разделе мы видели только небольшую часть мощности интеграционных тестов; начиная с этого раздела, мы увидим, насколько удивительными они могут быть.
8.4.1 Интеграционные тесты со стилем
Мы видели в Листинге 5.13 что RSpec интеграционные тесты поддерживают контроллер-тест–стиль конструкции, такие какget '/'
response.should have_selector('title', :content => "Home")
visit signin_path
fill_in "Name", :with => "Example User"
click_button
8.4.2 Сбой регистрации пользователя не должен создавать нового пользователя
Теперь мы готовы сделать интеграционный тест для регистрации пользователей. Как мы видели в Разделе 5.2.1, RSpec поставляется с генератором для изготовления подобных интеграционных specs; в данном случае, наши интеграционные тесты будут содержать различные действия, предпринимаемые пользователями, поэтому мы назовем тестыusers:$ rails generate integration_test users
invoke rspec
create spec/requests/users_spec.rb
users_spec.rb.10Мы начнем со сбоя регистрации. Простой способ организовать провальную регистрацию, это посетить signup URL и просто нажать на кнопку, в результате должна появиться страница как в Рис. 8.11. После сбоя предоставления, ответ должен сформировать
users/new шаблон. Если вы проверите результирующий HTML, вы должны увидеть что-то вроде разметки в Листинге 8.19. Это означает, что мы можем проверить на наличие сообщений об ошибках, поиском тега div с CSS id "error_explanation". Тест для этого шага представлен в Листинге 8.20.
Рисунок 8.11: Результат посещения /signup и просто клика “Sign up”. (полный размер)
Листинг 8.19.
div error_explanation из страницы на Рис. 8.11.
<div class="error_explanation" id="error_explanation">
<h2>5 errors prohibited this user from being saved</h2>
<p>There were problems with the following fields:</p>
<ul>
<li>Name can't be blank</li>
<li>Email can't be blank</li>
<li>Email is invalid</li>
<li>Password can't be blank</li>
<li>Password is too short (minimum is 6 characters)</li>
</ul>
</div>
Листинг 8.20. Тестирование сбоя регистрации.
spec/requests/users_spec.rb
require 'spec_helper'
describe "Users" do
describe "signup" do
describe "failure" do
it "should not make a new user" do
visit signup_path
fill_in "Name", :with => ""
fill_in "Email", :with => ""
fill_in "Password", :with => ""
fill_in "Confirmation", :with => ""
click_button
response.should render_template('users/new')
response.should have_selector("div#error_explanation")
end
end
end
end
"div#error_explanation" это CSS-вдохновленное сокращение для<div id="error_explanation">...</div>
User не изменилось. Как мы видели в Листинге 8.6 и Листинге 8.14, это может быть достигнуто с lambda. В тех случаях блок lambda содержал только одну строку, но мы видим в Листинг 8.21 Листинге 8.21 что она может обвернуть несколько строк так же легко.
Листинг 8.21. Тестирование сбоя регистрации с
lambda. spec/requests/users_spec.rb
require 'spec_helper'
describe "Users" do
describe "signup" do
describe "failure" do
it "should not make a new user" do
lambda do
visit signup_path
fill_in "Name", :with => ""
fill_in "Email", :with => ""
fill_in "Password", :with => ""
fill_in "Confirmation", :with => ""
click_button
response.should render_template('users/new')
response.should have_selector("div#error_explanation")
end.should_not change(User, :count)
end
end
end
end
should_not change(User, :count)
lambda блока не меняет значение User.count.Интеграционный тест в Листинге 8.21 связывает воедино все части Rails, включая модели, представления, контроллеры, маршрутизацию и хелперы. Он обеспечивает непрерывную цепь проверяющую, что наш регистрационный механизм исправно работает, по крайней мере для случая сбоя регистрации.
8.4.3 Успешная регистрация пользователя должна создавать нового пользователя
Теперь мы переходим к интеграцоннму тесту для успешной регистрации. В этом случае нам необходимо заполнить поля регистрации валидными данными пользователя. Когда мы закончим, результом должна быть страница показывающая пользователя с “flash success”div тегом, и это должно изменить количество User на 1. Листинг 8.22 показывает, как это сделать
Листинг 8.22. Тестирование успешной регистрации.
spec/requests/users_spec.rb
require 'spec_helper'
describe "Users" do
describe "signup" do
.
.
.
describe "success" do
it "should make a new user" do
lambda do
visit signup_path
fill_in "Name", :with => "Example User"
fill_in "Email", :with => "user@example.com"
fill_in "Password", :with => "foobar"
fill_in "Confirmation", :with => "foobar"
click_button
response.should have_selector("div.flash.success",
:content => "Welcome")
response.should render_template('users/show')
end.should change(User, :count).by(1)
end
end
end
end
fill_in :user_name тоже работает.11 (Это особенно хорошо для форм, которые не используют labels(??).)Я надеюсь, вы согласитесь, что этот синтаксис веб-навигации невероятно естественный и краткий. Например, чтобы заполнить поля значениями, мы просто используем такой код:
fill_in "Name", :with => "Example User"
fill_in "Email", :with => "user@example.com"
fill_in "Password", :with => "foobar"
fill_in "Confirmation", :with => "foobar"
fill_in являются значения
меток, т. е. именно тот текст, который пользователь видит в браузере;
нет необходимости знать что-нибудь о лежащей в его основе структуре
HTML, сгенерированной Rails хелпером form_for.Наконец, мы подошли к coup de grâce—тестированию того, что успешная регистрация действительно создает пользователя в базе данных:
it "should make a new user" do
lambda do
.
.
.
end.should change(User, :count).by(1)
lambda блок. В этом случае, вместо того, чтобы убедиться, что количество пользователей не
меняется, мы убедимся, что оно увеличивается на 1 за счет записи
пользователя, создаваемой в тестовой базе данных. Результат выглядит
следующим образом:$ bundle exec rspec spec/requests/users_spec.rb
..
Finished in 2.14 seconds
2 examples, 0 failures
8.5 Заключение
Возможность регистрировать пользователей это важная веха для нашего приложения. Хотя пример приложения до сих пор не делает ничего полезного, мы заложили необходимый фундамент для будущего развития. В следующих двух главах, мы завершим еще две основные вехи: во-первых, в Главе 9 мы завершим нашу аутентификационную машинерию, позволив пользователям входить и выходить из приложения, во-вторых, в Главе 10 мы позволим всем пользователям обновлять информацию в их учетных записях и позволим администраторам сайта удалять пользователей, а также добавим защиту страниц для обеспечения безопасности сайта, завершив тем самым полный набор REST действий для ресурса Users из Таблицы 6.2.Как обычно, если вы используете Git, вы должны объединить ваши изменения c главной веткой:
$ git add .
$ git commit -m "User signup complete"
$ git checkout master
$ git merge signing-up
8.6 Упражнения
- Используя модель из Листинга 8.23, напишите тесты для проверки на наличие каждого поля в регистрационной форме. (Не забудьте строку
render_views, необходимую для работы этих тестов.) - Зачастую регистрационные формы очищают поле пароля при сбое регистрации, как показано на Рис. 8.12. Модифицируйте действие
createконтроллера Users, чтобы изменить соответствующим образом поведение нашего приложения. Подсказка: Сброс@user.password. - HTML флэша в Листинге 8.16 это, практически, уродская комбинация HTML и ERb. Проверьте, запустив набор тестов, что более чистый код в Листинге 8.24, использующий Rails хелпер
content_tag, тоже работает.
Листинг 8.23. Шаблон для написания тестов на наличие каждого поля в регистрационной форме.
spec/controllers/users_controller_spec.rb
require 'spec_helper'
describe UsersController do
render_views
.
.
.
describe "GET 'new'" do
.
.
.
it "should have a name field" do
get :new
response.should have_selector("input[name='user[name]'][type='text']")
end
it "should have an email field"
it "should have a password field"
it "should have a password confirmation field"
end
.
.
.
end
Рисунок 8.12: Провальная регистрационная форма с очищенным полем пароля. (полный размер)
Листинг 8.24. The
flash ERb в макете сайта, использующий content_tag. app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
.
.
.
<section class="round">
<% flash.each do |key, value| %>
<%= content_tag(:div, value, :class => "flash #{key}") %>
<% end %>
<%= yield %>
</section>
.
.
.
</html>
- Если вы получили ошибку вроде
views/users/new.html.erb_spec.rb fails, удалите этот мерзкий view specs командой$ rm -rf spec/views. ↑ - Название происходит от лямбда-исчисления, математической системы для представления функций и их операций. ↑
- До Rails 3, отображение сообщений об ошибках было реализовано через магический вызов специального метода
error_messagesна объект формыf, следующим образом: <%= f.error_messages %>. Хотя зачастую это было удобно, этот волшебный метод было тяжело настроить, так что команда Rails Core решила рекомендовать использование Embedded Ruby для отображения ошибок вручную. ↑ - Я выяснил это путем поиска
pluralizeв Rails API. ↑ - В случае, если вам интересно, код ответа 302, в отличие от “постоянной переадресации” 301 кратко обсуждаемой в Блоке 3.2. ↑
- Обратите внимание, что ключ
:successявляется символом, но Embedded Ruby автоматически преобразует его в строку"success"перед вставкой в шаблон. ↑ - На самом деле, мы будем использовать тесно связаный
flash.now, но мы не будем акцентироваться на этом ньюансе, до тех пор, пока он нам не понадобится. ↑ - Индексы имеют нулевое смещние, как и массивы (Раздел 4.3.1), поэтому возвращаемое значение,
0означает что строка совпадает с регулярным выражением, начиная с первого символа. ↑ - Что касается этой записи, этот синтаксис доступен благодаря Webrat, (# WebКрыса), которая предоставляется как гем зависимость для rspec-rails, но Webrat была написана до широкого внедрения Rack и в конечном итоге будет вытеснена проектом Capybara. К счастью, Capybara разработан как прямая замена для Webrat, поэтому синтаксис должен остаться тем же. ↑
- Обратите внимание на множественное число; это не User spec
user_spec.rb, который является тестом модели, а не интеграционным тестом. ↑ - Вы можете использовать Firebug или ’s “Исходный код
страницы” вашего браузера, если вам необходимо выяснить id. Или вы
можете заметить, что Rails использует имя ресурса и имя атрибута
разделенные знаком подчеркивания, что приводит к
user_name,user_email, и т.д.. ↑
Комментариев нет:
Отправить комментарий