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

Регистрация

Применение form_for

Теперь, когда у нас есть хорошие провальные тесты для регистрации пользователя, мы начнем добиваться их прохождения, для начала создав форму для регистрации пользователей. Мы можем сделать это в Rails с помощью вспомогательного метода form_for который принимает объект Active Record и конструирует форму используя атрибуты объекта. Результат представлен в Листинге 7.17. (Читатели знакомые с Rails 2.x должны обратить внимание что form_for использует "процент-равно" ERb синтаксис для вставки контента; там где Rails 2.x использовали <% form_for ... %>, мы теперь используем <%= form_for ... %>.)

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

<div class="row">
  <div class="span6 offset3">
    <%= form_for(@user) do |f| %>

      <%= f.label :name %>
      <%= f.text_field :name %>

      <%= f.label :email %>
      <%= f.text_field :email %>

      <%= f.label :password %>
      <%= f.password_field :password %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation %>

      <%= f.submit "Create my account", class: "btn btn-large btn-primary" %>
    <% end %>
  </div>
</div>
Листинг 7.17. Форма для регистрации новых пользователей. app/views/users/new.html.erb

Давайте рассмотрим этот код по частям. Наличие ключевого слова do указывает на то что form_for принимает блок с одной переменной, которую мы назвали f (от "form"):

<%= form_for(@user) do |f| %>
  .
  .
  .
<% end %>

Как это зачастую происходит с хелперами Rails, нам нет надобности знать о подробностях реализации, но нам необходимо знать что делает объект f: при вызове с методом соответствующим элементу HTML формы — таким как текстовое поле, радио кнопка, или поле пароля — он возвращает особым образом организованный код для этого элемента для установки атрибута объекта @user. Другими словами,

<%= f.label :name %>
<%= f.text_field :name %>

создает HTML необходимый для создания элемента маркированного текстового поля для назначения атрибута name модели User. (Мы взглянем на сам HTML в Разделе 7.2.3.)

Для того чтобы увидеть это в действии нам необходимо посмотреть на HTML производимый этой формой, но здесь мы сталкиваемся с проблемой: страница в настоящее время сломана, поскольку мы не установили переменную @user — как все неопределенные переменные экземпляра (Раздел 4.4.5), @user в настоящее время является nil. Соответственно, если вы запустите ваш набор тестов в этой точке, вы увидите что тесты для содержимого страницы регистрации из Листинга 7.6 сейчас не проходят:

$ bundle exec rspec spec/requests/user_pages_spec.rb -e "signup page"

(Флаг -e в данном случае позволяет запустить только спеки, чье описание совпадает с "signup page". В частности, обратите внимание что это не подстрока "signup", что привело бы к запуску всех тестов в Листинге 7.16.) Для того чтобы вновь сделать эти тесты проходящими и для того чтобы отрендерить нашу форму, мы должны определить переменную @user в действии контроллера, соответствующем new.html.erb, т.е., в действии new контроллера Users. Хелпер form_for ожидает что @user будет объектом User и поскольку мы создаем нового пользователя, мы просто используем User.new, как это видно в Листинге 7.18.

class UsersController < ApplicationController
  .
  .
  .
  def new
    @user = User.new
  end
end
Листинг 7.18. Добавление переменной @user в действие new. app/controllers/users_controller.rb

После определения переменной @user, тесты для страницы регистрации должны пройти:

$ bundle exec rspec spec/requests/user_pages_spec.rb -e "signup page"

В этой точке у нас должна получиться форма (со стилями из Листинга 7.19) выглядящая как на рис. 7.12. Обратите внимание на повторное использование примеси box_sizing из Листинга 7.2.

.
.
.

/* forms */

input, textarea, select, .uneditable-input {
  border: 1px solid #bbb;
  width: 100%;
  margin-bottom: 15px;
  @include box_sizing;
}

input {
  height: auto !important;
}
Листинг 7.19. CSS для формы регистрации. app/assets/stylesheets/custom.css.scss
Форма регистрации новых пользователей /signup.

Рис. 7.12. Форма регистрации новых пользователей /signup.

HTML формы

Как нам подсказывает Рис. 7.12, теперь страница регистрации рендерится как следует, что указывает на то, что код form_for в Листинге 7.17 производит валидный HTML. Если вы взглянете на HTML для сгенерированной формы (используя Firebug или функцию "просмотр исходного кода страницы" вашего браузера), вы должны увидеть разметку как в Листинге 7.20. Хотя большая часть деталей несущественна для наших целей, давайте улучим минутку и ознакомимся с наиболее важными частями ее структуры.

<form accept-charset="UTF-8" action="/users" class="new_user"
      id="new_user" method="post">

  <label for="user_name">Name</label>
  <input id="user_name" name="user[name]" type="text" />

  <label for="user_email">Email</label>
  <input id="user_email" name="user[email]" type="text" />

  <label for="user_password">Password</label>
  <input id="user_password" name="user[password]"
         type="password" />

  <label for="user_password_confirmation">Confirmation</label>
  <input id="user_password_confirmation"
         name="user[password_confirmation]" type="password" />

  <input class="btn btn-large btn-primary" name="commit" type="submit"
         value="Create my account" />
</form>
Листинг 7.20. HTML для формы на Рис. 7.12.

(Здесь я опустил HTML связанный с authenticity token, который Rails автоматически включают для предотвращения определенного вида атак, назывемого подделка межсайтовых запросов (CSRF). См введение в Rails authenticity token на Stack Overflow если вас интересуют подробности того как это работает и почему это так важно.)

Мы начнем с внутренней структуры документа. Сравнивая Листинг 7.17 с Листингом 7.20, мы видим что Embedded Ruby

<%= f.label :name %>
<%= f.text_field :name %>

производит HTML

<label for="user_name">Name</label>
<input id="user_name" name="user[name]" type="text" />

и

<%= f.label :password %>
<%= f.password_field :password %>

производит HTML

<label for="user_password">Password</label><br />
<input id="user_password" name="user[password]" type="password" />

Как видно из рис. 7.13, текстовые поля (type="text") просто отображают их содержимое, в то время как поля паролей (type="password") скрывают вводимое в целях безопасности, как это показано на рис. 7.13.

Заполненная форма с text и password полями.

Рис. 7.13. Заполненная форма с text и password полями.

Как мы увидим в Разделе 7.4, ключом к созданию пользователя является специальный name атрибут в каждом input:

<input id="user_name" name="user[name]" - - - />
.
.
.
<input id="user_password" name="user[password]" - - - />

Эти значения name позволяют Rails сконструировать хэш инициализации (через переменную paramsparams) для создания пользователей с использованием значений введеных пользователем, как мы это увидим в Разделе 7.3.

Второй важный элемент это сам тег form. Rails создает тег formform используя объект @user: поскольку каждый объект в Ruby знает свой класс (Раздел 4.4.1), Rails определяет что @user принадлежит к классу User; кроме того, поскольку @user это новый пользователь, Rails знает что необходимо построить форму с post методом, который является правильным глаголом для создания нового объекта (Блок 3.3):

<form action="/users" class="new_user" id="new_user" method="post">

Здесь атрибуты class и id, по большому счету, не имеют особого значения; более важными являются action="/users" и method="post". Совместно они формируют инструкцию для отправки HTTP запроса POST на URL /users. В следующих двух разделах мы увидим к чему это приводит.

Провальная регистрация

Мы кратко рассмотрели HTML формы показанной на рис. 7.12 (Листинге 7.20), теперь мы копнем эту тему чуть глубже. Так как HTML формы лучше всего понимается в контексте сбоя регистрации, в этом разделе мы создадим регистрационную форму которая принимает невалидные данные и вновь рендерит страницу регистрации со списком ошибок, как это показано на наброске рис. 7.14.

Набросок страницы неудавшейся регистрации.

Рис. 7.14. Набросок страницы неудавшейся регистрации.

Рабочая форма

Вспомните из Раздела 7.1.2 что добавление resources :users в файл routes.rb (Листинг 7.3) автоматически обеспечивает наше Rails приложение возможностью отвечать на RESTful URL из Таблицы 7.1. В частности, это приводит к тому, что POST запрос к /users обрабатывается действием create. Наша стратегия для действия create зaключается в использовании отправки формы для создания объекта нового пользователя с помощью User.new, попытке (провальной) сохранить этого пользователя и последующем рендеринге страницы регистрации для возможной повторной отправки формы. Давайте начнем с того что еще раз взглянем на код для формы регистрации:

<form action="/users" class="new_user" id="new_user" method="post">

Как было отмечено в Разделе 7.2.3, этот HTML выдает POST запрос к /users URL.

Нашим первым шагом на пути к зеленым тестам для невалидных данных из Листинга 7.16 будет добавление кода из Листинге 7.21. Этот листинг включает второе использование метода render который мы впервые видели в контексте партиалов (Раздел 5.1.3); как вы можете видеть, render также работает и в действиях контроллера. Обратите внимание что мы воспользовались этой возможностью чтобы представить ветвящуюся структуру if-else, которая позволяет нам обрабатывать случаи сбоя и успеха раздельно, в зависимости от значения @user.save, которое (как мы видели в Разделе 6.1.3) может быть либо true либо false в зависимости от успешности сохранения.

class UsersController < ApplicationController
  .
  .
  .
  def create
    @user = User.new(params[:user])    # Not the final implementation!
    if @user.save
      # Handle a successful save.
    else
      render 'new'
    end
  end
end
Листинг 7.21. Действие create которое может обрабатывать провальную регистрацию (но не успешную). app/controllers/users_controller.rb

Обратите внимание на комментарий: это не конечная реализация, но этого достаточно для начала. Мы закончим реализацию в Разделе 7.3.2.

Лучший способ понять как работает код в Листинге 7.21 это отправить форму с какими-нибудь невалидными регистрационными данными; результат представлен на рис. 7.15 и полная отладочная информация (с увеличенным размером шрифта) представлена на рис. 7.16.

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

Рис. 7.15. Сбой регистрации.
Отладочная информация сбоя регистрации.

Рис. 7.16. Отладочная информация сбоя регистрации.

Чтобы получить более четкую картину того как Rails обрабатывает отправку регистрационных данных, давайте поближе познакомимся с user частью хэша параметров из отладочной информации ( рис. 7.16):

"user" => { "name" => "Foo Bar",
            "email" => "foo@invalid",
            "password" => "[FILTERED]",
            "password_confirmation" => "[FILTERED]"
          }

Этот хэш передается в контроллер Users в качестве params и мы видели, начиная с Раздела 7.1.2, что хэш params содержит информацию о каждом запросе. В случае URL типа /users/1, значение params[:id] это id соответствующего пользователя (1 в этом примере). В случае отправки регистрационной формы, params вместо этого содержит хэш хэшей, конструкцию, которую мы впервые видели в Разделе 4.3.3, который представил стратегически названную params переменную в консольной сессии. Эта отладочная информация показывает, что предоставление (отправка) формы дает в результате user хэш с атрибутами, соответствующими предоставленным значениям, где ключи происходят от name атрибутов тегов input которые мы видели в Листинге 7.17; например, значение

<input id="user_email" name="user[email]" type="text" />

с именем "user[email]" это именно email атрибут хэша user.

Хотя хэш-ключи показаны в отладочном выводе в виде строк, в контроллер Users они передаются в виде символов, так что params[:user] на самом деле является хэшем атрибутов пользователя, именно тех атрибутов, что необходимы в качестве аргумента для User.new, как мы впервые видели в Разделе 4.4.5 и как представлено в Листинге 7.21. Это означает, что строка

@user = User.new(params[:user])

практически эквивалентна

@user = User.new(name: "Foo Bar", email: "foo@invalid",
                 password: "foo", password_confirmation: "bar")

В предыдущих версиях Rails использование

@user = User.new(params[:user])

фактически работало, но было по умолчанию небезопасным, требовало особой тщательности при осуществлении довольно глючной процедуры имеющей своей целью защитить базу данных приложения от зловредных пользователей. В Rails 4.0 этот код вызывает ошибку (как показано на рис. 7.15 и рис. 7.16 выше), что означает что он безопасен по умолчанию. Мы можем еще раз убедиться в этом проверив что соответствующие тесты не проходят:

$ bundle exec rspec spec/requests/user_pages_spec.rb \
< -e "signup with invalid information"
Вадим Обозин
Вадим Обозин

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