Здравствуйте, записался на курс. При этом ставил галочку на "обучаться с тьютором". На email пришло письмо, о том, что записался на самостоятельное изучение курса. Как выбрать тьютора? |
Регистрация
Изображение Gravatar и боковая панель
Определив базовую страницу пользователя в предыдущем разделе, теперь мы немного улучшим ее, добавив изображение пользователя и начальную реализацию боковой панели пользователя. При создании страницы мы сфокусируемся на ее внешнем виде, не особо заботясь о ее структуре, это означает, что (по крайней мере пока) мы не будем писать тесты. Когда мы подойдем к аспектам представления, более подверженным ошибкам, таким как пагинация (Раздел 9.3.3), мы продолжим разработку через тестирование.
Мы начнем с добавления "глобально распознаваемого аватара" (также известного как Граватар) к профилю пользователя.6В индуизме, аватар это форма проявление божества в человеке или животном. В более широком смысле термин avatar обычно используется для обозначения персонализации пользователя, особенно в виртуальной среде. Но вы уже видели фильм, и почти наверняка знаете об этом. Автором Граватар является Tom Preston-Werner (сооснователь GitHub), впоследствии его (Граватар) приобрела компания Automattic (создатели WordPress), это бесплатный сервис который позволяет пользователям загружать изображения и связывать их с подконтрольными им адресами электронной почты. Gravatar это удобный способ добавить изображения пользователей не связываясь с проблемами загрузки изображений, их обрезкой и хранением; все что нам нужно, это создать правильный URL Gravatar изображения используя адрес электронной почты пользователя и соответствующее изображение Gravatar появится автоматич ески.7Если вашему приложению потребуется обработка кастомных изображений или загрузки других файлов, я рекомендую использовать для этих целей гем Paperclip.
Мы планируем определить вспомогательную функцию gravatar_for которая будет возвращать изображение Граватар для данного пользователя, как это показано в Листинге 7.12.
<% provide(:title, @user.name) %> <h1> <%= gravatar_for @user %> <%= @user.name %> </h1>Листинг 7.12. Представление показывающее пользователя с именем и Граватаром. app/views/users/show.html.erb
В этой точке вы можете проверить что набор тестов не проходит:
$ 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. Определение вспомогательного метода gravatar_for. app/helpers/users_helper.rb
Код в Листинге 7.13 возвращает тег img для Граватара с классом "gravatar" и альтернативным текстом эквивалентным имени пользователя (что особенно удобно для браузеров используемых слабовидящими людьми которые работают с помощью считывателей экрана). Вы можете проверить что теперь набор тестов проходит:
$ bundle exec rspec spec/
Страница профиля представленная на рис. 7.7, показывающая дефолтное изображение Граватара, выглядит подобным образом т.к. user@example.com это невалидный email адрес (домен example.com зарезервирован для примеров (examples).
Для того чтобы отобразить кастомный Граватар в нашем приложении, мы будем использовать 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
Здесь мы назначили пользователю адрес электронной почты example@railstutorial.org, который я связал с логотипом Rails Tutorial, как это видно на рис. 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>Листинг 7.14. Добавление боковой панели к представлению user show. app/views/users/show.html.erb
Имея соответствующие HTML элементы и CSS классы, мы можем отстилить страницу профиля (включая боковую панель и Gravatar) с SCSS показанным в Листинге 7.15. (Обратите внимание на наследование CSS правил, которое работает только благодаря препроцессору Sass, используемому файлопроводом.) Получившаяся страница показана на рис. 7.9.
. . . /* 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; }Листинг 7.15. SCSS для стилизации страницы показывающей пользователя, включая боковую панель. app/assets/stylesheets/custom.css.scss
Форма регистрации
Теперь, когда у нас есть рабочая (хоть и незавершенная) страница профиля пользователя, мы готовы приступить к созданию формы для регистрации на нашем сайте. Мы видели на рис. 5.9 (дублированном на рис. 7.10) что страница регистрации в настоящий момент пуста и совершенно бесполезна для регистрации новых пользователей. Целью этого раздела является разработка формы регистрации из рис. 7.11.
Поскольку мы говорим о создании пользователей через веб-интерфейс, давайте удалим пользователя созданного через консоль в Разделе 6.3.5. Простейшим способом сделать это является очистка базы данных с помощью Rake-задачи db:reset:
$ bundle exec rake db:reset
После очистки базы данных на некоторых системах также необходимо заново подготовить тестовую базу данных:
$ bundle exec rake test:prepare
Наконец, на некоторых системах вам может потребоваться перезагрузка сервера для того чтобы изменения вступили в силу.8Странно, не правда ли? Я тоже этого не понимаю.
Тесты для регистрации пользователя
До появления мощных веб-фреймворков с полным набором тестировочных средств, тестирование часто доставляло много хлопот. Например, для тестирования страницы регистрации вручную, нам бы пришлось посещать страницу в браузере, затем заполнять форму различными валидными и невалидными данными и проверять что в каждом случае поведение приложения соответствует ожидаемому. Кроме того, нам бы пришлось проделывать это каждый раз при изменении приложения. С помощью RSpec и Capybara мы сможем написать выразительные тесты, которые автоматизируют задачи, которые раньше делались вручную.
Мы уже видели как Capybara поддерживает интуитивно понятный синтаксис веб-навигации. До сих пор мы в основном использовали visit для посещения конкретных страниц, но Capybara может гораздо больше, включая заполнение полей, вроде тех что мы видели на рис. 7.11 и кликанья по кнопке. Синтаксис выглядит примерно так:
visit signup_path fill_in "Name", with: "Example User" . . . click_button "Create my account"
Сейчас нашей целью является написание тестов для правильного поведения при предоставлении невалидной и валидной регистрационной информации. Поскольку эти тесты довольно продвинутые, мы будем писать их постепенно. Если вам интересно посмотреть как они работают (включая файл в который они должны быть помещены), вы можете перейти непосредственно к Листингу 7.16.
Наша первая задача это тестирование провальной отправки регистрационной формы, и мы можем симулировать отправку невалидных данных посетив страницу и кликнув по кнопке с помощью 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"
Эти тесты проверяют что поведение приложения после клика по кнопке "Create my account" соответствует нашим ожиданиям: создается новый пользователь если информация валидна и не создается новый пользователь в случае когда она невалидна. Для того чтобы сделать это мы проверяем количество пользователей и наши тесты будут использовать метод count, доступный для каждого класса Active Record, включая User:
$ rails console >> User.count => 0
Здесь User.count является 0 поскольку мы очистили базу данных в начале этого раздела.
При отправке невалидных данных, мы ожидаем что количество пользователей не будет изменено; при отправке валидных данных мы ожидаем что оно изменится на 1. Мы можем выразить это в RSpec скомбинировав метод 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)
которая читается как естественный язык и является намного более компактной (eq это RSpec метод для тестирования на равенство).
Случай с валидными данными аналогичен, но вместо проверки того, что количество пользователей не изменилось, мы проверяем что клик по кнопке изменяет их количество на 1:
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Листинг 7.16. Хорошие базовые тесты для регистрации пользователей. spec/requests/user_pages_spec.rb
Мы будем добавлять тесты по мере надобности в последующих разделах, но базовые тесты в Листинге 7.16 уже покрывают внушительный объем функционала. Для того чтобы получить их прохождение, мы должны создать страницу регистрации с необходимым минимумом правильных элементов, предопределить направление пользователя в правильное место после отправки формы и создание нового пользователя в базе данных только в случае если полученная информация валидна.
Конечно же, в этой точке тесты должны быть провальными:
$ bundle exec rspec spec/