Опубликован: 27.01.2016 | Уровень: для всех | Доступ: платный
Лекция 7:

Регистрация

Изображение 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).

Страница профиля пользователя /users/1 с дефолтным Граватаром.

Рис. 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

Здесь мы назначили пользователю адрес электронной почты example@railstutorial.org, который я связал с логотипом Rails Tutorial, как это видно на рис. 7.8.

Страница показывающая пользователя с кастомным Граватаром.

Рис. 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
Страница показывающая пользователя /users/1 с боковой панелью и CSS.

Рис. 7.9. Страница показывающая пользователя /users/1 с боковой панелью и CSS.

Форма регистрации

Теперь, когда у нас есть рабочая (хоть и незавершенная) страница профиля пользователя, мы готовы приступить к созданию формы для регистрации на нашем сайте. Мы видели на рис. 5.9 (дублированном на рис. 7.10) что страница регистрации в настоящий момент пуста и совершенно бесполезна для регистрации новых пользователей. Целью этого раздела является разработка формы регистрации из рис. 7.11.

Текущее состояние страницы регистрации /signup.

Рис. 7.10. Текущее состояние страницы регистрации /signup.
Набросок страницы регистрации пользователей.

Рис. 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/
Вадим Обозин
Вадим Обозин

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

Акбар Ахвердов
Акбар Ахвердов
Россия, г. Москва
Артём Зайцев
Артём Зайцев
Украина, ДНР