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

Регистрация

Ресурс Users

В конце Главы 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).

Страница ошибки для /users/1.

Рис. 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. Добавление ресурса Users в файл маршрутов. config/routes.rb

Вы возможно заметили, что Листинг 7.3 удаляет строку

get "users/new"

последний раз замеченную в Листинге 5.35. Это связано с тем, что resources :users не просто добавляет работающий /users/1 URL; эта строка также обеспечивает наш пример приложения всеми действиями, необходимыми для RESTful (полностью REST) ресурса Users,5Это означает что маршрутизация работает, но соответствующие страницы необязательно должны работать в этой точке. Например, /users/1/edit правильно направляется к edit действию контроллера Users, но поскольку действие edit пока не существует, обращение к этому URL вернет ошибку. наряду с большим количеством именованных маршрутов (Раздел 5.3.3) для генерации URL пользователя. Получившееся соответствие URL, действий и именованных маршрутов показано в таблица 7.1. (Сравните с таблица 2.2.) В течение следующих трех глав, мы охватим остальные записи в таблица 7.1 поскольку мы заполним все действия, необходимые, для того, чтобы сделать Users RESTful ресурсом.

Таблица 7.1. RESTful маршруты, обеспеченные ресурсом Users в Листинге 7.3.
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.5). Для того чтобы исправить это, мы начнем с минималистичной версии страницы профиля, которую мы откормим в Разделе 7.1.4.

URL /users/1 с маршрутом, но без страницы.

Рис. 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 %>
Листинг 7.4. Представление-заглушка для отображения информации пользователя. app/views/users/show.html.erb

Это представление использует 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
Листинг 7.5. Контроллер Users с действием show action. app/controllers/users_controller.rb

Здесь мы использовали 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.

Страница показывающая пользователя на /users/1 после добавления ресурса Users.

Рис. 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
Листинг 7.6. Резюме начального спека страниц пользователя. spec/requests/user_pages_spec.rb

Для того чтобы протестировать страницу показывающую пользователя, нам необходим объект модели 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_path ( таблица 7.1) для генерации пути к странице показывающей данного пользователя. Затем мы тестируем что и страница и тайтл содержат имя пользователя.

Для того чтобы создать необходимый объект модели 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
.
.
.
Листинг 7.7. Добавление Factory Girl в Gemfile.

Затем устанавливаем как обычно:

$ 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
Листинг 7.8. Фабрика для симуляции объектов модели User. spec/factories.rb

Передавая символ :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
Листинг 7.9. Тест для страницы показывающей пользователя. spec/requests/user_pages_spec.rb

В этой точке вам необходимо проверить что набор тестов в красном:

$ bundle exec rspec spec/

Мы можем получить зеленые тесты с кодом из Листинга 7.10.

<% provide(:title, @user.name) %>
<h1><%= @user.name %></h1>
Листинг 7.10. Добавление заголовка браузера и заголовка первого уровня для страницы профиля пользователя. app/views/users/show.html.erb

Повторный запуск тестов должен подтвердить что тесты из Листинга 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
Листинг 7.11. Переопределение фактора стоимости Bсrypt в тестовом окружении. config/environments/test.rb
Вадим Обозин
Вадим Обозин

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