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