Теперь, когда у нас есть рабочая модель User, пришло время добавить возможность, без которой некоторые сайты просто жить не могут: позволить пользователям регистрироваться на сайте. Мы будем использовать HTML форму для предоставления пользователями регистрационной информации нашему приложению в Разделе 7.2, которая затем будет использована для создания нового пользователя и сохранения его атрибутов в базе данных в Разделе 7.4. В конце процесса регистрации важно отобразить страницу профиля с информацией о новом созданном пользователе, так что мы начнем с создания страницы для демонстрации пользователей, которая будет для нас первым шагом на пути реализации REST архитектуры для пользователей (Раздел 2.2.2). Как обычно, мы будем писать тесты по мере разработки, расширяя наши познания о применении RSpec и Capybara для написания кратких и выразительных интеграционных тестов.
Для того чтобы создать страницу профиля пользователя, нам необходимо иметь пользователя в базе данных, что представлеяет проблему яйца и курицы: как сайт может иметь пользователя до появления рабочей страницы регистрации? К счастью, эта проблема уже решена: в Разделе 6.3.5 мы создавали запись User вручную с помощью Rails консоли. Если вы пропустили этот раздел, вам следует вернуться и закончить его перед продолжением.
Если вы используете управление версиями, создайте тему ветки, как обычно:
$ git checkout master $ git checkout -b sign-up
В этом разделе мы сделаем первый шаг на пути к конечной странице пользователя, создав страницу для отображения имени и фотографии пользователя, на что указывает набросок страницы на рис 7.1.
(рис 7.1) Набросок страницы профиля пользователя которая будет сделана в этом разделе
(рис 7.2) Набросок наших лучших ожиданий от конечной страницы профиля.
Профили в этом разделе будут первыми по-настоящему динамическими страницами в нашем приложении. Хотя представление будет существовать как одна страница кода, каждый профиль будет кастомизирован с использованием информации получаемой из базы данных сайта. В качестве подготовки к добавлению динамических страниц в наш пример приложения, сейчас хорошее время, чтобы добавить некоторую отладочную информацию к шаблону нашего сайта (Листинг 7.1). Это отображает некоторую полезную информацию о каждой странице с помощью встроенного debug метода и params переменной (о которой мы узнаем больше в Разделе 7.1.2).
<!DOCTYPE html>
<html>
.
.
.
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
<%= render 'layouts/footer' %>
<%= debug(params) if Rails.env.development? %>
</div>
</body>
</html>
Для того чтобы вывод отладочной информации приятно выглядел, мы добавим несколько правил к кастомной таблице стилей созданной в Главе 5, как это показано в Листинге 7.2.
@import "bootstrap";
/* mixins, variables, etc. */
$grayMediumLight: #eaeaea;
@mixin box_sizing {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.
.
.
/* miscellaneous */
.debug_dump {
clear: both;
float: left;
width: 100%;
margin-top: 45px;
@include box_sizing;
}
Этот код вводит новую для нас возможность Sass - примесь, в данном случае названную box_sizing. Примесь позволяет группировать CSS правила с тем чтобы они могли использоваться для нескольких элементов, конвертируя
.debug_dump {
.
.
.
@include box_sizing;
}
в
.debug_dump {
.
.
.
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
Мы еще раз применим эту примесь в Разделе 7.2.2. Результат ее применения в контексте блока с отладочной информацией показан на рис 7.3.
(рис 7.3) Home страница примера приложения (/) с отладочной информацией.
Вывод отладочной информации на рис 7.3 дает потенциально полезную информацию об отображаемой в настоящий момент странице:
--- controller: static_pages action: home
Это YAMLdebug информация показана как YAML (a recursive acronym (или рекурсивный акроним)) обозначающий "YAML Ain’t Markup Language"), который является дружественным форматом данных, разработанным так, чтобы быть удобочитаемым и для машин и для людей.params, который по сути является хэшем и в данном случае указывает на контроллер и действие страницы. Мы увидим еще один пример в Разделе 7.1.2
Поскольку мы не хотим показывать отладочную информацию пользователям развернутого приложения, Листинге 7.1 использует
if Rails.env.development?
для того чтобы показывать отладочную информацию только в среде разработки, которая является одной из трех сред, определенных по умолчанию в Rails (Блок 7.1).Rails.env.development? является true только в среде разработки, так что Embedded Ruby
<%= debug(params) if Rails.env.development? %>
не будет вставлен в развернутое приложение или тесты. (Вставка отладочной информации в тесты возможно не навредит, но скорее всего и не сделает ничего хорошего, так что лучше ограничиться ее отображением только в среде разработки.)
___________________________________________
Блок 7.1.Окружения Rails
Rails поставляется с тремя окружениями: test, development и production. Дефолтным окружением для Rails консоли является development:
$ rails console Loading development environment >> Rails.env => "development" >> Rails.env.development? => true >> Rails.env.test? => false
Как вы можете видеть, Rails обеспечивает объект Rails атрибутом env и связанными с ним булевыми методами окружения, таким образом, например, Rails.env.test? возвращает true в тестовом окружении и false во всех остальных.
Если вам когда либо понадобится запустить консоль в окружении, отличном от дефолтного (для отладки теста, например), вы можете предать окружение в качестве параметра к console скрипту:
$ rails console test Loading test environment >> Rails.env => "test" >> Rails.env.test? => true
Как и в случае с консолью, development является дефолтным окружением для локального Rails сервера, но вы также можете запустить его и в другом окружении:
$ rails server --environment production
Если вы попробуете посмотреть на свое приложение запущенное в production, оно не будет работать без production базы данных, которую мы можем создать запустив rake db:migrate в production:
$ bundle exec rake db:migrate RAILS_ENV=production
(Я считаю запутывающим, когда консоль, сервер и команды миграций устанавливают не дефолтные окружения тремя взаимно исключающими способами, поэтому я дал себе труд показать все три.)
Кстати, если вы развернули свое приложение на Heroku, вы можете посмотреть его окружение с помощью команды heroku, которая предоставляет свою собственную (удаленную) консоль:
$ heroku run console Ruby console for yourapp.herokuapp.com >> Rails.env => "production" >> Rails.env.production? => true
Вполне естественно, что, поскольку Heroku является платформой для production сайтов, он запускает каждое приложение в production окружении.
___________________________________________
В конце Главы 6 мы создали нового пользователя в базе данных. Как показано в Разделе 6.3.5, этот пользователь имеет id 1 и наша цель теперь заключается в создании страницы для отображения информации этого пользователя. Мы будем следовать соглашениям REST архитектуры, предпочитаемой Rails в Rails приложениях (Блок 2.2), что означает представление данных в качестве ресурсов которые могут быть созданы, показаны, обновлены, или уничтожены — четыре действия, соответствующие четырем фундаментальным операциям POST, GET, PATCH и DELETE определенным стандартом HTTP (Блок 3.3).
Следуя принципам REST, на ресурсы обычно ссылаются, используя имя ресурса и уникальный идентификатор. Что это означает в контексте пользователей — о которых мы теперь думаем, как о ресурсе Users—то, что мы должны показать пользователя с id 1 выдав GET запрос к URL /users/1. Здесь show действие неявно в типе запроса —когда Rails’ функции REST активированы, GET запросы автоматически обрабатываются show действием.
Мы видели в Разделе 2.2.1 что страница для пользователя с id 1 имеет URL /users/1. К сожалению, сейчас посещение этой страницы лишь выдает ошибку (рис 7.4).
(рис 7.4) Страница ошибки для /users/1.
Мы можем заполучить REST-style Users URL на работу, добавив одну-единственную строку в наш файл маршрутов (config/routes.rb):
resources :users
Результат представлен в Листинге 7.3.
SampleApp::Application.routes.draw do resources :users root 'static_pages#home' match '/signup', to: 'users#new', via: 'get' . . . end
Вы возможно заметили, что Листинг 7.3 удаляет строку
get "users/new"
последний раз замеченную в Листинге 5.35. Это связано с тем, что
| HTTP запрос | URL | Действие | Именованный маршрут | Назначение |
|---|---|---|---|---|
| GET | /users | index |
users_path |
страница показывающая список всех пользователей |
| GET | /users/1 | show |
user_path(user) |
страница показывающая пользователя |
| GET | /users/new | new |
new_user_path |
страница для создания нового пользователя (регистрация) |
| POST | /users | create |
users_path |
создание нового пользователя |
| GET | /users/1/edit | edit |
edit_user_path(user) |
страница для редактирования пользователя с id 1 |
| PATCH | /users/1 | update |
user_path(user) |
обновление пользователя |
| DELETE | /users/1 | destroy |
user_path(user) |
удаление пользователя |
С кодом в Листинге 7.3, маршруты работают, но страница по-прежнему не существует (рис 7.3" type="example">Листинге 7.3, маршруты работают, но страница по-прежнему не существует ((рис 7.5) URL /users/1 с маршрутом, но без страницы.
Мы будем использовать стандартное Rails размещение для представления показывающего пользователя, которым является app/views/users/show.html.erb. В отличие от представления new.html.erb, которое мы создали с помощью генератора в Листинге 5.31, файл show.html.erb в данный момент не существует, так что нам необходимо создать его вручную и заполнить его содержимым Листинга 7.4.
<%= @user.name %>, <%= @user.email %>
Это представление использует Embedded Ruby для отображения имени и адреса электронной почты пользователя, предполагая наличие переменной экземпляра @user. Конечно же, в итоге реальная страница пользователя будет выглядеть совершенно иначе и не будет публично демонстрировать адрес электронной почты.
Для того чтобы заставить представление user show работать, нам необходимо определить переменную @user в соответствующем show действии контроллера Users. Как вы и ожидали, мы используем метод find на модели User (Раздел 6.1.4) для получения пользователя из базы данных, как это показано в Листинге 7.5.
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
end
end
Здесь мы использовали params для получения id пользователя. Когда мы сделаем соответствующий запрос в контроллер Users, params[:id] будет пользовательским id 1, так что эффект тот же что и с методом find
User.find(1)
который мы видели в Разделе 6.1.4. (Технически, params[:id] является строкой "1", но find достаточно умен, чтобы преобразовать это в целое число.)
С представлением и определенным действием, URL /users/1 работает замечательно (рис 7.6). Обратите внимание что отладочная информация в рис 7.6 подтверждает значение params[:id]:
--- action: show controller: users id: '1'
Вот почему код
User.find(params[:id])
в Листинге 7.5 находит пользователя с id 1.
(рис 7.6) Страница показывающая пользователя на /users/1 после добавления ресурса Users.
Теперь, когда у нас есть минимально рабочий профиль, пришло время поработать над версией из наброска на рис 7.1. Как и в случае с созданием статических страниц (Глава 3) и моделью User (Глава 6), мы сделаем это используя разработку через тестирование.
Вспомните из Раздела 5.4.2 что для страниц связанных с ресурсом Users мы приняли решение использовать интеграционные тесты. В случае со страницей регистрации, наш тест вначале посещает signup_path а затем проверяет правильность h1 и title тегов, как это показано в Листинге 5.34 и повторено в Листинге 7.6. (Обратите внимание, что мы опустили full_title хелпер из Раздела 5.3.4 поскольку полный заголовок уже адекватно протестирован.)
require 'spec_helper'
describe "User pages" do
subject { page }
describe "signup page" do
before { visit signup_path }
it { should have_content('Sign up') }
it { should have_title(full_title('Sign up')) }
end
end
Для того чтобы протестировать страницу показывающую пользователя, нам необходим объект модели User для того чтобы коду в действии show (Листинг 7.5) было что искать:
describe "profile page" do
# Replace with code to make a user variable
before { visit user_path(user) }
it { should have_content(user.name) }
it { should have_title(user.name) }
end
где нам нужно заполнить комментарий соответствующим кодом. Здесь используется именованный маршрут ) для генерации пути к странице показывающей данного пользователя. Затем мы тестируем что и страница и тайтл содержат имя пользователя.
Для того чтобы создать необходимый объект модели User, мы можем использовать Active Record для создания пользователя посредством User.create, но практика показывает, что пользовательские фабрики являются более удобным способом определения и вставки в базу данных объектов user. Мы будем использовать фабрики генерируемые Factory Girl - Ruby гемом который сделан хорошими людьми из thoughtbot. Как и RSpec, Factory Girl определяет предметно-ориентированный язык в Ruby, в данном случае предназначенный для определения объектов Active Record. Синтаксис прост, опирается на Ruby блоки и кастомные методы для определения атрибутов описываемого объекта. Для случаев вроде тех что мы увидим в этой главе, преимущество над Active Record может быть неочевидным, но мы будем использовать более продвинутые техники фабрик в последующих главах. Например, в Разделе 9.3.3 нам понадобится создать набор пользователей с уникальными адресами электронной почты и фабрики позволят нам проделать это с легкостью.
Как и с другими Ruby гемами, мы можем установить Factory Girl добавив строку в Gemfile используемый Bundler-ом (Листинг 7.7). (Поскольку Factory Girl нужна только в тестах, мы поместили ее в группу :test.)
source 'https://rubygems.org' . . . group :test do . . . gem 'factory_girl_rails', '4.2.1' end . . .
Затем устанавливаем как обычно:
$ bundle install
Мы поместим все наши фабрики Factory Girl в файл spec/factories.rb, который автоматически будет загружен RSpec-ом. Код необходимый для создания фабрики User представлен в Листинге 7.8.
FactoryGirl.define do
factory :user do
name "Michael Hartl"
email "michael@example.com"
password "foobar"
password_confirmation "foobar"
end
end
Передавая символ :user команде factory, мы говорим Factory Girl что последующее определение предназначено для объекта модели User.
С определением в Листинге 7.8, мы можем создать фабрику User в тестах используя команду let (Блок 6.3) и метода FactoryGirl поддерживаемого Factory Girl:
let(:user) { FactoryGirl.create(:user) }
Конечный результат представлен в Листинге 7.9.
require 'spec_helper'
describe "User pages" do
subject { page }
describe "profile page" do
let(:user) { FactoryGirl.create(:user) }
before { visit user_path(user) }
it { should have_content(user.name) }
it { should have_title(user.name) }
end
describe "signup page" do
before { visit signup_path }
it { should have_content('Sign up') }
it { should have_title(full_title('Sign up')) }
end
end
В этой точке вам необходимо проверить что набор тестов в красном:
$ bundle exec rspec spec/
Мы можем получить зеленые тесты с кодом из Листинга 7.10.
<% provide(:title, @user.name) %> <h1><%= @user.name %></h1>
Повторный запуск тестов должен подтвердить что тесты из Листинга 7.9 теперь проходят:
$ bundle exec rspec spec/
Вы быстро заметите что тесты с Factory Girl - медленные. И это происходит не по вине Factory Girl и фактически это фича, а не баг. Проблема связана с тем, что алгоритм BCrypt используемый в Разделе 6.3.1 для создания хэша безопасного пароля является медленным по своей природе: именно низкая скорость BCrypt отчасти делает его таким сложным для взлома. К сожалению, это означает что создание пользователей может утопить набор тестов; к счастью, есть простой способ исправить это. Библиотека bcrypt-ruby использует фактор стоимости для управления вычислительной сложностью создаваемого безопасного хэша. Дефолтное значение призвано обеспечить безопасность, а не скорость и для рабочих приложений это здорово, но в тестах наши потребности противоположны: мы хотим быстрые тесты и нас совершенно не тревожит безопасность хэшей паролей тестовых пользователей. Решением будет добавление строки которая переопределит фактор стоимости от дефолтного безопасного значения к минимально возможному в файл конфигурации тестового окру жения config/environments/test.rb, как это показано в Листинге 7.11. Даже для небольшого набора тестов выигрыш в скорости от этого шага может быть весьма значительным и я настоятельно рекомендую включить Листинг 7.11 в ваш test.rb конфиг.
SampleApp::Application.configure do . . . # Speed up tests by lowering bcrypt's cost function. ActiveModel::SecurePassword.min_cost = true end
Определив базовую страницу пользователя в предыдущем разделе, теперь мы немного улучшим ее, добавив изображение пользователя и начальную реализацию боковой панели пользователя. При создании страницы мы сфокусируемся на ее внешнем виде, не особо заботясь о ее структуре, это означает, что (по крайней мере пока) мы не будем писать тесты. Когда мы подойдем к аспектам представления, более подверженным ошибкам, таким как пагинация (Раздел 9.3.3), мы продолжим разработку через тестирование.
Мы начнем с добавления "глобально распознаваемого аватара" (также известного как Граватар) к профилю пользователя.
Мы планируем определить вспомогательную функцию gravatar_for которая будет возвращать изображение Граватар для данного пользователя, как это показано в Листинге 7.12.
<% provide(:title, @user.name) %> <h1> <%= gravatar_for @user %> <%= @user.name %> </h1>
В этой точке вы можете проверить что набор тестов не проходит:
$ bundle exec rspec spec/
Из-за того что метод gravatar_for неопределен, страница показывающая пользователя в настоящий момент не работает. (Отлов подобных ошибок это, возможно самый полезный аспект тестов представлений. Вот почему так важно наличие хоть каких-то тестов представлений, даже минималистичных.)
По умолчанию, методы определеннные в любом файле хелпера являются автоматически доступным в любом представлении, но для удобства мы поместим метод gravatar_for в файл для хелперов, связанных с контроллером Users. Как отмечено на главной странице Граватар, URL Граватара основывается на MD5 хэше адреса электронной почты пользователя. В Ruby, алгоритм MD5 хэширования реализутся с помощью метода hexdigest, который является частью библиотеки Digest:
>> email = "MHARTL@example.COM". >> Digest::MD5::hexdigest(email.downcase) => "1fda4469bcbec3badf5418269ffc5968"
Поскольку адреса электронной почты нечувствительны к регистру (Раздел 6.2.4), в отличие от MD5 хэшей, мы используем метод downcase для того чтобы быть уверенными в том, что аргумент передаваемый в hexdigest находится в нижнем регистре. Получившийся хелпер gravatar_for представлен в Листинге 7.13.
module UsersHelper
# Returns the Gravatar (http://gravatar.com/) for the given user.
def gravatar_for(user)
gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}"
image_tag(gravatar_url, alt: user.name, class: "gravatar")
end
end
Код в Листинге 7.13 возвращает тег img для Граватара с классом "gravatar" и альтернативным текстом эквивалентным имени пользователя (что особенно удобно для браузеров используемых слабовидящими людьми которые работают с помощью считывателей экрана). Вы можете проверить что теперь набор тестов проходит:
$ bundle exec rspec spec/
Страница профиля представленная на рис 7.7, показывающая дефолтное изображение Граватара, выглядит подобным образом т.к. user@example.com это невалидный email адрес (домен example.com зарезервирован для примеров (examples).
(рис 7.7) Страница профиля пользователя /users/1 с дефолтным Граватаром.
Для того чтобы отобразить кастомный Граватар в нашем приложении, мы будем использовать update_attributes (Раздел 6.1.5) для обновления пользователя в базе данных:
$ rails console >> user = User.first >> user.update_attributes(name: "Example User", ?> email: "example@railstutorial.org", ?> password: "foobar", ?> password_confirmation: "foobar") => true
Здесь мы назначили пользователю адрес электронной почты .
(рис 7.8) Страница показывающая пользователя с кастомным Граватаром.
Последний элемент, необходимый для завершения наброска из рис 7.1 это начальная версия пользовательской боковой панели. Мы реализуем ее с помощью тега aside, который используется для элементов (таких как сайдбары), которые дополняют страницу, но также могут быть использованы отдельно. Мы включаем row и span4 классы, которые являются частью Bootstrap. Код для измененной страницы показывающей пользователя представлен в Листинге 7.14.
<% provide(:title, @user.name) %>
<div class="row">
<aside class="span4">
<section>
<h1>
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
</section>
</aside>
</div>
Имея соответствующие HTML элементы и CSS классы, мы можем отстилить страницу профиля (включая боковую панель и Gravatar) с SCSS показанным в Листинге 7.15. (Обратите внимание на наследование CSS правил, которое работает только благодаря препроцессору Sass, используемому файлопроводом.) Получившаяся страница показана на рис 7.15" type="example">Листинге 7.15. (Обратите внимание на наследование CSS правил, которое работает только благодаря препроцессору Sass, используемому файлопроводом.) Получившаяся страница показана на .
.
.
/* sidebar */
aside {
section {
padding: 10px 0;
border-top: 1px solid $grayLighter;
:first-child {
border: 0;
padding-top: 0;
}
span {
display: block;
margin-bottom: 3px;
line-height: 1;
}
h1 {
font-size: 1.4em;
text-align: left;
letter-spacing: -1px;
margin-bottom: 3px;
margin-top: 0px;
}
}
}
.gravatar {
float: left;
margin-right: 10px;
}
Теперь, когда у нас есть рабочая (хоть и незавершенная) страница профиля пользователя, мы готовы приступить к созданию формы для регистрации на нашем сайте. Мы видели на рис 5.9 (дублированном на рис 7.10) что страница регистрации в настоящий момент пуста и совершенно бесполезна для регистрации новых пользователей. Целью этого раздела является разработка формы регистрации из рис 7.11. Поскольку мы говорим о создании пользователей через веб-интерфейс, давайте удалим пользователя созданного через консоль в Разделе 6.3.5. Простейшим способом сделать это является очистка базы данных с помощью Rake-задачи После очистки базы данных на некоторых системах также необходимо заново подготовить тестовую базу данных: Наконец, на некоторых системах вам может потребоваться перезагрузка сервера для того чтобы изменения вступили в силу. До появления мощных веб-фреймворков с полным набором тестировочных средств, тестирование часто доставляло много хлопот. Например, для тестирования страницы регистрации вручную, нам бы пришлось посещать страницу в браузере, затем заполнять форму различными валидными и невалидными данными и проверять что в каждом случае поведение приложения соответствует ожидаемому. Кроме того, нам бы пришлось проделывать это каждый раз при изменении приложения. С помощью RSpec и Capybara мы сможем написать выразительные тесты, которые автоматизируют задачи, которые раньше делались вручную. Мы уже видели как Capybara поддерживает интуитивно понятный синтаксис веб-навигации. До сих пор мы в основном использовали и кликанья по кнопке. Синтаксис выглядит примерно так: Сейчас нашей целью является написание тестов для правильного поведения при предоставлении невалидной и валидной регистрационной информации. Поскольку эти тесты довольно продвинутые, мы будем писать их постепенно. Если вам интересно посмотреть как они работают (включая файл в который они должны быть помещены), вы можете перейти непосредственно к Листингу 7.16. Наша первая задача это тестирование провальной отправки регистрационной формы, и мы можем симулировать отправку невалидных данных посетив страницу и кликнув по кнопке с помощью Это эквивалентно посещению страницы регистрации и отправке формы незаполненной регистрационной информацией. Аналогично, для симуляции отправки валидных данных, мы заполняем форму валидными данными с помощью Эти тесты проверяют что поведение приложения после клика по кнопке "Create my account" соответствует нашим ожиданиям: создается новый пользователь если информация валидна и не создается новый пользователь в случае когда она невалидна. Для того чтобы сделать это мы проверяем количество пользователей и наши тесты будут использовать метод Здесь При отправке невалидных данных, мы ожидаем что количество пользователей не будет изменено; при отправке валидных данных мы ожидаем что оно изменится на 1. Мы можем выразить это в RSpec скомбинировав метод Обратите внимание: вычисляет до и после выполнения В данном случае, мы хотим чтобы данный код не изменял количества, что мы можем выразить с помощью метода на одну строку которая читается как естественный язык и является намного более компактной (eq это RSpec метод для тестирования на равенство). Случай с валидными данными аналогичен, но вместо проверки того, что количество пользователей не изменилось, мы проверяем что клик по кнопке изменяет их количество на 1: Здесь используется метод Комбинирование двух случаев с соответствующими блоками Мы будем добавлять тесты по мере надобности в последующих разделах, но базовые тесты в Листинге 7.16 уже покрывают внушительный объем функционала. Для того чтобы получить их прохождение, мы должны создать страницу регистрации с необходимым минимумом правильных элементов, предопределить направление пользователя в правильное место после отправки формы и создание нового пользователя в базе данных только в случае если полученная информация валидна. Конечно же, в этой точке тесты должны быть провальными:
(рис 7.9) Страница показывающая пользователя /users/1 с боковой панелью и CSS.
Форма регистрации
(рис 7.10) Текущее состояние страницы регистрации /signup.
(рис 7.11) Набросок страницы регистрации пользователей.
db:reset:$ bundle exec rake db:reset
$ bundle exec rake test:prepare
Тесты для регистрации пользователя
visit signup_path
fill_in "Name", with: "Example User"
.
.
.
click_button "Create my account"
click_button:visit signup_path
click_button "Create my account"
fill_in: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 "Create my account"
count, доступный для каждого класса Active Record, включая User:$ rails console
>> User.count
=> 0
User.count является 0 поскольку мы очистили базу данных в начале этого раздела.expect с методом to или методом not_to. Мы начнем со случая с невалидными данными поскольку он проще; мы посетим страницу регистрации и кликнем по кнопке, и мы ожидаем что это не изменит количества пользователей:visit signup_path
expect { click_button "Create my account" }.not_to change(User, :count)
expect обворачивает click_button в блок (Раздел 4.3.2). Что необходимо для работы метода change, который принимает в качестве аргумента объект и символ, а затем вычисляет результат вызова этого символа в качестве метода на объекте до и после блока. Другими словами, кодexpect { click_button "Create my account" }.not_to change(User, :count)
User.count
click_button "Create my account"
not_to. В результате, закрыв клик по кнопке в блоке, мы можем заменитьinitial = User.count
click_button "Create my account"
final = User.count
expect(initial).to eq final
expect { click_button "Create my account" }.not_to change(User, :count)
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"
expect do
click_button "Create my account"
end.to change(User, :count).by(1)
to поскольку мы ожидаем что клик по кнопке (вкупе с валидными данными) изменит количество пользователей на единицу.describe и выталкивание общего кода в блоки before, приводит нас к хорошим базовым тестам для регистрации пользователей, как это показано в Листинге 7.16. Здесь мы вынесли общий текст для кнопки регистрации в переменную submit, которую мы определили с помощью метода let.require 'spec_helper'
describe "User pages" do
subject { page }
.
.
.
describe "signup page" do
before { visit signup_path }
let(:submit) { "Create my account" }
describe "with invalid information" do
it "should not create a user" do
expect { click_button submit }.not_to change(User, :count)
end
end
describe "with valid information" do
before do
fill_in "Name", with: "Example User"
fill_in "Email", with: "user@example.com"
fill_in "Password", with: "foobar"
fill_in "Confirmation", with: "foobar"
end
it "should create a user" do
expect { click_button submit }.to change(User, :count).by(1)
end
end
end
end
$ bundle exec rspec spec/
Теперь, когда у нас есть хорошие провальные тесты для регистрации пользователя, мы начнем добиваться их прохождения, для начала создав форму для регистрации пользователей. Мы можем сделать это в 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>
Давайте рассмотрим этот код по частям. Наличие ключевого слова 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
После определения переменной @user, тесты для страницы регистрации должны пройти:
$ bundle exec rspec spec/requests/user_pages_spec.rb -e "signup page"
В этой точке у нас должна получиться форма (со стилями из Листинга 7.19) выглядящая как на рис 7.19" type="example">Листинга 7.19) выглядящая как на 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.12) Форма регистрации новых пользователей /signup.
Как нам подсказывает Рис. 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>
(Здесь я опустил 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, текстовые поля (.
(рис 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.20" type="example">Листинге 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.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"
В Разделе 4.4.5 мы коротко упомянули об идее массового назначения, которая подразумевает инициализацию Ruby-переменной с хэшем значений:
@user = User.new(params[:user]) # Not the final implementation!
Комментарий включенный в Листинг 7.21 и воспроизведенный выше указывает на то что это не финальная реализация. Причина по которой иниацилизация всего хэша params является очень опасной затеей заключается в том что это приводит к передаче в User.new всех данных отправленных пользователем. В частности, предположим что, в дополнение к текущим атрибутам, модель User включает в себя атрибут admin используемый для идентификации пользователей-администраторов сайта. (Мы реализуем именно такой атрибут в Разделе 9.4.1.) Способом присвоить этому атрибуту значение true является передача значения admin=’1’ как части params[:user], этого легко достичь с помощью такого консольного HTTP клиента как curl. Таким образом, передавая весь хэш params в User.new, мы тем самым позволяем любому пользователю сайта получить права администратора включив admin=’1’ в
веб-запрос.
Для решения этой проблемы предыдущие версии Rails использовали метод attr_accessible на уровне модели, но в Rails 4.0 более предпочтительной является техника с использованием так называемых строгих параметров на уровне контроллера. Это позваляет нам прописать какие именно параметры являются обязательными, а какие разрешенными. К тому же, передача чистого хэша params (как это было показано выше) приведет к ошибке, т.е. теперь Rails приложения невосприимчивы к уязвимостям массового назначиния по умолчанию.
В данном случае мы хотим требовать от хэша params наличие атрибута :user и мы хотим позволить наличие атрибутов name, email, password и password_confirmation (но только их). Мы можем достигнуть этого следующим образом:
params.require(:user).permit(:name, :email, :password, :password_confirmation)
Этот код вернет версию хэша params содержащего только разрешенные атрибуты (при этом будет вызвана ошибка если отсутствует атрибут :user).
Для облегчения использования этих параметров, обычно вводят вспомогательный метод с названием user_params который возвращает соответствующий инициализационных хэш используемый вместо params[:user]:
@user = User.new(user_params)
Поскольку user_params будет использоваться только внутри контроллера Users и нет никакой надобности открывать к нему доступ внешним пользователям через веб, мы сделаем его приватным используя ключевое слово private, как это показано в Листинге 7.22. (Мы более детально обсудим private в Разделе 8.2.1.)
class UsersController < ApplicationController
.
.
.
def create
@user = User.new(user_params)
if @user.save
# Handle a successful save.
else
render 'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
С кодом из Листинге 7.22 тесты отправки невалидных данных должны пройти:
$ bundle exec rspec spec/requests/user_pages_spec.rb \ > -e "signup with invalid information"
В качестве финального шага в реализации провального создания пользователя мы добавим полезные сообщения об ошибках для указания на проблемы которые помешали регистрации. 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.2) содержит массив сообщений об ошибках.
Как и в консольной сессии выше, сбой сохранения в Листинге 7.21 генерирует список сообщений об ошибках, связанных с объектом @user. Для отображения сообщения в браузере, мы рендерим партиал error_messages на странице user new как это показано в Листинге 7.23. (Написание теста на сообщения об ошибках это хорошая идея и мы оставим эти тесты в качестве упражнения; см. Раздел 7.6.) Стоит отметить что этот партиал сообщений об ошибках лишь первая попытка; конечная версия представлена в Разделе 10.3.2.
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<div class="row">
<div class="span6 offset3">
<%= form_for(@user) do |f| %>
<%= render 'shared/error_messages' %>
.
.
.
<% end %>
</div>
</div>
Заметим здесь, что мы render партиал ’shared/error_messages’; что отражает общую конвенцию Rails, которая предписывает размещение частичных шаблонов которые мы планируем использовать во многих контроллерах в специально отведенном каталоге shared/. Это означает, что мы должны создать этот новый каталог вместе с файлом партиала _error_messages.html.erb. Сам партиал представлен в Листинге 7.24.
<% if @user.errors.any? %>
<div id="error_explanation">
<div class="alert alert-error">
The form contains <%= pluralize(@user.errors.count, "error") %>.
</div>
<ul>
<% @user.errors.full_messages.each do |msg| %>
<li>* <%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
Этот частичный шаблон вводит несколько новых Rails и Ruby конструкций, в том числе два метода для объекта ошибок Rails. Первый метод это count, который просто возвращает количество ошибок:
>> user.errors.count => 2
Другой новый метод это any?, который (совместно с empty?) является одним из пары взаимодополняющих методов:
>> user.errors.empty? => false >> user.errors.any? => true
Мы видим здесь что метод empty?, который мы впервые увидели в Разделе 4.2.3 в контексте строк, также работает на объекте ошибок Rails, возваращая true для пустого объекта и false в противном случае. Метод any? это просто противоположность empty?, возвращающая true если существует какой-нибудь элемент и false в противном случае. (Кстати, все эти методы — count, empty? и any? — работают и на массивах Ruby. Мы найдем хорошее применение этому факту в Разделе 10.2.)
Другой новой идеей является текстовый хелпер pluralize в Rails API
>> include ActionView::Helpers::TextHelper >> 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") %>
возвращает "0 errors", "1 error", "2 errors" и т.д., в зависимости от количества ошибок, тем самым позволяя нам избежать грамматически неверных фраз вроде "1 errors".
Обратите внимание: Листинг 7.24 включает CSS . Поскольку сообщения генерируются валидациями модели, они автоматически изменятся, если вы когда-нибудь поменяете свое мнение о, скажем, формате адресов электронной почты или минимальной длине паролей.
.
.
.
/* forms */
.
.
.
#error_explanation {
color: #f00;
ul {
list-style: none;
margin: 0 0 18px 0;
}
}
.field_with_errors {
@extend .control-group;
@extend .error;
}
(рис 7.17) Сбой регистрации с сообщениями об ошибках.
Для того чтобы увидеть результаты нашей работы в этом разделе, мы повторим шаги из теста провальной регистрации Листинга 7.16 посетив страницу регистрации и кликнув по "Sign up" с пустыми полями ввода. Результат показан на рис 7.16" type="example">Листинга 7.16 посетив страницу регистрации и кликнув по "Sign up" с пустыми полями ввода. Результат показан на $ bundle exec rspec spec/requests/user_pages_spec.rb \
> -e "signup with invalid information"
Получив обработку отправки невалидной формы, теперь пришло время для завершения регистрационной формы, на самом деле сохранив нового пользователя (если валидный) в базу данных. Во-первых, мы попробуем сохранить пользователя; если сохранение пройдет успешно, информация пользователя будет записана в базу данных автоматически, а затем мы перенаправим браузер на страницу профиля пользователя (с дружеским приветствием), как это показано на наброске рис 7.19. Если это не удастся, мы просто отступим к сценарию, разработанному в Разделе 7.3. Для того чтобы закончить работу с формой регистрации, нам необходимо заполнить закомментированный раздел в Листинге 7.21 соответствующим поведением. Сейчас тесты для отправки валидной формы должны быть провальными: Это происходит из-за того что дефолтным поведением для Rails-действий является рендеринг соответствующего представления, но здесь нет (и не должно быть) представления соответствующего действию Обратите внимание на то что мы можем опустить С кодом в Листинге 7.26 наша регистрационная форма отлично работает, что вы можете проверить запустив набор тестов: Перед отправкой валидной регистрации в браузер мы собираемся немного отполировать ее, в соответствии с общепринятой в веб приложениях идеей: добавив сообщение, которое временно появляется на следующей странице (в данном случае, приветствуя нашего нового пользователя), а затем исчезает либо при посещении следующей страницы либо при перезагрузке текущей. Rails-способ сделать это состоит в использовании специальной переменной flash с которой мы можем обращаться как с обычным хэшем. Вы можете даже вспомнить консольный пример из Разделе 4.3.3, где мы видели как для перебора хэша использовался стратегически именованный хэш Мы можем организовать отображение содержимого флэш повсеместно на сайте, включив его в шаблон нашего приложения, как это показно в Листинге 7.27. (Этот код представляет из себя особенно уродливую комбинацию HTML и ERb; упражнение в Разделе 7.6 показывает как сделать его более красивым.) Код в Листинге 7.27 организует вставку каждого флэш элемента в произведет такой HTML: (Обратите внимание: ключ Написание теста на правильное флэш сообщение оставлено в качестве упражнения (Раздел 7.6) и мы можем получить прохождение теста назначив Вы можете увидеть результат всей этой работы, зарегистрировав нашего первого пользователя под именем "Rails Tutorial" и с адресом электронной почты "example@railstutorial.org". Получившаяся в результате страница (рис 7.20) показывает дружеское сообщение после успешной регистрации, включая приятный зеленый стиль для класса ). Теперь мы можем проверить нашу базу данных просто для того чтобы еще раз убедиться что новый пользователь действительно создан: Мы разработали модель User и функционал регистрации, пришло время развернуть пример приложения на сервере. (Если вы еще не выполнили шаги из введения к Главе 3, вам следует вернуться и сделать это сейчас.) В рамках развертывания мы добавим http://ru.wikipedia.org/wiki/SSL В качестве подготовки к развертыванию, вам следует объединить ваши изменения с master веткой: Для того чтобы задеплоить приложение, нам для начала нужно добавить строку инициирующую использование SSL в продакшен. Получившийся в результате конфигурационный файл продакшен окружения config/environments/production.rb представлен в Листинге 7.29. Для того чтобы получить рабочий продакшен сайт, мы должны закоммитить изменения в конфигурационном файле и отправить результат на Heroku: Затем нам нужно запустить миграцию на продакшен базе данных для того чтобы сообщить Heroku о модели данных User (Вы можете увидеть deprecation предупреждения в этой точке; просто игнорируйте их.) Наконец, нам необходимо установить SSL на удаленном сервере. Конфигурирование продакшен сайта для использования SSL это довольно неприятная процедура, кроме всего прочего подразумевающая покупку SSL сертификата для вашего домена. К счастью, для приложений запущенных на домене Heroku (таких как наш Пример Приложения), мы можем упасть на хвост SSL сертификату Heroku. Если вы хотите запустить SSL на собственном домене, таком как example.com, вам ничего не остается кроме как немного помучиться, о том как это правильно делать вы можете прочитать на Heroku странице о SSL. Результатом всей этой работы является рабочая форма регистрации на продакшен сервере (рис 7.22): Обратите внимание на https:// вместо обычного http:// (рис 7.22). Дополнительная ‘s’ это указание на то что SSL работает. Теперь вы можете посетить страницу регистрации и создать нового пользователя. Если возникнут какие-то проблемы, попробуйте выполнить для того чтобы отловить ошибку с помощью логов Heroku. Возможность регистрировать пользователей это важная веха для нашего приложения. Хотя пример приложения до сих пор не делает ничего полезного, мы заложили необходимый фундамент для последующей разработки. В Главе 8 мы завершим наш механизм аутентификации позволив пользователям входить и выходить из приложения. В Главе 9 позволим пользователям обновлять информацию в их учетных записях и позволим администраторам сайта удалять пользователей тем самым завершив полный набор REST действий ресурса Users из Таблицы 7.1. Наконец, мы добавим методы авторизации к нашим действиям для того чтобы обеспечить безопасность сайта.
(рис 7.18) Результат посещения /signup и клика по “Create my account”.
Успешная регистрация
(рис 7.19) Набросок успешной регистрации.
Завершенная форма регистрации
$ bundle exec rspec spec/requests/user_pages_spec.rb \
> -e "signup with valid information"
create. Вместо этого мы должны перенаправить на другую страницу и будет вполне логично если этой страницей будет профиль вновь созданного пользователя. Тестирование того что рендерится правильная страница оставлено в качестве упражнения (Раздел 7.6); код представлен в Листинге 7.26.class UsersController < ApplicationController
.
.
.
def create
@user = User.new(user_params)
if @user.save
redirect_to @user
else
render 'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
user_path в редиректе, написав просто redirect_to @user для перенаправления на страницу показывающую пользователя.$ bundle exec rspec spec/
Флэш
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.
<!DOCTYPE html>
<html>
.
.
.
<body>
<%= render 'layouts/header' %>
<div class="container">
<% flash.each do |key, value| %>
<div class="alert alert-<%= key %>"><%= value %></div>
<% end %>
<%= yield %>
<%= render 'layouts/footer' %>
<%= debug(params) if Rails.env.development? %>
</div>
.
.
.
</body>
</html>
div тег, с CSS классом, указывающим на тип сообщения. Например, если flash[:success] = "Welcome to the Sample App!", то код<% flash.each do |key, value| %>
<div class="alert alert-<%= key %>"><%= value %></div>
<% end %>
<div class="alert alert-success">Welcome to the Sample App!</div>
flash.now, но мы отложим эти тонкости до тех пор пока они нам не понадобятся.flash[:success] приветственное сообщение в действии create как это показано в Листинге 7.28.class UsersController < ApplicationController
.
.
.
def create
@user = User.new(user_params)
if @user.save
flash[:success] = "Welcome to the Sample App!"
redirect_to @user
else
render 'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
Первая регистрация
(рис 7.20) Результирующая страница успешной регистрации с флэш сообщением.
(рис 7.21) Профиль пользователя с исчезнувшим после перезагрузки страницы флэш сообщением.
$ rails console
>> User.find_by(email: "example@railstutorial.org")
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org",
created_at: "2013-03-12 05:51:34", updated_at: "2013-03-12 05:51:34",
password_digest: "$2a$10$A58/j7wwh3aAffGkMAO9Q.jjh3jshd.6akhDKtchAz/R...">
Развертывание приложения на сервере с SSL
$ git add .
$ git commit -m "Finish user signup"
$ git checkout master
$ git merge sign-up
SampleApp::Application.configure do
.
.
.
# Force all access to the app over SSL, use Strict-Transport-Security,
# and use secure cookies.
config.force_ssl = true
.
.
.
end
$ git commit -a -m "Add SSL in production"
$ git push heroku
$ heroku run rake db:migrate
$ heroku open
$ heroku logs
(рис 7.22) Рабочая страница регистрации в Web.
Заключение
Упражнения
gravatar_for, определенному в Разделе 7.1.4 принимать опциональный параметр size, позволив код вроде gravatar_for user, size: 40 в представлении.create. Листинг 7.32 использует метов have_selector представленный в упражнениях Главы 5 (Раздел 5.6). В данном случае мы используем have_selector для выбора определенных CSS классов вместе с конкретными HTML тегами.content_tag, тоже работает.module UsersHelper
# Returns the Gravatar (http://gravatar.com/) for the given user.
def gravatar_for(user, options = { size: 50 })
gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
size = options[:size]
gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
image_tag(gravatar_url, alt: user.name, class: "gravatar")
end
end
.
.
.
describe "signup" do
before { visit signup_path }
.
.
.
describe "with invalid information" do
.
.
.
describe "after submission" do
before { click_button submit }
it { should have_title('Sign up') }
it { should have_content('error') }
end
.
.
.
.
.
.
describe "with valid information" do
.
.
.
describe "after saving the user" do
before { click_button submit }
let(:user) { User.find_by(email: 'user@example.com') }
it { should have_title(user.name) }
it { should have_selector('div.alert.alert-success', text: 'Welcome') }
end
.
.
.
<!DOCTYPE html>
<html>
.
.
.
<% flash.each do |key, value| %>
<%= content_tag(:div, value, class: "alert alert-#{key}") %>
<% end %>
.
.
.
</html>
Теперь, когда у нас есть рабочая модель User, пришло время добавить возможность, без которой некоторые сайты просто жить не могут: позволить пользователям регистрироваться на сайте. Мы будем использовать HTML форму для предоставления пользователями регистрационной информации нашему приложению в Разделе 7.2, которая затем будет использована для создания нового пользователя и сохранения его атрибутов в базе данных в Разделе 7.4. В конце процесса регистрации важно отобразить страницу профиля с информацией о новом созданном пользователе, так что мы начнем с создания страницы для демонстрации пользователей, которая будет для нас первым шагом на пути реализации REST архитектуры для пользователей (Раздел 2.2.2). Как обычно, мы будем писать тесты по мере разработки, расширяя наши познания о применении RSpec и Capybara для написания кратких и выразительных интеграционных тестов.
Для того чтобы создать страницу профиля пользователя, нам необходимо иметь пользователя в базе данных, что представлеяет проблему яйца и курицы: как сайт может иметь пользователя до появления рабочей страницы регистрации? К счастью, эта проблема уже решена: в Разделе 6.3.5 мы создавали запись User вручную с помощью Rails консоли. Если вы пропустили этот раздел, вам следует вернуться и закончить его перед продолжением.
Если вы используете управление версиями, создайте тему ветки, как обычно:
$ git checkout master $ git checkout -b sign-up
В этом разделе мы сделаем первый шаг на пути к конечной странице пользователя, создав страницу для отображения имени и фотографии пользователя, на что указывает набросок страницы на рис 7.1.
(рис 7.1) Набросок страницы профиля пользователя которая будет сделана в этом разделе
(рис 7.2) Набросок наших лучших ожиданий от конечной страницы профиля.
Профили в этом разделе будут первыми по-настоящему динамическими страницами в нашем приложении. Хотя представление будет существовать как одна страница кода, каждый профиль будет кастомизирован с использованием информации получаемой из базы данных сайта. В качестве подготовки к добавлению динамических страниц в наш пример приложения, сейчас хорошее время, чтобы добавить некоторую отладочную информацию к шаблону нашего сайта (Листинг 7.1). Это отображает некоторую полезную информацию о каждой странице с помощью встроенного debug метода и params переменной (о которой мы узнаем больше в Разделе 7.1.2).
<!DOCTYPE html>
<html>
.
.
.
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
<%= render 'layouts/footer' %>
<%= debug(params) if Rails.env.development? %>
</div>
</body>
</html>
Для того чтобы вывод отладочной информации приятно выглядел, мы добавим несколько правил к кастомной таблице стилей созданной в Главе 5, как это показано в Листинге 7.2.
@import "bootstrap";
/* mixins, variables, etc. */
$grayMediumLight: #eaeaea;
@mixin box_sizing {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.
.
.
/* miscellaneous */
.debug_dump {
clear: both;
float: left;
width: 100%;
margin-top: 45px;
@include box_sizing;
}
Этот код вводит новую для нас возможность Sass - примесь, в данном случае названную box_sizing. Примесь позволяет группировать CSS правила с тем чтобы они могли использоваться для нескольких элементов, конвертируя
.debug_dump {
.
.
.
@include box_sizing;
}
в
.debug_dump {
.
.
.
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
Мы еще раз применим эту примесь в Разделе 7.2.2. Результат ее применения в контексте блока с отладочной информацией показан на рис 7.3.
(рис 7.3) Home страница примера приложения (/) с отладочной информацией.
Вывод отладочной информации на рис 7.3 дает потенциально полезную информацию об отображаемой в настоящий момент странице:
--- controller: static_pages action: home
Это YAMLdebug информация показана как YAML (a recursive acronym (или рекурсивный акроним)) обозначающий "YAML Ain’t Markup Language"), который является дружественным форматом данных, разработанным так, чтобы быть удобочитаемым и для машин и для людей.params, который по сути является хэшем и в данном случае указывает на контроллер и действие страницы. Мы увидим еще один пример в Разделе 7.1.2
Поскольку мы не хотим показывать отладочную информацию пользователям развернутого приложения, Листинге 7.1 использует
if Rails.env.development?
для того чтобы показывать отладочную информацию только в среде разработки, которая является одной из трех сред, определенных по умолчанию в Rails (Блок 7.1).Rails.env.development? является true только в среде разработки, так что Embedded Ruby
<%= debug(params) if Rails.env.development? %>
не будет вставлен в развернутое приложение или тесты. (Вставка отладочной информации в тесты возможно не навредит, но скорее всего и не сделает ничего хорошего, так что лучше ограничиться ее отображением только в среде разработки.)
___________________________________________
Блок 7.1.Окружения Rails
Rails поставляется с тремя окружениями: test, development и production. Дефолтным окружением для Rails консоли является development:
$ rails console Loading development environment >> Rails.env => "development" >> Rails.env.development? => true >> Rails.env.test? => false
Как вы можете видеть, Rails обеспечивает объект Rails атрибутом env и связанными с ним булевыми методами окружения, таким образом, например, Rails.env.test? возвращает true в тестовом окружении и false во всех остальных.
Если вам когда либо понадобится запустить консоль в окружении, отличном от дефолтного (для отладки теста, например), вы можете предать окружение в качестве параметра к console скрипту:
$ rails console test Loading test environment >> Rails.env => "test" >> Rails.env.test? => true
Как и в случае с консолью, development является дефолтным окружением для локального Rails сервера, но вы также можете запустить его и в другом окружении:
$ rails server --environment production
Если вы попробуете посмотреть на свое приложение запущенное в production, оно не будет работать без production базы данных, которую мы можем создать запустив rake db:migrate в production:
$ bundle exec rake db:migrate RAILS_ENV=production
(Я считаю запутывающим, когда консоль, сервер и команды миграций устанавливают не дефолтные окружения тремя взаимно исключающими способами, поэтому я дал себе труд показать все три.)
Кстати, если вы развернули свое приложение на Heroku, вы можете посмотреть его окружение с помощью команды heroku, которая предоставляет свою собственную (удаленную) консоль:
$ heroku run console Ruby console for yourapp.herokuapp.com >> Rails.env => "production" >> Rails.env.production? => true
Вполне естественно, что, поскольку Heroku является платформой для production сайтов, он запускает каждое приложение в production окружении.
___________________________________________
В конце Главы 6 мы создали нового пользователя в базе данных. Как показано в Разделе 6.3.5, этот пользователь имеет id 1 и наша цель теперь заключается в создании страницы для отображения информации этого пользователя. Мы будем следовать соглашениям REST архитектуры, предпочитаемой Rails в Rails приложениях (Блок 2.2), что означает представление данных в качестве ресурсов которые могут быть созданы, показаны, обновлены, или уничтожены — четыре действия, соответствующие четырем фундаментальным операциям POST, GET, PATCH и DELETE определенным стандартом HTTP (Блок 3.3).
Следуя принципам REST, на ресурсы обычно ссылаются, используя имя ресурса и уникальный идентификатор. Что это означает в контексте пользователей — о которых мы теперь думаем, как о ресурсе Users—то, что мы должны показать пользователя с id 1 выдав GET запрос к URL /users/1. Здесь show действие неявно в типе запроса —когда Rails’ функции REST активированы, GET запросы автоматически обрабатываются show действием.
Мы видели в Разделе 2.2.1 что страница для пользователя с id 1 имеет URL /users/1. К сожалению, сейчас посещение этой страницы лишь выдает ошибку (рис 7.4).
(рис 7.4) Страница ошибки для /users/1.
Мы можем заполучить REST-style Users URL на работу, добавив одну-единственную строку в наш файл маршрутов (config/routes.rb):
resources :users
Результат представлен в Листинге 7.3.
SampleApp::Application.routes.draw do resources :users root 'static_pages#home' match '/signup', to: 'users#new', via: 'get' . . . end
Вы возможно заметили, что Листинг 7.3 удаляет строку
get "users/new"
последний раз замеченную в Листинге 5.35. Это связано с тем, что
| HTTP запрос | URL | Действие | Именованный маршрут | Назначение |
|---|---|---|---|---|
| GET | /users | index |
users_path |
страница показывающая список всех пользователей |
| GET | /users/1 | show |
user_path(user) |
страница показывающая пользователя |
| GET | /users/new | new |
new_user_path |
страница для создания нового пользователя (регистрация) |
| POST | /users | create |
users_path |
создание нового пользователя |
| GET | /users/1/edit | edit |
edit_user_path(user) |
страница для редактирования пользователя с id 1 |
| PATCH | /users/1 | update |
user_path(user) |
обновление пользователя |
| DELETE | /users/1 | destroy |
user_path(user) |
удаление пользователя |
С кодом в Листинге 7.3, маршруты работают, но страница по-прежнему не существует (рис 7.3" type="example">Листинге 7.3, маршруты работают, но страница по-прежнему не существует ((рис 7.5) URL /users/1 с маршрутом, но без страницы.
Мы будем использовать стандартное Rails размещение для представления показывающего пользователя, которым является app/views/users/show.html.erb. В отличие от представления new.html.erb, которое мы создали с помощью генератора в Листинге 5.31, файл show.html.erb в данный момент не существует, так что нам необходимо создать его вручную и заполнить его содержимым Листинга 7.4.
<%= @user.name %>, <%= @user.email %>
Это представление использует Embedded Ruby для отображения имени и адреса электронной почты пользователя, предполагая наличие переменной экземпляра @user. Конечно же, в итоге реальная страница пользователя будет выглядеть совершенно иначе и не будет публично демонстрировать адрес электронной почты.
Для того чтобы заставить представление user show работать, нам необходимо определить переменную @user в соответствующем show действии контроллера Users. Как вы и ожидали, мы используем метод find на модели User (Раздел 6.1.4) для получения пользователя из базы данных, как это показано в Листинге 7.5.
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
end
end
Здесь мы использовали params для получения id пользователя. Когда мы сделаем соответствующий запрос в контроллер Users, params[:id] будет пользовательским id 1, так что эффект тот же что и с методом find
User.find(1)
который мы видели в Разделе 6.1.4. (Технически, params[:id] является строкой "1", но find достаточно умен, чтобы преобразовать это в целое число.)
С представлением и определенным действием, URL /users/1 работает замечательно (рис 7.6). Обратите внимание что отладочная информация в рис 7.6 подтверждает значение params[:id]:
--- action: show controller: users id: '1'
Вот почему код
User.find(params[:id])
в Листинге 7.5 находит пользователя с id 1.
(рис 7.6) Страница показывающая пользователя на /users/1 после добавления ресурса Users.
Теперь, когда у нас есть минимально рабочий профиль, пришло время поработать над версией из наброска на рис 7.1. Как и в случае с созданием статических страниц (Глава 3) и моделью User (Глава 6), мы сделаем это используя разработку через тестирование.
Вспомните из Раздела 5.4.2 что для страниц связанных с ресурсом Users мы приняли решение использовать интеграционные тесты. В случае со страницей регистрации, наш тест вначале посещает signup_path а затем проверяет правильность h1 и title тегов, как это показано в Листинге 5.34 и повторено в Листинге 7.6. (Обратите внимание, что мы опустили full_title хелпер из Раздела 5.3.4 поскольку полный заголовок уже адекватно протестирован.)
require 'spec_helper'
describe "User pages" do
subject { page }
describe "signup page" do
before { visit signup_path }
it { should have_content('Sign up') }
it { should have_title(full_title('Sign up')) }
end
end
Для того чтобы протестировать страницу показывающую пользователя, нам необходим объект модели User для того чтобы коду в действии show (Листинг 7.5) было что искать:
describe "profile page" do
# Replace with code to make a user variable
before { visit user_path(user) }
it { should have_content(user.name) }
it { should have_title(user.name) }
end
где нам нужно заполнить комментарий соответствующим кодом. Здесь используется именованный маршрут ) для генерации пути к странице показывающей данного пользователя. Затем мы тестируем что и страница и тайтл содержат имя пользователя.
Для того чтобы создать необходимый объект модели User, мы можем использовать Active Record для создания пользователя посредством User.create, но практика показывает, что пользовательские фабрики являются более удобным способом определения и вставки в базу данных объектов user. Мы будем использовать фабрики генерируемые Factory Girl - Ruby гемом который сделан хорошими людьми из thoughtbot. Как и RSpec, Factory Girl определяет предметно-ориентированный язык в Ruby, в данном случае предназначенный для определения объектов Active Record. Синтаксис прост, опирается на Ruby блоки и кастомные методы для определения атрибутов описываемого объекта. Для случаев вроде тех что мы увидим в этой главе, преимущество над Active Record может быть неочевидным, но мы будем использовать более продвинутые техники фабрик в последующих главах. Например, в Разделе 9.3.3 нам понадобится создать набор пользователей с уникальными адресами электронной почты и фабрики позволят нам проделать это с легкостью.
Как и с другими Ruby гемами, мы можем установить Factory Girl добавив строку в Gemfile используемый Bundler-ом (Листинг 7.7). (Поскольку Factory Girl нужна только в тестах, мы поместили ее в группу :test.)
source 'https://rubygems.org' . . . group :test do . . . gem 'factory_girl_rails', '4.2.1' end . . .
Затем устанавливаем как обычно:
$ bundle install
Мы поместим все наши фабрики Factory Girl в файл spec/factories.rb, который автоматически будет загружен RSpec-ом. Код необходимый для создания фабрики User представлен в Листинге 7.8.
FactoryGirl.define do
factory :user do
name "Michael Hartl"
email "michael@example.com"
password "foobar"
password_confirmation "foobar"
end
end
Передавая символ :user команде factory, мы говорим Factory Girl что последующее определение предназначено для объекта модели User.
С определением в Листинге 7.8, мы можем создать фабрику User в тестах используя команду let (Блок 6.3) и метода FactoryGirl поддерживаемого Factory Girl:
let(:user) { FactoryGirl.create(:user) }
Конечный результат представлен в Листинге 7.9.
require 'spec_helper'
describe "User pages" do
subject { page }
describe "profile page" do
let(:user) { FactoryGirl.create(:user) }
before { visit user_path(user) }
it { should have_content(user.name) }
it { should have_title(user.name) }
end
describe "signup page" do
before { visit signup_path }
it { should have_content('Sign up') }
it { should have_title(full_title('Sign up')) }
end
end
В этой точке вам необходимо проверить что набор тестов в красном:
$ bundle exec rspec spec/
Мы можем получить зеленые тесты с кодом из Листинга 7.10.
<% provide(:title, @user.name) %> <h1><%= @user.name %></h1>
Повторный запуск тестов должен подтвердить что тесты из Листинга 7.9 теперь проходят:
$ bundle exec rspec spec/
Вы быстро заметите что тесты с Factory Girl - медленные. И это происходит не по вине Factory Girl и фактически это фича, а не баг. Проблема связана с тем, что алгоритм BCrypt используемый в Разделе 6.3.1 для создания хэша безопасного пароля является медленным по своей природе: именно низкая скорость BCrypt отчасти делает его таким сложным для взлома. К сожалению, это означает что создание пользователей может утопить набор тестов; к счастью, есть простой способ исправить это. Библиотека bcrypt-ruby использует фактор стоимости для управления вычислительной сложностью создаваемого безопасного хэша. Дефолтное значение призвано обеспечить безопасность, а не скорость и для рабочих приложений это здорово, но в тестах наши потребности противоположны: мы хотим быстрые тесты и нас совершенно не тревожит безопасность хэшей паролей тестовых пользователей. Решением будет добавление строки которая переопределит фактор стоимости от дефолтного безопасного значения к минимально возможному в файл конфигурации тестового окру жения config/environments/test.rb, как это показано в Листинге 7.11. Даже для небольшого набора тестов выигрыш в скорости от этого шага может быть весьма значительным и я настоятельно рекомендую включить Листинг 7.11 в ваш test.rb конфиг.
SampleApp::Application.configure do . . . # Speed up tests by lowering bcrypt's cost function. ActiveModel::SecurePassword.min_cost = true end
Определив базовую страницу пользователя в предыдущем разделе, теперь мы немного улучшим ее, добавив изображение пользователя и начальную реализацию боковой панели пользователя. При создании страницы мы сфокусируемся на ее внешнем виде, не особо заботясь о ее структуре, это означает, что (по крайней мере пока) мы не будем писать тесты. Когда мы подойдем к аспектам представления, более подверженным ошибкам, таким как пагинация (Раздел 9.3.3), мы продолжим разработку через тестирование.
Мы начнем с добавления "глобально распознаваемого аватара" (также известного как Граватар) к профилю пользователя.
Мы планируем определить вспомогательную функцию gravatar_for которая будет возвращать изображение Граватар для данного пользователя, как это показано в Листинге 7.12.
<% provide(:title, @user.name) %> <h1> <%= gravatar_for @user %> <%= @user.name %> </h1>
В этой точке вы можете проверить что набор тестов не проходит:
$ bundle exec rspec spec/
Из-за того что метод gravatar_for неопределен, страница показывающая пользователя в настоящий момент не работает. (Отлов подобных ошибок это, возможно самый полезный аспект тестов представлений. Вот почему так важно наличие хоть каких-то тестов представлений, даже минималистичных.)
По умолчанию, методы определеннные в любом файле хелпера являются автоматически доступным в любом представлении, но для удобства мы поместим метод gravatar_for в файл для хелперов, связанных с контроллером Users. Как отмечено на главной странице Граватар, URL Граватара основывается на MD5 хэше адреса электронной почты пользователя. В Ruby, алгоритм MD5 хэширования реализутся с помощью метода hexdigest, который является частью библиотеки Digest:
>> email = "MHARTL@example.COM". >> Digest::MD5::hexdigest(email.downcase) => "1fda4469bcbec3badf5418269ffc5968"
Поскольку адреса электронной почты нечувствительны к регистру (Раздел 6.2.4), в отличие от MD5 хэшей, мы используем метод downcase для того чтобы быть уверенными в том, что аргумент передаваемый в hexdigest находится в нижнем регистре. Получившийся хелпер gravatar_for представлен в Листинге 7.13.
module UsersHelper
# Returns the Gravatar (http://gravatar.com/) for the given user.
def gravatar_for(user)
gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}"
image_tag(gravatar_url, alt: user.name, class: "gravatar")
end
end
Код в Листинге 7.13 возвращает тег img для Граватара с классом "gravatar" и альтернативным текстом эквивалентным имени пользователя (что особенно удобно для браузеров используемых слабовидящими людьми которые работают с помощью считывателей экрана). Вы можете проверить что теперь набор тестов проходит:
$ bundle exec rspec spec/
Страница профиля представленная на рис 7.7, показывающая дефолтное изображение Граватара, выглядит подобным образом т.к. user@example.com это невалидный email адрес (домен example.com зарезервирован для примеров (examples).
(рис 7.7) Страница профиля пользователя /users/1 с дефолтным Граватаром.
Для того чтобы отобразить кастомный Граватар в нашем приложении, мы будем использовать update_attributes (Раздел 6.1.5) для обновления пользователя в базе данных:
$ rails console >> user = User.first >> user.update_attributes(name: "Example User", ?> email: "example@railstutorial.org", ?> password: "foobar", ?> password_confirmation: "foobar") => true
Здесь мы назначили пользователю адрес электронной почты .
(рис 7.8) Страница показывающая пользователя с кастомным Граватаром.
Последний элемент, необходимый для завершения наброска из рис 7.1 это начальная версия пользовательской боковой панели. Мы реализуем ее с помощью тега aside, который используется для элементов (таких как сайдбары), которые дополняют страницу, но также могут быть использованы отдельно. Мы включаем row и span4 классы, которые являются частью Bootstrap. Код для измененной страницы показывающей пользователя представлен в Листинге 7.14.
<% provide(:title, @user.name) %>
<div class="row">
<aside class="span4">
<section>
<h1>
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
</section>
</aside>
</div>
Имея соответствующие HTML элементы и CSS классы, мы можем отстилить страницу профиля (включая боковую панель и Gravatar) с SCSS показанным в Листинге 7.15. (Обратите внимание на наследование CSS правил, которое работает только благодаря препроцессору Sass, используемому файлопроводом.) Получившаяся страница показана на рис 7.15" type="example">Листинге 7.15. (Обратите внимание на наследование CSS правил, которое работает только благодаря препроцессору Sass, используемому файлопроводом.) Получившаяся страница показана на .
.
.
/* sidebar */
aside {
section {
padding: 10px 0;
border-top: 1px solid $grayLighter;
:first-child {
border: 0;
padding-top: 0;
}
span {
display: block;
margin-bottom: 3px;
line-height: 1;
}
h1 {
font-size: 1.4em;
text-align: left;
letter-spacing: -1px;
margin-bottom: 3px;
margin-top: 0px;
}
}
}
.gravatar {
float: left;
margin-right: 10px;
}
Теперь, когда у нас есть рабочая (хоть и незавершенная) страница профиля пользователя, мы готовы приступить к созданию формы для регистрации на нашем сайте. Мы видели на рис 5.9 (дублированном на рис 7.10) что страница регистрации в настоящий момент пуста и совершенно бесполезна для регистрации новых пользователей. Целью этого раздела является разработка формы регистрации из рис 7.11. Поскольку мы говорим о создании пользователей через веб-интерфейс, давайте удалим пользователя созданного через консоль в Разделе 6.3.5. Простейшим способом сделать это является очистка базы данных с помощью Rake-задачи После очистки базы данных на некоторых системах также необходимо заново подготовить тестовую базу данных: Наконец, на некоторых системах вам может потребоваться перезагрузка сервера для того чтобы изменения вступили в силу. До появления мощных веб-фреймворков с полным набором тестировочных средств, тестирование часто доставляло много хлопот. Например, для тестирования страницы регистрации вручную, нам бы пришлось посещать страницу в браузере, затем заполнять форму различными валидными и невалидными данными и проверять что в каждом случае поведение приложения соответствует ожидаемому. Кроме того, нам бы пришлось проделывать это каждый раз при изменении приложения. С помощью RSpec и Capybara мы сможем написать выразительные тесты, которые автоматизируют задачи, которые раньше делались вручную. Мы уже видели как Capybara поддерживает интуитивно понятный синтаксис веб-навигации. До сих пор мы в основном использовали и кликанья по кнопке. Синтаксис выглядит примерно так: Сейчас нашей целью является написание тестов для правильного поведения при предоставлении невалидной и валидной регистрационной информации. Поскольку эти тесты довольно продвинутые, мы будем писать их постепенно. Если вам интересно посмотреть как они работают (включая файл в который они должны быть помещены), вы можете перейти непосредственно к Листингу 7.16. Наша первая задача это тестирование провальной отправки регистрационной формы, и мы можем симулировать отправку невалидных данных посетив страницу и кликнув по кнопке с помощью Это эквивалентно посещению страницы регистрации и отправке формы незаполненной регистрационной информацией. Аналогично, для симуляции отправки валидных данных, мы заполняем форму валидными данными с помощью Эти тесты проверяют что поведение приложения после клика по кнопке "Create my account" соответствует нашим ожиданиям: создается новый пользователь если информация валидна и не создается новый пользователь в случае когда она невалидна. Для того чтобы сделать это мы проверяем количество пользователей и наши тесты будут использовать метод Здесь При отправке невалидных данных, мы ожидаем что количество пользователей не будет изменено; при отправке валидных данных мы ожидаем что оно изменится на 1. Мы можем выразить это в RSpec скомбинировав метод Обратите внимание: вычисляет до и после выполнения В данном случае, мы хотим чтобы данный код не изменял количества, что мы можем выразить с помощью метода на одну строку которая читается как естественный язык и является намного более компактной (eq это RSpec метод для тестирования на равенство). Случай с валидными данными аналогичен, но вместо проверки того, что количество пользователей не изменилось, мы проверяем что клик по кнопке изменяет их количество на 1: Здесь используется метод Комбинирование двух случаев с соответствующими блоками Мы будем добавлять тесты по мере надобности в последующих разделах, но базовые тесты в Листинге 7.16 уже покрывают внушительный объем функционала. Для того чтобы получить их прохождение, мы должны создать страницу регистрации с необходимым минимумом правильных элементов, предопределить направление пользователя в правильное место после отправки формы и создание нового пользователя в базе данных только в случае если полученная информация валидна. Конечно же, в этой точке тесты должны быть провальными: Теперь, когда у нас есть хорошие провальные тесты для регистрации пользователя, мы начнем добиваться их прохождения, для начала создав форму для регистрации пользователей. Мы можем сделать это в Rails с помощью вспомогательного метода Давайте рассмотрим этот код по частям. Наличие ключевого слова do указывает на то что Как это зачастую происходит с хелперами Rails, нам нет надобности знать о подробностях реализации, но нам необходимо знать что делает объект f: при вызове с методом соответствующим элементу HTML формы — таким как текстовое поле, радио кнопка, или поле пароля — он возвращает особым образом организованный код для этого элемента для установки атрибута объекта @user. Другими словами, создает HTML необходимый для создания элемента маркированного текстового поля для назначения атрибута Для того чтобы увидеть это в действии нам необходимо посмотреть на HTML производимый этой формой, но здесь мы сталкиваемся с проблемой: страница в настоящее время сломана, поскольку мы не установили переменную (Флаг После определения переменной @user, тесты для страницы регистрации должны пройти: В этой точке у нас должна получиться форма (со стилями из Листинга 7.19) выглядящая как на рис 7.19" type="example">Листинга 7.19) выглядящая как на box_sizing из Листинга 7.2. Как нам подсказывает Рис. 7.12, теперь страница регистрации рендерится как следует, что указывает на то, что код (Здесь я опустил HTML связанный с authenticity token, который Rails автоматически включают для предотвращения определенного вида атак, назывемого подделка межсайтовых запросов (CSRF). См введение в Rails authenticity token на Stack Overflow если вас интересуют подробности того как это работает и почему это так важно.) Мы начнем с внутренней структуры документа. Сравнивая Листинг 7.17 с Листингом 7.20, мы видим что Embedded Ruby производит HTML и производит HTML Как видно из рис 7.13, текстовые поля (. Как мы увидим в Разделе 7.4, ключом к созданию пользователя является специальный Эти значения Второй важный элемент это сам тег Здесь атрибуты Мы кратко рассмотрели HTML формы показанной на рис 7.12 (Листинге 7.20), теперь мы копнем эту тему чуть глубже. Так как HTML формы лучше всего понимается в контексте сбоя регистрации, в этом разделе мы создадим регистрационную форму которая принимает невалидные данные и вновь рендерит страницу регистрации со списком ошибок, как это показано на наброске рис 7.20" type="example">Листинге 7.20), теперь мы копнем эту тему чуть глубже. Так как HTML формы лучше всего понимается в контексте сбоя регистрации, в этом разделе мы создадим регистрационную форму которая принимает невалидные данные и вновь рендерит страницу регистрации со списком ошибок, как это показано на наброске (рис 7.14) Набросок страницы неудавшейся регистрации.
Вспомните из Раздела 7.1.2 что добавление Как было отмечено в Разделе 7.2.3, этот HTML выдает POST запрос к /users URL. Нашим первым шагом на пути к зеленым тестам для невалидных данных из Листинга 7.16 будет добавление кода из Листинге 7.21. Этот листинг включает второе использование метода Обратите внимание на комментарий: это не конечная реализация, но этого достаточно для начала. Мы закончим реализацию в Разделе 7.3.2. Лучший способ понять как работает код в Листинге 7.21 это отправить форму с какими-нибудь невалидными регистрационными данными; результат представлен на рис 7.15 и полная отладочная информация (с увеличенным размером шрифта) представлена на рис 7.16. Чтобы получить более четкую картину того как Rails обрабатывает отправку регистрационных данных, давайте поближе познакомимся с user частью хэша параметров из отладочной информации (рис 7.16): Этот хэш передается в контроллер Users в качестве с именем " Хотя хэш-ключи показаны в отладочном выводе в виде строк, в контроллер Users они передаются в виде символов, так что практически эквивалентна В предыдущих версиях Rails использование фактически работало, но было по умолчанию небезопасным, требовало особой тщательности при осуществлении довольно глючной процедуры имеющей своей целью защитить базу данных приложения от зловредных пользователей. В Rails 4.0 этот код вызывает ошибку (как показано на рис 7.15 и рис 7.16 выше), что означает что он безопасен по умолчанию. Мы можем еще раз убедиться в этом проверив что соответствующие тесты не проходят: В Разделе 4.4.5 мы коротко упомянули об идее массового назначения, которая подразумевает инициализацию Ruby-переменной с хэшем значений: Комментарий включенный в Листинг 7.21 и воспроизведенный выше указывает на то что это не финальная реализация. Причина по которой иниацилизация всего хэша Для решения этой проблемы предыдущие версии Rails использовали метод В данном случае мы хотим требовать от хэша Этот код вернет версию хэша Для облегчения использования этих параметров, обычно вводят вспомогательный метод с названием Поскольку С кодом из Листинге 7.22 тесты отправки невалидных данных должны пройти: В качестве финального шага в реализации провального создания пользователя мы добавим полезные сообщения об ошибках для указания на проблемы которые помешали регистрации. Rails автоматически предоставляет такие сообщения, основываясь на валидациях модели User. Рассмотрим, например, попытку сохранения пользователя с неправильным адресом электронной почты и коротким паролем: Здесь объект Как и в консольной сессии выше, сбой сохранения в Листинге 7.21 генерирует список сообщений об ошибках, связанных с объектом Заметим здесь, что мы Этот частичный шаблон вводит несколько новых Rails и Ruby конструкций, в том числе два метода для объекта ошибок Rails. Первый метод это count, который просто возвращает количество ошибок: Другой новый метод это Мы видим здесь что метод Другой новой идеей является текстовый хелпер Мы видим здесь, что В результате код возвращает Обратите внимание: Листинг 7.24 включает CSS . Поскольку сообщения генерируются валидациями модели, они автоматически изменятся, если вы когда-нибудь поменяете свое мнение о, скажем, формате адресов электронной почты или минимальной
длине паролей. Для того чтобы увидеть результаты нашей работы в этом разделе, мы повторим шаги из теста провальной регистрации Листинга 7.16 посетив страницу регистрации и кликнув по "Sign up" с пустыми полями ввода. Результат показан на рис 7.16" type="example">Листинга 7.16 посетив страницу регистрации и кликнув по "Sign up" с пустыми полями ввода. Результат показан на $ bundle exec rspec spec/requests/user_pages_spec.rb \
> -e "signup with invalid information"
Получив обработку отправки невалидной формы, теперь пришло время для завершения регистрационной формы, на самом деле сохранив нового пользователя (если валидный) в базу данных. Во-первых, мы попробуем сохранить пользователя; если сохранение пройдет успешно, информация пользователя будет записана в базу данных автоматически, а затем мы перенаправим браузер на страницу профиля пользователя (с дружеским приветствием), как это показано на наброске рис 7.19. Если это не удастся, мы просто отступим к сценарию, разработанному в Разделе 7.3. Для того чтобы закончить работу с формой регистрации, нам необходимо заполнить закомментированный раздел в Листинге 7.21 соответствующим поведением. Сейчас тесты для отправки валидной формы должны быть провальными: Это происходит из-за того что дефолтным поведением для Rails-действий является рендеринг соответствующего представления, но здесь нет (и не должно быть) представления соответствующего действию Обратите внимание на то что мы можем опустить С кодом в Листинге 7.26 наша регистрационная форма отлично работает, что вы можете проверить запустив набор тестов: Перед отправкой валидной регистрации в браузер мы собираемся немного отполировать ее, в соответствии с общепринятой в веб приложениях идеей: добавив сообщение, которое временно появляется на следующей странице (в данном случае, приветствуя нашего нового пользователя), а затем исчезает либо при посещении следующей страницы либо при перезагрузке текущей. Rails-способ сделать это состоит в использовании специальной переменной flash с которой мы можем обращаться как с обычным хэшем. Вы можете даже вспомнить консольный пример из Разделе 4.3.3, где мы видели как для перебора хэша использовался стратегически именованный хэш Мы можем организовать отображение содержимого флэш повсеместно на сайте, включив его в шаблон нашего приложения, как это показно в Листинге 7.27. (Этот код представляет из себя особенно уродливую комбинацию HTML и ERb; упражнение в Разделе 7.6 показывает как сделать его более красивым.) Код в Листинге 7.27 организует вставку каждого флэш элемента в произведет такой HTML: (Обратите внимание: ключ Написание теста на правильное флэш сообщение оставлено в качестве упражнения (Раздел 7.6) и мы можем получить прохождение теста назначив Вы можете увидеть результат всей этой работы, зарегистрировав нашего первого пользователя под именем "Rails Tutorial" и с адресом электронной почты "example@railstutorial.org". Получившаяся в результате страница (рис 7.20) показывает дружеское сообщение после успешной регистрации, включая приятный зеленый стиль для класса ). Теперь мы можем проверить нашу базу данных просто для того чтобы еще раз убедиться что новый пользователь действительно создан: Мы разработали модель User и функционал регистрации, пришло время развернуть пример приложения на сервере. (Если вы еще не выполнили шаги из введения к Главе 3, вам следует вернуться и сделать это сейчас.) В рамках развертывания мы добавим http://ru.wikipedia.org/wiki/SSL В качестве подготовки к развертыванию, вам следует объединить ваши изменения с master веткой: Для того чтобы задеплоить приложение, нам для начала нужно добавить строку инициирующую использование SSL в продакшен. Получившийся в результате конфигурационный файл продакшен окружения config/environments/production.rb представлен в Листинге 7.29. Для того чтобы получить рабочий продакшен сайт, мы должны закоммитить изменения в конфигурационном файле и отправить результат на Heroku: Затем нам нужно запустить миграцию на продакшен базе данных для того чтобы сообщить Heroku о модели данных User (Вы можете увидеть deprecation предупреждения в этой точке; просто игнорируйте их.) Наконец, нам необходимо установить SSL на удаленном сервере. Конфигурирование продакшен сайта для использования SSL это довольно неприятная процедура, кроме всего прочего подразумевающая покупку SSL сертификата для вашего домена. К счастью, для приложений запущенных на домене Heroku (таких как наш Пример Приложения), мы можем упасть на хвост SSL сертификату Heroku. Если вы хотите запустить SSL на собственном домене, таком как example.com, вам ничего не остается кроме как немного помучиться, о том как это правильно делать вы можете прочитать на Heroku странице о SSL. Результатом всей этой работы является рабочая форма регистрации на продакшен сервере (рис 7.22): Обратите внимание на https:// вместо обычного http:// (рис 7.22). Дополнительная ‘s’ это указание на то что SSL работает. Теперь вы можете посетить страницу регистрации и создать нового пользователя. Если возникнут какие-то проблемы, попробуйте выполнить для того чтобы отловить ошибку с помощью логов Heroku. Возможность регистрировать пользователей это важная веха для нашего приложения. Хотя пример приложения до сих пор не делает ничего полезного, мы заложили необходимый фундамент для последующей разработки. В Главе 8 мы завершим наш механизм аутентификации позволив пользователям входить и выходить из приложения. В Главе 9 позволим пользователям обновлять информацию в их учетных записях и позволим администраторам сайта удалять пользователей тем самым завершив полный набор REST действий ресурса Users из Таблицы 7.1. Наконец, мы добавим методы авторизации к нашим действиям для того чтобы обеспечить безопасность сайта.
(рис 7.9) Страница показывающая пользователя /users/1 с боковой панелью и CSS.
Форма регистрации
(рис 7.10) Текущее состояние страницы регистрации /signup.
(рис 7.11) Набросок страницы регистрации пользователей.
db:reset:$ bundle exec rake db:reset
$ bundle exec rake test:prepare
Тесты для регистрации пользователя
visit signup_path
fill_in "Name", with: "Example User"
.
.
.
click_button "Create my account"
click_button:visit signup_path
click_button "Create my account"
fill_in: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 "Create my account"
count, доступный для каждого класса Active Record, включая User:$ rails console
>> User.count
=> 0
User.count является 0 поскольку мы очистили базу данных в начале этого раздела.expect с методом to или методом not_to. Мы начнем со случая с невалидными данными поскольку он проще; мы посетим страницу регистрации и кликнем по кнопке, и мы ожидаем что это не изменит количества пользователей:visit signup_path
expect { click_button "Create my account" }.not_to change(User, :count)
expect обворачивает click_button в блок (Раздел 4.3.2). Что необходимо для работы метода change, который принимает в качестве аргумента объект и символ, а затем вычисляет результат вызова этого символа в качестве метода на объекте до и после блока. Другими словами, кодexpect { click_button "Create my account" }.not_to change(User, :count)
User.count
click_button "Create my account"
not_to. В результате, закрыв клик по кнопке в блоке, мы можем заменитьinitial = User.count
click_button "Create my account"
final = User.count
expect(initial).to eq final
expect { click_button "Create my account" }.not_to change(User, :count)
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"
expect do
click_button "Create my account"
end.to change(User, :count).by(1)
to поскольку мы ожидаем что клик по кнопке (вкупе с валидными данными) изменит количество пользователей на единицу.describe и выталкивание общего кода в блоки before, приводит нас к хорошим базовым тестам для регистрации пользователей, как это показано в Листинге 7.16. Здесь мы вынесли общий текст для кнопки регистрации в переменную submit, которую мы определили с помощью метода let.require 'spec_helper'
describe "User pages" do
subject { page }
.
.
.
describe "signup page" do
before { visit signup_path }
let(:submit) { "Create my account" }
describe "with invalid information" do
it "should not create a user" do
expect { click_button submit }.not_to change(User, :count)
end
end
describe "with valid information" do
before do
fill_in "Name", with: "Example User"
fill_in "Email", with: "user@example.com"
fill_in "Password", with: "foobar"
fill_in "Confirmation", with: "foobar"
end
it "should create a user" do
expect { click_button submit }.to change(User, :count).by(1)
end
end
end
end
$ bundle exec rspec spec/
Применение form_for
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>
form_for принимает блок с одной переменной, которую мы назвали f (от "form"):<%= form_for(@user) do |f| %>
.
.
.
<% end %>
<%= f.label :name %>
<%= f.text_field :name %>
name модели User. (Мы взглянем на сам HTML в Разделе 7.2.3.)@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
$ bundle exec rspec spec/requests/user_pages_spec.rb -e "signup page"
.
.
.
/* forms */
input, textarea, select, .uneditable-input {
border: 1px solid #bbb;
width: 100%;
margin-bottom: 15px;
@include box_sizing;
}
input {
height: auto !important;
}
(рис 7.12) Форма регистрации новых пользователей /signup.
HTML формы
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>
<%= f.label :name %>
<%= f.text_field :name %>
<label for="user_name">Name</label>
<input id="user_name" name="user[name]" type="text" />
<%= f.label :password %>
<%= f.password_field :password %>
<label for="user_password">Password</label><br />
<input id="user_password" name="user[password]" type="password" />
(рис 7.13) Заполненная форма с text и password полями.
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. В следующих двух разделах мы увидим к чему это приводит.Провальная регистрация
Рабочая форма
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">
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.15) Сбой регистрации.
(рис 7.16) Отладочная информация сбоя регистрации.
"user" => { "name" => "Foo Bar",
"email" => "foo@invalid",
"password" => "[FILTERED]",
"password_confirmation" => "[FILTERED]"
}
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.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")
@user = User.new(params[:user])
$ bundle exec rspec spec/requests/user_pages_spec.rb \
< -e "signup with invalid information"
Строгие параметры
@user = User.new(params[:user]) # Not the final implementation!
params является очень опасной затеей заключается в том что это приводит к передаче в User.new всех данных отправленных пользователем. В частности, предположим что, в дополнение к текущим атрибутам, модель User включает в себя атрибут admin используемый для идентификации пользователей-администраторов сайта. (Мы реализуем именно такой атрибут в Разделе 9.4.1.) Способом присвоить этому атрибуту значение true является передача значения admin=’1’ как части params[:user], этого легко достичь с помощью такого консольного HTTP клиента как curl. Таким образом, передавая весь хэш params в User.new, мы тем самым позволяем любому пользователю сайта получить права администратора включив admin=’1’ в
веб-запрос.attr_accessible на уровне модели, но в Rails 4.0 более предпочтительной является техника с использованием так называемых строгих параметров на уровне контроллера. Это позваляет нам прописать какие именно параметры являются обязательными, а какие разрешенными. К тому же, передача чистого хэша params (как это было показано выше) приведет к ошибке, т.е. теперь Rails приложения невосприимчивы к уязвимостям массового назначиния по умолчанию.params наличие атрибута :user и мы хотим позволить наличие атрибутов name, email, password и password_confirmation (но только их). Мы можем достигнуть этого следующим образом:params.require(:user).permit(:name, :email, :password, :password_confirmation)
params содержащего только разрешенные атрибуты (при этом будет вызвана ошибка если отсутствует атрибут :user).user_params который возвращает соответствующий инициализационных хэш используемый вместо params[:user]:@user = User.new(user_params)
user_params будет использоваться только внутри контроллера Users и нет никакой надобности открывать к нему доступ внешним пользователям через веб, мы сделаем его приватным используя ключевое слово private, как это показано в Листинге 7.22. (Мы более детально обсудим private в Разделе 8.2.1.)class UsersController < ApplicationController
.
.
.
def create
@user = User.new(user_params)
if @user.save
# Handle a successful save.
else
render 'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
$ bundle exec rspec spec/requests/user_pages_spec.rb \
> -e "signup with invalid information"
Сообщения об ошибках при регистрации
$ 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.2) содержит массив сообщений об ошибках.@user. Для отображения сообщения в браузере, мы рендерим партиал error_messages на странице user new как это показано в Листинге 7.23. (Написание теста на сообщения об ошибках это хорошая идея и мы оставим эти тесты в качестве упражнения; см. Раздел 7.6.) Стоит отметить что этот партиал сообщений об ошибках лишь первая попытка; конечная версия представлена в Разделе 10.3.2.<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<div class="row">
<div class="span6 offset3">
<%= form_for(@user) do |f| %>
<%= render 'shared/error_messages' %>
.
.
.
<% end %>
</div>
</div>
render партиал ’shared/error_messages’; что отражает общую конвенцию Rails, которая предписывает размещение частичных шаблонов которые мы планируем использовать во многих контроллерах в специально отведенном каталоге shared/. Это означает, что мы должны создать этот новый каталог вместе с файлом партиала _error_messages.html.erb. Сам партиал представлен в Листинге 7.24.<% if @user.errors.any? %>
<div id="error_explanation">
<div class="alert alert-error">
The form contains <%= pluralize(@user.errors.count, "error") %>.
</div>
<ul>
<% @user.errors.full_messages.each do |msg| %>
<li>* <%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
>> user.errors.count
=> 2
any?, который (совместно с empty?) является одним из пары взаимодополняющих методов:>> user.errors.empty?
=> false
>> user.errors.any?
=> true
empty?, который мы впервые увидели в Разделе 4.2.3 в контексте строк, также работает на объекте ошибок Rails, возваращая true для пустого объекта и false в противном случае. Метод any? это просто противоположность empty?, возвращающая true если существует какой-нибудь элемент и false в противном случае. (Кстати, все эти методы — count, empty? и any? — работают и на массивах Ruby. Мы найдем хорошее применение этому факту в Разделе 10.2.)pluralize в Rails API>> include ActionView::Helpers::TextHelper
>> 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") %>
"0 errors", "1 error", "2 errors" и т.д., в зависимости от количества ошибок, тем самым позволяя нам избежать грамматически неверных фраз вроде "1 errors"..
.
.
/* forms */
.
.
.
#error_explanation {
color: #f00;
ul {
list-style: none;
margin: 0 0 18px 0;
}
}
.field_with_errors {
@extend .control-group;
@extend .error;
}
(рис 7.17) Сбой регистрации с сообщениями об ошибках.
(рис 7.18) Результат посещения /signup и клика по “Create my account”.
Успешная регистрация
(рис 7.19) Набросок успешной регистрации.
Завершенная форма регистрации
$ bundle exec rspec spec/requests/user_pages_spec.rb \
> -e "signup with valid information"
create. Вместо этого мы должны перенаправить на другую страницу и будет вполне логично если этой страницей будет профиль вновь созданного пользователя. Тестирование того что рендерится правильная страница оставлено в качестве упражнения (Раздел 7.6); код представлен в Листинге 7.26.class UsersController < ApplicationController
.
.
.
def create
@user = User.new(user_params)
if @user.save
redirect_to @user
else
render 'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
user_path в редиректе, написав просто redirect_to @user для перенаправления на страницу показывающую пользователя.$ bundle exec rspec spec/
Флэш
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.
<!DOCTYPE html>
<html>
.
.
.
<body>
<%= render 'layouts/header' %>
<div class="container">
<% flash.each do |key, value| %>
<div class="alert alert-<%= key %>"><%= value %></div>
<% end %>
<%= yield %>
<%= render 'layouts/footer' %>
<%= debug(params) if Rails.env.development? %>
</div>
.
.
.
</body>
</html>
div тег, с CSS классом, указывающим на тип сообщения. Например, если flash[:success] = "Welcome to the Sample App!", то код<% flash.each do |key, value| %>
<div class="alert alert-<%= key %>"><%= value %></div>
<% end %>
<div class="alert alert-success">Welcome to the Sample App!</div>
flash.now, но мы отложим эти тонкости до тех пор пока они нам не понадобятся.flash[:success] приветственное сообщение в действии create как это показано в Листинге 7.28.class UsersController < ApplicationController
.
.
.
def create
@user = User.new(user_params)
if @user.save
flash[:success] = "Welcome to the Sample App!"
redirect_to @user
else
render 'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
Первая регистрация
(рис 7.20) Результирующая страница успешной регистрации с флэш сообщением.
(рис 7.21) Профиль пользователя с исчезнувшим после перезагрузки страницы флэш сообщением.
$ rails console
>> User.find_by(email: "example@railstutorial.org")
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org",
created_at: "2013-03-12 05:51:34", updated_at: "2013-03-12 05:51:34",
password_digest: "$2a$10$A58/j7wwh3aAffGkMAO9Q.jjh3jshd.6akhDKtchAz/R...">
Развертывание приложения на сервере с SSL
$ git add .
$ git commit -m "Finish user signup"
$ git checkout master
$ git merge sign-up
SampleApp::Application.configure do
.
.
.
# Force all access to the app over SSL, use Strict-Transport-Security,
# and use secure cookies.
config.force_ssl = true
.
.
.
end
$ git commit -a -m "Add SSL in production"
$ git push heroku
$ heroku run rake db:migrate
$ heroku open
$ heroku logs
(рис 7.22) Рабочая страница регистрации в Web.
Заключение
Упражнения
gravatar_for, определенному в Разделе 7.1.4 принимать опциональный параметр size, позволив код вроде gravatar_for user, size: 40 в представлении.create. Листинг 7.32 использует метов have_selector представленный в упражнениях Главы 5 (Раздел 5.6). В данном случае мы используем have_selector для выбора определенных CSS классов вместе с конкретными HTML тегами.content_tag, тоже работает.module UsersHelper
# Returns the Gravatar (http://gravatar.com/) for the given user.
def gravatar_for(user, options = { size: 50 })
gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
size = options[:size]
gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
image_tag(gravatar_url, alt: user.name, class: "gravatar")
end
end
.
.
.
describe "signup" do
before { visit signup_path }
.
.
.
describe "with invalid information" do
.
.
.
describe "after submission" do
before { click_button submit }
it { should have_title('Sign up') }
it { should have_content('error') }
end
.
.
.
.
.
.
describe "with valid information" do
.
.
.
describe "after saving the user" do
before { click_button submit }
let(:user) { User.find_by(email: 'user@example.com') }
it { should have_title(user.name) }
it { should have_selector('div.alert.alert-success', text: 'Welcome') }
end
.
.
.
<!DOCTYPE html>
<html>
.
.
.
<% flash.each do |key, value| %>
<%= content_tag(:div, value, class: "alert alert-#{key}") %>
<% end %>
.
.
.
</html>
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.