пятница, 12 октября 2012 г.

Ruby on Rails Tutorial 8

Глава 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
blank_signup_page
Рисунок 8.1: Страница регистрации на данный момент /signup(полный размер)
signup_mockup
Рисунок 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>
создает HTML, необходимый, чтобы сделать маркированный элемент текстового поля, подходящий для установки 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;
}
signup_form
Рисунок 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>
Мы начнем с внутренней структуры. Сравнивая Листинг 8.2 с Листингом 8.5, мы видим, что Embedded Ruby
<div class="field">
  <%= f.label :name %><br />
  <%= f.text_field :name %>
</div>
производит HTML
<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>
производит HTML
<div class="field">
  <label for="user_password">Password</label><br />
  <input id="user_password" name="user[password]" size="30" type="password" />
</div>
Как видно в Рис. 8.4, текстовые поля (type="text") просто отображает их содержимое, в то время как поле пароля (type="password") скрывает ввод в целях безопасности, как показано на Рис. 8.4.
filled_in_form
Рисунок 8.4: Заполненная форма, показывающая разницу между text и password полями. (полный размер)
Как мы увидим в Разделе 8.3, ключ к созданию пользователя это специальный атрибут 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>
Здесь Rails использует специальное уникальное значение, чтобы помешать конкретному виду межсайтовой скриптовой атаки, называемой forgery (подделка); см. the Stack Overflow entry on the Rails authenticity token если вам интересны детали того, как это работает и почему это важно. К счастью, Rails позаботился о проблеме за вас, и input тег hidden (скрытый), так что вы не должны дважды думать о нем, но его видно, когда вы смотрите исходный код формы, поэтому я хотел по крайней мере упомянуть об этом.

8.2 Сбой регистрации

Хотя мы кратко рассмотрели HTML формы, показанной в Рис. 8.3 (Листинг 8.5), он (HTML) лучше всего понимается в контексте сбоя регистрации. Просто получить регистрационную форму, которая принимает недействительные данные и вновь показывает страницу регистрации (как на макете в Рис. 8.5) является значительным достижением, и это будет целью данного раздела.
signup_failure_mockup
Рисунок 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">
Как отмечено в Разделе 8.1.2, этот HTML выдает POST запрос к /users URL. По аналогии с методом 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
Последние два теста относительно просты: мы убеждаемся, что заголовок правильный, а потом проверяем, что не удавшаяся попытка регистрации просто повторно показывает new user страницу (используя RSpec метод 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)
Это откладывает Active Record 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.7 это отправить форму с некоторыми невалидными регистрационными данными; результаты представлены в Рис. 8.6.
signup_failure_rails_3
Рисунок 8.6: Сбой регистрации с хэшем params (полный размер)
Чтобы получить более четкую картину того, как Rails обрабатывает предоставление регистрационных данных, давайте поближе познакомимся с хэшем 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
Мы видели, начиная с Раздела 6.3.2, что хэш 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")
А это именно тот формат который необходим для инициализации объекта модели User с заданными атрибутами.
Конечно, такой экземпляр переменной имеет значение для успешной регистрации, как мы увидим в Разделе 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>
  .
  .
  .
будет производить HTML
<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 %>
Этот частичный шаблон вводит несколько новых Rails и Ruby конструкций, в том числе два метода для объектов класса 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;
}
signup_error_messages
Рисунок 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"}}
Хранение незашифрованных паролей в лог-файлах было бы страшным нарушением безопасности, если бы кто-нибудь овладел файлом, он потенциально мог получить пароли каждого пользователя в системе. (Конечно, здесь показана провальная регистрация, но для успешной регистрации проблема аналогична.) Эта проблема была настолько распространена в Rails приложениях, что Rails 3 реализовал новое значение по умолчанию: теперь все 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
Если вы когда-нибудь будете писать Rails приложение с именем защищаемого параметра, отличным от 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.
signup_success_mockup
Рисунок 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
Как и тестами сбоя регистрации (Листинг 8.6), здесь мы используем 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
Этот вид переадресации происходит при, практически, каждой успешной отправке формы в вебе, и, благодаря удобному синтаксису RSpec, вы не должны ничего знать о лежащем в его основе коде ответа HTTP.5 Сам URL генерируется с использованием именованного маршрута 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.
Листинг 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 %>
произведет такой HTML:6
<div class="flash success">Welcome to the Sample App!</div>
Причина, по которой мы перебираем все возможные пары ключ/значение заключается в том, что благодаря этому мы сможем включать другие виды флэш сообщений; например, в Листинге 9.8 мы увидим 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
Значения, возвращаемые здесь консолью, могут выглядеть странно: для несовпадения, regex сравнение возвращает 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).
signup_flash
Рисунок 8.9: Результат успешной регистрации, с флеш сообщением. (полный размер)
signup_flash_reloaded
Рисунок 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")
Однако это не единственный вид поддерживаемого синтаксиса, RSpec интеграционные тесты также поддерживают очень выразительный синтаксис веб-навигации.9 В этом разделе, мы увидим как использовать этот синтаксис для имитации заполнения регистрационной формы, используя код вида:
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
Как и в Разделе 5.2.1, генератор автоматически добавляет spec идентификатор, что приводит название файла теста к виду users_spec.rb.10
Мы начнем со сбоя регистрации. Простой способ организовать провальную регистрацию, это посетить signup URL и просто нажать на кнопку, в результате должна появиться страница как в Рис. 8.11. После сбоя предоставления, ответ должен сформировать users/new шаблон. Если вы проверите результирующий HTML, вы должны увидеть что-то вроде разметки в Листинге 8.19. Это означает, что мы можем проверить на наличие сообщений об ошибках, поиском тега div с CSS id "error_explanation". Тест для этого шага представлен в Листинге 8.20.
blank_signup
Рисунок 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>
Обратите внимание, какой естественный язык в Листинге 8.20. Единственная проблема заключается в том, что это (# написано по-английски?) немного не тот тест который мы хотели: мы на самом деле не протестировали что сбой предоставления формы приводит к сбою создания нового пользователя. Чтобы так сделать, нам нужно свернуть шаги теста в один пакет, а затем проверить что количество 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
Как и в Листинг 8.6, это использует
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
Кстати, хотя это не очевидно из документации RSpec, вы можете использовать HTML-атрибут 'id' текстового поля вместо содержимого связанного тега label, так что 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)
Как и в Листинге 8.21, мы завернули код для успешной регистрации в 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 Упражнения

  1. Используя модель из Листинга 8.23, напишите тесты для проверки на наличие каждого поля в регистрационной форме. (Не забудьте строку render_views, необходимую для работы этих тестов.)
  2. Зачастую регистрационные формы очищают поле пароля при сбое регистрации, как показано на Рис. 8.12. Модифицируйте действие create контроллера Users, чтобы изменить соответствующим образом поведение нашего приложения. Подсказка: Сброс @user.password.
  3. 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
cleared_password
Рисунок 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>
  1. Если вы получили ошибку вроде views/users/new.html.erb_spec.rb fails, удалите этот мерзкий view specs командой $ rm -rf spec/views
  2. Название происходит от лямбда-исчисления, математической системы для представления функций и их операций. 
  3. До Rails 3, отображение сообщений об ошибках было реализовано через магический вызов специального метода error_messages на объект формы f, следующим образом: <%= f.error_messages %>. Хотя зачастую это было удобно, этот волшебный метод было тяжело настроить, так что команда Rails Core решила рекомендовать использование Embedded Ruby для отображения ошибок вручную. 
  4. Я выяснил это путем поиска pluralize в Rails API. 
  5. В случае, если вам интересно, код ответа 302, в отличие от “постоянной переадресации” 301 кратко обсуждаемой в Блоке 3.2
  6. Обратите внимание, что ключ :success является символом, но Embedded Ruby автоматически преобразует его в строку "success" перед вставкой в шаблон. 
  7. На самом деле, мы будем использовать тесно связаный flash.now, но мы не будем акцентироваться на этом ньюансе, до тех пор, пока он нам не понадобится. 
  8. Индексы имеют нулевое смещние, как и массивы (Раздел 4.3.1), поэтому возвращаемое значение, 0 означает что строка совпадает с регулярным выражением, начиная с первого символа. 
  9. Что касается этой записи, этот синтаксис доступен благодаря Webrat, (# WebКрыса), которая предоставляется как гем зависимость для rspec-rails, но Webrat была написана до широкого внедрения Rack и в конечном итоге будет вытеснена проектом Capybara. К счастью, Capybara разработан как прямая замена для Webrat, поэтому синтаксис должен остаться тем же. 
  10. Обратите внимание на множественное число; это не User spec user_spec.rb, который является тестом модели, а не интеграционным тестом. 
  11. Вы можете использовать Firebug или ’s “Исходный код страницы” вашего браузера, если вам необходимо выяснить id. Или вы можете заметить, что Rails использует имя ресурса и имя атрибута разделенные знаком подчеркивания, что приводит к user_name, user_email, и т.д.. 

Комментариев нет:

Отправить комментарий