Ruby on Rails Tutorial

Изучение Rails на Примерах

Майкл Хартл

Содержание

  1. Предисловие к русскому изданию
  2. Глава 1 От нуля к развертыванию
    1. 1.1 Введение
      1. 1.1.1 Комментарии для разных читателей
      2. 1.1.2 “Масштабирование” Rails
      3. 1.1.3 Соглашения в этой книге
    2. 1.2 За работу
      1. 1.2.1 Среда разработки
        1. Интегрированные Среды Разработки
        2. Текстовые редакторы и инструменты командной строки
        3. Браузеры
        4. Примечание об инструментах
      2. 1.2.2 Ruby, RubyGems, Rails и Git
        1. Rails Installer (Windows)
        2. Установка Git
        3. Установка Ruby
        4. Установка RubyGems
        5. Установка Rails
      3. 1.2.3 Первое приложение
      4. 1.2.4 Bundler
      5. 1.2.5 rails server
      6. 1.2.6 Модель-представление-контроллер (MVC)
    3. 1.3 Управление версиями с Git
      1. 1.3.1 Установка и настройка
        1. Первоначальная настройка системы
        2. Первоначальная настройка репозитория
      2. 1.3.2 Добавление и фиксация
      3. 1.3.3 Что хорошего Git делает для вас?
      4. 1.3.4 GitHub
      5. 1.3.5 Ветвление, редактирование, фиксация, объединение
        1. Ветвление
        2. Редактирование
        3. Фиксация
        4. Объединение
        5. Отправка
    4. 1.4 Развертывание
      1. 1.4.1 Установка Heroku
      2. 1.4.2 Развертывание на Heroku, шаг первый
      3. 1.4.3 Развертывание на Heroku, шаг второй
      4. 1.4.4 Команды Heroku
    5. 1.5 Заключение
  3. Глава 2 demo app
    1. 2.1 Планирование приложения
      1. 2.1.1 Моделирование пользователей
      2. 2.1.2 Моделирование микросообщений
    2. 2.2 Ресурс Users
      1. 2.2.1 Обзор пользователя
      2. 2.2.2 MVC в действии
      3. 2.2.3 Недостатки данного ресурса Users
    3. 2.3 Ресурс Microposts
      1. 2.3.1 Микрообзор микросообщений
      2. 2.3.2 Помещение micro в микросообщения
      3. 2.3.3 Пользователь has_many микросообщений
      4. 2.3.4 Иерархия наследования
      5. 2.3.5 Развертывание демонстрационного приложения
    4. 2.4 Заключение
  4. Глава 3 В основном статические страницы
    1. 3.1 Статические страницы
    2. 3.2 Наши первые тесты
      1. 3.2.1 Разработка через тестирование
      2. 3.2.2 Добавление страницы
        1. Красный
        2. Зеленый
        3. Refactor (реорганизация)
    3. 3.3 Немного динамические страницы
      1. 3.3.1 Тестирование изменения заголовка
      2. 3.3.2 Прохождение тестов заголовка
      3. 3.3.3 Встроенный Ruby
      4. 3.3.4 Устранение дублирования шаблонами
    4. 3.4 Заключение
    5. 3.5 Упражнения
    6. 3.6 Продвинутые настройки
      1. 3.6.1 Избавляемся от bundle exec
        1. RVM Bundler интеграция
        2. binstubs
      2. 3.6.2 Автоматизируем тесты с Guard
      3. 3.6.3 Ускоряем тесты с помощью Spork
        1. Guard и Spork
      4. 3.6.4 Запускаем тесты внутри Sublime Text
  5. Глава 4 Rails-приправленный Ruby
    1. 4.1 Причины
    2. 4.2 Строки и методы
      1. 4.2.1 Комментарии
      2. 4.2.2 Строки
        1. Вывод на экран
        2. Строки в одинарных кавычках
      3. 4.2.3 Объекты и передача сообщений
      4. 4.2.4 Определение методов
      5. 4.2.5 Возвращение к title хелперу
    3. 4.3 Другие структуры данных
      1. 4.3.1 Массивы и диапазоны
      2. 4.3.2 Блоки
      3. 4.3.3 Хэши и символы
      4. 4.3.4 Вновь CSS
    4. 4.4 Ruby классы
      1. 4.4.1 Конструкторы
      2. 4.4.2 Наследование классов
      3. 4.4.3 Изменение встроенных классов
      4. 4.4.4 Класс контроллер
      5. 4.4.5 Класс User
    5. 4.5 Заключение
    6. 4.6 Упражнения
  6. Глава 5 Заполнение шаблона
    1. 5.1 Добавление некоторых структур
      1. 5.1.1 Навигация по сайту
      2. 5.1.2 Bootstrap и кастомные CSS
      3. 5.1.3 Частичные шаблоны (partials)
    2. 5.2 Sass и файлопровод (asset pipeline)
      1. 5.2.1 Файлопровод
        1. Директории ассетов
        2. Файлы-манифесты
        3. Препроцессоры
        4. Производительность в продакшен
      2. 5.2.2 Синтаксически обалденные таблицы стилей
        1. Вложение
        2. Переменные
    3. 5.3 Ссылки в шаблоне
      1. 5.3.1 Тестирование маршрутов
      2. 5.3.2 Rails маршруты
      3. 5.3.3 Именованные маршруты
      4. 5.3.4 Приятный RSpec
    4. 5.4 Регистрация пользователей: Первый шаг
      1. 5.4.1 Контроллер Users
      2. 5.4.2 URL для регистрации
    5. 5.5 Заключение
    6. 5.6 Упражнения
  7. Глава 6 Моделирование пользователей
    1. 6.1 Модель User
      1. 6.1.1 Миграции базы данных
      2. 6.1.2 Файл модели
      3. 6.1.3 Создание объектов user
      4. 6.1.4 Поиск объектов user
      5. 6.1.5 Обновление объектов user
    2. 6.2 Валидации User
      1. 6.2.1 Начальные тесты для пользователей
      2. 6.2.2 Валидация наличия
      3. 6.2.3 Валидация длины
      4. 6.2.4 Валидация формата
      5. 6.2.5 Валидация уникальности
        1. Предостережение уникальности
    3. 6.3 Добавление безопасного пароля
      1. 6.3.1 Зашифрованный пароль
      2. 6.3.2 Пароль и подтверждение
      3. 6.3.3 Аутентификация пользователя
      4. 6.3.4 У пользователя есть безопасный пароль
      5. 6.3.5 Создание пользователя
    4. 6.4 Заключение
    5. 6.5 Упражнения
  8. Chapter 7 Регистрация
    1. 7.1 Демонстрация пользователей
      1. 7.1.1 Отладка и окружения Rails
      2. 7.1.2 A Ресурс Users
      3. 7.1.3 Тестирование страницы показывающей пользователя (с фабриками)
      4. 7.1.4 Изображение Gravatar и боковая панель
    2. 7.2 Форма регистрации
      1. 7.2.1 Тесты для регистрации пользователя
      2. 7.2.2 Применение form_for
      3. 7.2.3 HTML формы
    3. 7.3 Провальная регистрация
      1. 7.3.1 Рабочая форма
      2. 7.3.2 Строгие параметры
      3. 7.3.3 Сообщения об ошибках при регистрации
    4. 7.4 Успешная регистрация
      1. 7.4.1 Завершенная форма регистрации
      2. 7.4.2 Флэш
      3. 7.4.3 Первая регистрация
      4. 7.4.4 Развертывание приложения на сервере с SSL
    5. 7.5 Заключение
    6. 7.6 Упражнения
  9. Глава 8 Войти, выйти
    1. 8.1 Сессии и провальный вход
      1. 8.1.1 Sessions контроллер
      2. 8.1.2 Тестирование входа
      3. 8.1.3 Форма для входа
      4. 8.1.4 Обзор отправки формы
      5. 8.1.5 Рендеринг с флэш сообщением
    2. 8.2 Успешный вход
      1. 8.2.1 Запомнить меня
      2. 8.2.2 Рабочий метод sign_in
      3. 8.2.3 Текущий пользователь
      4. 8.2.4 Изменение ссылок шаблона
      5. 8.2.5 Вход после регистрации
      6. 8.2.6 Выход
    3. 8.3 Введение в Cucumber (опционально)
      1. 8.3.1 Установка и настройка
      2. 8.3.2 Фичи и шаги
      3. 8.3.3 Контрапункт: кастомные проверки RSpec
    4. 8.4 Заключение
    5. 8.5 Упражнения
  10. Глава 9 Обновление, демонстрация и удаление пользователей
    1. 9.1 Обновление пользователей
      1. 9.1.1 Форма для редактирования
      2. 9.1.2 Провальное редактирование
      3. 9.1.3 Успешное редактирование
    2. 9.2 Авторизация
      1. 9.2.1 Требование входа пользователей
      2. 9.2.2 Требование правильного пользователя
      3. 9.2.3 Дружелюбная переадресация
    3. 9.3 Отображение всех пользователей
      1. 9.3.1 Список пользователей
      2. 9.3.2 Образцы пользователей
      3. 9.3.3 Пагинация
      4. 9.3.4 Частичный рефакторинг
    4. 9.4 Уничтожение пользователей
      1. 9.4.1 Административные пользователи
        1. Возвращение к строгим параметрам
      2. 9.4.2 Destroy действие
    5. 9.5 Заключение
    6. 9.6 Упражнения
  11. Глава 10 Микросообщения пользователей
    1. 10.1 Модель Micropost
      1. 10.1.1 Базовая модель
      2. 10.1.2 Первая валидация
      3. 10.1.3 Ассоциации Пользователь/Микросообщения
      4. 10.1.4 Улучшение микросообщений
        1. Дефолтное пространство (scope)
        2. Dependent: destroy
      5. 10.1.5 Валидации контента
    2. 10.2 Просмотр микросообщений
      1. 10.2.1 Дополнение страницы показывающей пользователя
      2. 10.2.2 Образцы микросообщений
    3. 10.3 Манипулирование микросообщениями
      1. 10.3.1 Контроль доступа
      2. 10.3.2 Создание микросообщений
      3. 10.3.3 Предварительная реализация потока сообщений
      4. 10.3.4 Уничтожение микросообщений
    4. 10.4 Заключение
    5. 10.5 Упражнения
  12. Глава 11 Слежение за сообщениями пользователей
    1. 11.1 Модель Relationship
      1. 11.1.1 Проблема с моделью данных (и ее решение)
      2. 11.1.2 Ассоциации пользователь/взаимоотношение
      3. 11.1.3 Валидации
      4. 11.1.4 Читаемые пользователи
      5. 11.1.5 Читатели пользователя
    2. 11.2 Веб-интерфейс для читаемых пользователей
      1. 11.2.1 Образцы данных
      2. 11.2.2 Статистика и форма для слежения за сообщениями пользователя
      3. 11.2.3 Страницы с читаемыми и читателями
      4. 11.2.4 Стандартный способ реализации кнопки "читать" (follow)
      5. 11.2.5 Реализация кнопки "читать" (follow) с Ajax
    3. 11.3 Поток сообщений
      1. 11.3.1 Мотивация и стратегия
      2. 11.3.2 Первая реализация потока сообщений
      3. 11.3.3 Подзапросы
      4. 11.3.4 Новый поток сообщений
    4. 11.4 Заключение
      1. 11.4.1 Расширения к примеру приложения
        1. Реплики
        2. Обмен сообщениями
        3. Уведомления о новых читателях
        4. Напоминание пароля
        5. Подтверждение регистрации
        6. RSS канал
        7. REST API
        8. Поиск
      2. 11.4.2 Руководство по дальнейшим ресурсам
    5. 11.5 Упражнения

Предисловие

Моя компания (CD Baby) была одной из первых громко перешедших на Ruby on Rails, а затем еще громче вернувшейся обратно на PHP (Google расскажет вам об этой драме). Эту книгу, написанную Майклом Хартлом так высоко рекомендовали, что я должен был попробовать её, и Ruby on Rails Tutorial это всё, что я использовал, чтобы вернуться к Rails.

Хотя я уже прошел через много книг по Rails, это одна из немногих, что, наконец, зацепила меня. Было много написано книг типа «Путь Rails» — после которых я чувствовал себя неестественно, но после этой книги я наконец почувствовал себя естественно. Это также единственная книга по Rails, которая соблюдает методику «разработка через тестирование» на всем своем протяжении, этот подход строго рекомендуется специалистами, но он никогда не был так чётко продемонстрирован ранее. Наконец, Git, GitHub и Heroku присутствуют в демо-примерах, автор действительно дает вам почувствовать, что он хотел сделать реальный проект. Учебный код примеров не изолирован.

Линейное повествование — отличный формат. Лично я прошел Rails Tutorial в течении трёх долгих дней, делая все примеры и задачи в конце каждой главы. Делайте всё от начала до конца, не прыгая, и вы получите максимальную пользу.

Наслаждайтесь!

Derek Sivers (sivers.org)
Основатель CD Baby

Благодарности

Ruby On Rails Учебник во многом обязан моей предыдущей книге по Rails, RailsSpace и, следовательно, моему соавтору Aurelius Prochazka. Я хотел бы поблагодарить Aure как за работу, которую он проделал над прошлой книгой, так и за поддержку этой. Я также хотел бы поблагодарить Debra Williams Cauley, редактора обеих книг RailsSpace и Rails Tutorial; до тех пор, пока она не прекратит брать меня на бейсбол, я буду продолжать писать книги для нее.

Я хотел бы поблагодарить огромное количество Рубистов учивших и вдохновлявших меня на протяжении многих лет: David Heinemeier Hansson, Yehuda Katz, Carl Lerche, Jeremy Kemper, Xavier Noria, Ryan Bates, Geoffrey Grosenbach, Peter Cooper, Matt Aimonetti, Gregg Pollack, Wayne E. Seguin, Amy Hoy, Dave Chelimsky, Pat Maddox, Tom Preston-Werner, Chris Wanstrath, Chad Fowler, Josh Susser, Obie Fernandez, Ian McFarland, Steven Bristol, Pratik Naik, Sarah Mei, Sarah Allen, Wolfram Arnold, Alex Chaffee, Giles Bowkett, Evan Dorn, Long Nguyen, James Lindenbaum, Adam Wiggins, Tikhon Bernstam, Ron Evans, Wyatt Greene, Miles Forrest, хороших людей из Pivotal Labs, команду Heroku, thoughtbot ребят, и команду GitHub. Наконец, многих, многих читателей - слишком много чтобы перечислять их здесь - внёсших большое количество предложений по улучшению и сообщивших об ошибках во время написания этой книги, и я с благодарностью признаю их помощь в написании ее настолько хорошей, насколько это было возможно.

Об авторе

Майкл Хартл – автор Ruby on Rails Tutorial, лидирующего введения в веб разработку на Ruby on Rails. Его предыдущий опыт включает в себя написание и разработку RailsSpace - чрезвычайно устаревшего учебника по Rails и разработку Insoshi - некогда популярной, а ныне устаревшей платформы для социальных сетей написанной на Ruby on Rails. В 2011, Майкл получил Ruby Hero Award за его вклад в Ruby сообщество. Он закончил Harvard College, имеет степень Кандидата Физических Наук присвоенную в Caltech и является выпускником предпринимательских курсов Y Combinator.

Копирайт и лицензия

Ruby on Rails Tutorial: Learn Web Development with Rails. Copyright © 2012 by Michael Hartl. Весь исходный код в Ruby on Rails Tutorial доступен под MIT License и Beerware License.

Лицензия MIT

Copyright (c) 2013 Michael Hartl

Данная лицензия разрешает лицам, получившим копию данного программного
обеспечения и сопутствующей документации (в дальнейшем именуемыми
«Программное Обеспечение»), безвозмездно использовать Программное
Обеспечение без ограничений, включая неограниченное право на использование,
копирование, изменение, добавление, публикацию, распространение,
сублицензирование и/или продажу копий Программного Обеспечения, также
как и лицам, которым предоставляется данное Программное Обеспечение,
при соблюдении следующих условий:

Указанное выше уведомление об авторском праве и данные условия должны быть
включены во все копии или значимые части данного Программного Обеспечения.

ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ», БЕЗ КАКИХ-ЛИБО
ГАРАНТИЙ, ЯВНО ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ
ГАРАНТИЯМИ ТОВАРНОЙ ПРИГОДНОСТИ, СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ НАЗНАЧЕНИЮ И
ОТСУТСТВИЯ НАРУШЕНИЙ ПРАВ. НИ В КАКОМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ
НЕСУТ ОТВЕТСТВЕННОСТИ ПО ИСКАМ О ВОЗМЕЩЕНИИ УЩЕРБА, УБЫТКОВ ИЛИ ДРУГИХ
ТРЕБОВАНИЙ ПО ДЕЙСТВУЮЩИМ КОНТРАКТАМ, ДЕЛИКТАМ ИЛИ ИНОМУ, ВОЗНИКШИМ ИЗ,
ИМЕЮЩИМ ПРИЧИНОЙ ИЛИ СВЯЗАННЫМ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ ИЛИ ИСПОЛЬЗОВАНИЕМ
ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ ИЛИ ИНЫМИ ДЕЙСТВИЯМИ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ.
/*
 * ----------------------------------------------------------------------------
 * "ПИВНАЯ ЛИЦЕНЗИЯ" (Ревизия 42):
 * Весь код написан Майклом Хартлом. До тех пор пока вы осознаете это,
 * вы можете делать с ним все что захотите. Если мы когда нибудь
 * встретимся, и если это того стоило, вы можете купить мне
 * пиво в ответ.
 * ----------------------------------------------------------------------------
 */

Глава 3 В основном статические страницы

В этой главе мы начнем разработку примера приложения, которое будет служить нам образцом в остальной части этого учебника. Хотя в примере приложения, в конечном счете будут пользователи, сообщения и полная система входа и аутентификации, мы начнем с, казалось бы, ограниченной темы: с создания статических страниц. Несмотря на свою кажущуюся простоту, создание статических страниц весьма поучительное упражнение — идеальное начало для нашего зарождающегося приложения.

Хотя Rails предназначен для создания динамических веб сайтов, поддерживаемых базой данных, он также хорош при создании статических страниц, которые мы могли бы сделать на чистом HTML. В самом деле, использование Rails даже для статических страниц дает неоспоримое преимущество: мы можем легко добавлять лишь небольшое количество динамического содержания. В этой главе мы узнаем, как. По пути мы получим наш первый опыт автоматического тестирования, которое поможет нам быть более уверенными, в том, что наш код является правильным. Кроме того, хороший набор тестов позволит нам реорганизовывать (refactor) наш код с уверенностью, изменяя его форму, не меняя функций.

В этой главе довольно много кода, особенно в Разделе 3.2 и Разделе 3.3 и если вы новичок в Руби, вам не стоит беспокоиться о полном понимании происходящего. Как отмечалось в Разделе 1.1.1, один из возможных вариантов - копипастинг тестов и использование их для проверки кода приложения, не особо заботясь в этой точке о понимании того как они работают. К тому же, Глава 4 раскрывает тему Руби более полно, так что у этих идей будет масса возможностей осесть в вашей голове. Наконец, RSpec тесты будут часто повторяться на протяжении учебника, так что если вы застрянете, я рекомендую прорываться вперед; вы будете поражены тем, как, буквально через несколько глав, изначально непостижимый код совершенно внезапно окажется простым. (Вы также можете пройти RSpec курс в Code School, один из читателей сказал что он нашел в этом курсе ответы на бОльшую часть своих вопросов связанных с RSpec.)

Как и в Главе 2, перед началом работы нам необходимо создать новый Rails-проект, который мы в этот раз назовем sample_app:

$ cd ~/rails_projects
$ rails new sample_app --skip-test-unit
$ cd sample_app

Здесь --skip-test-unit опция команды rails говорит Rails не генерировать директорию test, связанную с дефолтным Test::Unit фреймворком. И это вовсе не потому что мы не хотим писать тесты; наоборот, начиная с Раздела 3.2 мы будем использовать альтернативный тестовый фреймворк RSpec для написания основательного набора тестов.

Как и в Разделе 2.1, нашим следующим шагом будет использование текстового редактора для добавления в Gemfile гемов, необходимых нашему приложению. Также, для примера приложения нам понадобятся два гема, в которых мы ранее не нуждались: гем для RSpec и гем для Rails-специфичной RSpec библиотеки. Код для их включения показан в Листинге 3.1. (Примечание: если вы хотите установить все гемы, необходимые для примера приложения, вам следует использовать код из Листинга 9.47.)

Листинг 3.1. Gemfile для примера приложения.
source 'https://rubygems.org'
ruby '2.0.0'
#ruby-gemset=railstutorial_rails_4_0

gem 'rails', '4.0.2'

group :development, :test do
  gem 'sqlite3', '1.3.8'
  gem 'rspec-rails', '2.13.1'
end

group :test do
  gem 'selenium-webdriver', '2.35.1'
  gem 'capybara', '2.1.0'
end

gem 'sass-rails', '4.0.1'
gem 'uglifier', '2.1.1'
gem 'coffee-rails', '4.0.1'
gem 'jquery-rails', '3.0.4'
gem 'turbolinks', '1.1.1'
gem 'jbuilder', '1.0.2'

group :doc do
  gem 'sdoc', '0.3.20', require: false
end

group :production do
  gem 'pg', '0.15.1'
  gem 'rails_12factor', '0.0.2'
end

Это включает rspec-rails в development mode таким образом мы получаем доступ к RSpec-специфичным генераторам и это включает его в test mode где он нужен для запуска тестов. Нам нет надобности устанавливать сам RSpec, поскольку он является зависимостью гема rspec-rails и поэтому он будет установлен автоматически. Мы также включили гем Capybara, который позволит нам симулировать взаимодействие пользователя с нашим примером приложения используя понятный Англоподобный синтаксис, совместно с Selenium, одной из зависимостей Capybara’.1 Как и в Главе 2, мы также должны включить pg и rails_12factor гемы в production для развертывания на Heroku:

group :production do
  gem 'pg', '0.15.1'
  gem 'rails_12factor', '0.0.2'
end

Heroku не рекомендует использовать разные базы данных для девелопмент и продакшн окружений; но для примера приложения это не будет иметь никакого значения, и SQLite намного проще чем PostgreSQL в установке и конфигурировании. Установку и конфигурирование PostgreSQL на вашей локальной машине остается в качестве амбициозного задания для авантюристов и мазохистов (Раздел 3.5).

Для установки и подключения новых гемов мы запускаем bundle update и bundle install:

$ bundle install --without production
$ bundle update
$ bundle install

Аналогично Разделу 1.4.1 и Главе 2, мы предотвратили установку продакшн гемов используя опцию --without production. Это “запоминаемая опция” и это означает что нам не придется добавлять ее каждый раз при вызове Bundler. Вместо этого мы можем просто писать bundle install и production гемы будут игнорированы автоматически.2

Поскольку наш пример приложения будет храниться в публичном репозитории, важно обновить так называемый secret token используемый в Rails для шифрования переменных связанных с сессией таким образом чтобы он генерировался динамически, а не был жестко прописан в коде (Листинг 3.2). (Код в Листинге 3.2 является довольно сложным для самого начала учебника, но, так как это может оказаться серьезной дырой в безопасности, я чувствую что важно включить его, даже на этой ранней стадии.) Убедитесь что вы используете расширенный файл .gitignore из Листинга 1.7 чтобы ключ .secret не отображался в вашем репозитории.

Листинг 3.2. Динамическая генерация секретного токена.
config/initializers/secret_token.rb
# Be sure to restart your server when you modify this file.

# Your secret key is used for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!

# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
# You can use `rake secret` to generate a secure secret key.

# Make sure your secret_key_base is kept private
# if you're sharing your code publicly.
require 'securerandom'

def secure_token
  token_file = Rails.root.join('.secret')
  if File.exist?(token_file)
    # Use the existing token.
    File.read(token_file).chomp
  else
    # Generate a new token and store it in token_file.
    token = SecureRandom.hex(64)
    File.write(token_file, token)
    token
  end
end

SampleApp::Application.config.secret_key_base = secure_token

Затем нам необходимо сконфигурировать Rails для использования RSpec вместо Test::Unit. Это может быть достигнуто с помощью rails generate rspec:install:

$ rails generate rspec:install

Если ваша система жалуется на отсутствие JavaScript runtime, посетите execjs страницу на GitHub, где приведен список возможных решений. Я особенно рекомендую установку Node.js.

Все что нам осталось — инициализировать Git репозиторий:3

$ git init
$ git add .
$ git commit -m "Initial commit"

Как и с первым приложением, я советую обновить README файл (находится в корневой директории приложения) чтобы сделать его более полезным и описательным, как показано в Листинге 3.3.

Листинг 3.3. Улучшенный README файл для примера приложения.
# Перевод Ruby on Rails Tutorial: пример приложения

Это пример приложения для
[*Ruby on Rails Tutorial*](https://railstutorial.org/)
by [Майкл Хартл](http://michaelhartl.com/).

Затем изменим его расширение на markdown и зафиксируем изменения:

$ git mv README.rdoc README.md
$ git commit -am "Improve the README"

Вы можете вспомнить из Раздела 1.3.5 что мы использовали Git команду git commit -a -m "Message", с флагами для “все изменения” (-a) и сообщение (-m). Как показано во второй команде выше, Git также позволяет свернуть два флага в один: git commit -am "Message".

Поскольку мы будем использовать этот пример приложения на протяжении оставшейся части учебника, хорошей идеей будет создание нового репозитория на GitHub отправка туда нашего кода:

$ git remote add origin https://github.com/<username>/sample_app.git
$ git push -u origin master

В результате выполнения мною этого шага вы можете найти код примера приложения на GitHub (от имени пользователя railstutorial и немного другим названием sample_app_rails_4).4

Конечно, мы также можем задеплоить приложение на Heroku даже на этой ранней стадии:

$ heroku create
$ git push heroku master
$ heroku run rake db:migrate

(Как было отмечено в Разделе 1.4.1, некоторые читатели сообщали о необходимости прекомпиляции ассетов при помощи команды:

# Это должно быть использовано только если вы не можете задеплоить на Heroku.
$ rake assets:precompile
$ git add .
$ git commit -m "Add precompiled assets for Heroku"
$ git push heroku master

Шаг прекомпиляции ассетов по идее не нужен и я не смог воспроизвести ситуацию в которой он был бы необходим, но количество получаемых сообщений об ошибках таково что я вынужден упомянуть здесь о решении.)

Я рекомендую регулярно пушить и деплоить ваш пример приложения в процессе чтения учебника:

$ git push
$ git push heroku
$ heroku run rake db:migrate

Это обеспечит вас бэкапами в удаленном (не локальном) хранилище и позволит отловить продакшен-ошибки настолько быстро, насколько это вообще возможно. Если вы столкнетесь с проблемами на Heroku, вам, вероятно, захочется взглянуть на продакшен логи для того чтобы попытаться диагностировать проблему:

$ heroku logs

Закончив все приготовления, мы наконец-то готовы начать разработку примера приложения.

3.1 Статические страницы

В этом разделе мы предпримем первые шаги в направлении создания динамических страниц создав набор Rails actions (действий) и views(представлений) содержащих (пока) только статический HTML.5 Rails действия объединены внутри контроллеров (C в MVC из Раздела 1.2.6), которые содержат наборы действий связанных общим назначением. Мы получили некоторое представление о контроллерах в Главе 2 и придем к их более глубокому пониманию когда начнем использовать REST архитектуру более плотно (начиная с Главы 6); по сути, контроллер это контейнер для группы (возможно динамических) веб страниц.

Для того чтобы сориентироваться, полезно будет вспомнить структуру директорий Rails из Раздела 1.2.3 (Рис. 1.2). В этом разделе мы в основном будем работать в app/controllers и app/views директориях. (В Разделе 3.2 мы даже добавим свою собственную директорию.) А сейчас я советую вам открыть пример приложения в вашем текстовом редакторе (или IDE) (Box 3.1).

Для того чтобы начать со статическими страницами, вспомните из Раздела 1.3.5 что, при использовании Git, хорошей практикой является делать нашу работу в отдельной, а не в master ветке. Если вы используете Git для контроля версий, вам следует выполнить следующую команду:

$ git checkout -b static-pages

Rails поставляется со скриптом для создания контроллеров называемым generate; имя контроллера это все, что ему (скрипту) нужно для его магической работы. Поскольку мы будем делать контроллер для обработки статических страниц, мы назовем его StaticPages. Мы также планируем сделать действия для страниц Home, Help и About. Скрипт generate принимает в качестве необязательного параметра список действий, так что мы включим два из начальных действий непостредственно в команду вызова скрипта (Листинг 3.4).

Листинг 3.4. Генерация контроллера StaticPages.
$ rails generate controller StaticPages home help --no-test-framework
      create  app/controllers/static_pages_controller.rb
       route  get "static_pages/help"
       route  get "static_pages/home"
      invoke  erb
      create    app/views/static_pages
      create    app/views/static_pages/home.html.erb
      create    app/views/static_pages/help.html.erb
      invoke  helper
      create    app/helpers/static_pages_helper.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/static_pages.js.coffee
      invoke    scss
      create      app/assets/stylesheets/static_pages.css.scss

Обратите внимание на опцию --no-test-framework используемую для подавления генерации дефолтных RSpec тестов, которые мы не планируем использовать. Вместо этого мы создадим тесты вручную в Разделе 3.2. Мы также намеренно не включили about действие в аргументы команды из Листинга 3.4 поэтому мы сможем увидеть как добавить его используя разработку через тестирование, или TDD (Раздел 3.2).

В Листинге 3.4, обратите внимание на то что мы передали имя контроллера в так называемом CamelCase, что привело к созданию файла контроллера, название которого написано в snake_case, таким образом контроллер названный StaticPages привел к файлу названному static_pages_controller.rb. Это просто соглашение и, фактически, использование snake_case в командной строке допустимо: команда

$ rails generate controller static_pages ...

тоже сгенерирует контроллер с названием static_pages_controller.rb. Поскольку Ruby использует CamelCase для имен классов (Раздел 4.4), при упоминании контроллеров я предпочитаю использовать их названия в CamelCase, но это дело вкуса. (Поскольку Рубишные названия файлов обычно используют snake_case, Rails генератор конвертирует CamelCase в snake_case с помощью метода underscore.)

Кстати, на случай если вы когда-либо сделаете ошибку при генерации кода, вам пригодятся знания о том как обратить процесс. См. в Блоке 3.2 несколько техник переделывания в Rails.

Генерация контроллера StaticPages в Листинге 3.4 автоматически обновляет routes файл, из каталога config/routes.rb, который Rails использует для определения соответствий между URL и веб страницами. Tак как это наше первое знакомство с config директорией, полезно взглянуть на нее (Рис. 3.1). Сonfig — директория где Rails хранит файлы, необходимые для конфигурации приложения — отсюда и название.

config_directory_rails_4
Рис. 3.1: Содержимое config директории примера приложения. (полный размер)

Так как мы сгенерировали home и help действия, файл маршрутов уже имеет правила для каждого из них, что видно в Листинге 3.5.

Листинг 3.5. Маршруты для home и help действий контроллера StaticPages.
config/routes.rb
SampleApp::Application.routes.draw do
  get "static_pages/home"
  get "static_pages/help"
  .
  .
  .
end

Здесь правило

get "static_pages/home"

направляет запрос для URL /static_pages/home в home действие контроллера StaticPages. Более того, используя get мы приговариваем маршрут отвечать на GET запрос, который является одним из основных HTTP глаголов поддерживаемых протоколом передачи гипертекста (Блок 3.3). В нашем случае это значит, что когда мы сгенерировали home действие внутри контроллера StaticPages мы автоматически получили страницу по адресу /static_pages/home. Чтобы увидеть результаты, убейте сервер, нажав Ctrl-C, запустите rails server, а затем перейдите на /static_pages/home (Рис. 3.2).

raw_home_view_31
Рис. 3.2: Cырое home представление (/static_pages/home). (полный размер)

Чтобы понять, откуда появилась эта страница, для начала посмотрите на контроллер StaticPages в текстовом редакторе, вы должны увидеть что-то вроде Листинга 3.6. Вы можете отметить, что, в отличие от демо контроллеров Users и Microposts из Главы 2, контроллер StaticPages не использует стандартные REST действия. И это нормально для набора статических страниц: REST архитектура — не панацея для всех проблем.

Листинг 3.6. StaticPages контроллер созданный Листингом 3.4.
app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController

  def home
  end

  def help
  end
end

Здесь мы видим что static_pages_controller.rb определяет class называемый StaticPagesController. Классы это всего лишь удобный способ организации функций (также называемых методами) таких как home и help действия, определяемых с помощью ключевого слова def. Угловая скобка < указывает на то, что StaticPagesController наследует от Rails класса ApplicationController; как мы увидим через мгновение, это означает, что наши страницы оснащены большим количеством Rails-специфичных функций. (Мы узнаем больше об этих двух классах и наследовании в Разделе 4.4.)

В случае со StaticPages контроллером, оба его метода изначально пусты:

def home
end

def help
end

В переводе на простой Ruby, эти методы просто ничего не делают. В Rails, ситуация иная; StaticPagesController это класс Ruby, но благодаря тому, что он наследует от ApplicationController поведение его методов специфично для Rails: при посещении URL /static_pages/home, Rails смотрит в контроллер StaticPages и выполняет код в home действии, а затем рендерит view (представление) (V в MVC из Раздела 1.2.6) соответствующее данному действию. В данном случае home действие пустое, поэтому единственное что происходит при посещении /static_pages/home - рендеринг (отображение) представления. Итак, как же выглядит представление, и как мы можем его найти?

Если вы еще раз посмотрите на вывод в Листинге 3.4, вы можете найти соответствие между действиями и представлениями: действие, такое как home имеет соответствующее представление home.html.erb. Мы узнаем в Разделе 3.3, что означает .erb часть расширения; .html часть расширения подсказывает, что представление, в основном, выглядит как HTML (Листинг 3.7).

Листинг 3.7. Сгенерированное представление для Home страницы.
app/views/static_pages/home.html.erb
<h1>StaticPages#home</h1>
<p>Find me in app/views/static_pages/home.html.erb</p>

Представление для help действия аналогично (Listing 3.8).

Листинг 3.8. Сгенерированное представление для страницы Help.
app/views/static_pages/help.html.erb
<h1>StaticPages#help</h1>
<p>Find me in app/views/static_pages/help.html.erb</p>

Оба эти представления — лишь заглушки: у них есть заголовок (внутри h1 тега) и абзац (p тег) с указанием полного пути к соответствующему файлу. Мы добавим немного (очень немного) динамического контента, начиная с Раздела 3.3, но пока они статичны, эти представления подчеркивают важный момент: Rails представления могут просто содержать статический HTML.

В оставшейся части этой главы мы добавим немного собственного контента в Home и Help страницы, а затем добавим About страницу которую мы пропустили в Разделе 3.1. Затем добавим очень небольшое количество динамического содержимого заменив статичный тайтл на тайтл, меняющийся в зависимости от страницы.

Прежде чем двинуться дальше, если вы используете Git, хорошей идеей будет добавление файлов для StaticPages контроллера в репозиторий:

$ git add .
$ git commit -m "Add a StaticPages controller"

3.2 Наши первые тесты

Rails Tutorial имеет интуитивно понятный подход к тестированию, который уделяет особое внимание поведению приложения, а не деталям его реализации, что является вариантом разработки через тестирование (TDD) известным как разработка через поведение (BDD). Нашими основными инструментами будут интеграционные тесты (начиная с этого раздела) и юнит тесты (начиная с Главы 6). Интеграционные тесты, известные в контексте RSpec как request specs, позволят нам симулировать действия пользователя взаимодействующего с нашим приложением через веб браузер. Совместно с замечательным синтаксисом предоставляемым Capybara, интеграционные тесты обеспечивают мощную методику тестирования функционала нашего приложения без необходимости вручную проверять каждую страницу в браузере. (Другой популярный инструмент для BDD, называемый Cucumber, будет представлен в Разделе 8.3.)

Основополагающий принцип TDD это написание тестов до написания кода приложения. Вначале это может оказаться непривычным и потребуется некоторое время для того чтобы привыкнуть, но оно того стоит. Написание провального теста и последующая реализация кода приложения для его прохождения увеличивает нашу уверенность в том, что данный тест действительно покрывает функциональность для которой он был написан. Кроме того, цикл разработки провал-реализация-прохождение вызывает потоковое состояние, что приводит к получению удовольствия от кодинга и высокой производительности. Наконец, тесты играют роль клиента для кода приложения, что обычно приводит к более элегантному дизайну софта.

Важно понимать, что TDD не всегда правильный инструмент для работы. В частности, когда вы не полностью уверены, в том, как решить данную проблему программирования, часто бывает полезным пропустить тесты и писать только код приложения, просто чтобы получить представление о том, как решение будет выглядеть. (На языке Экстремального Программирования (XP) этот поисковый этап называется spike.) Как только вы увидите общую форму решения, вы сможете использовать TDD для реализации более изысканной версии.

В этом разделе мы будем запускать тесты используя rspec команду, предоставляемую гемом RSpec. Такая практика эффективна, но не идеальна, и если вы являетесь более продвинутым пользователем, то я советую настроить вашу систему как это описано в Разделе 3.6.

3.2.1 Разработка через тестирование

В Test-Driven Development, мы вначале пишем провальный тест, который в большинстве инструментов тестирования представлен красным цветом. Затем мы реализуем код необходимый для прохождения теста, проходящий тест обычно представлен зеленым цветом. Наконец, если необходимо, мы реорганизуем (рефакторим) код, меняя его форму (например, устраняя повторения) не меняя его функций. Этот цикл известен как “Красный, Зеленый, Реорганизация”.

Мы начнем с того что добавим немного контента в Home страницу используя разработку через тестирование, включая заголовок верхнего уровня (<h1>) содержащий Sample App. Первый шаг это генерация интеграционного теста (request spec) для наших статических страниц:

$ rails generate integration_test static_pages
      invoke  rspec
      create    spec/requests/static_pages_spec.rb

Это создает static_pages_spec.rb в директории spec/requests. Как и бОльшая часть генерируемого кода, полученный результат удручает, так что мы откроем static_pages_spec.rb в текстовом редакторе и заменим его содержимым Листинга 3.9.

Листинг 3.9. Код для тестирования контента Home страницы.
spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "Static pages" do

  describe "Home page" do

    it "should have the content 'Sample App'" do
      visit '/static_pages/home'
      expect(page).to have_content('Sample App')
    end
  end
end

Код в Листинге 3.9 является чистым Ruby, но даже если вы изучали Ruby прежде, он, вероятно, выглядит не очень знакомым. Это происходит потому, что RSpec использует общую гибкость Ruby для определения предметно-ориентированного языка (DSL) созданного только для тестирования. Важным моментом является то, что вам не нужно понимать синтаксис RSpec, чтобы иметь возможность использовать RSpec. На первый взгляд это может показаться магией, но RSpec и Capybara разработаны так, чтобы быть более или менее похожими на английский и если вы будете следовать примерам generate скрипта и другим примерам в этом учебнике вы разберетесь с ним довольно быстро.

Листинг 3.9 содержит describe блок с одним примером, т.е., блоком, начинающимся с it "…" do:

describe "Home page" do

  it "should have the content 'Sample App'" do
    visit '/static_pages/home'
    expect(page).to have_content('Sample App')
  end
end

Первая строка указывает на то, что мы описываем (describing) Home страницу. Это описание является простой строкой и может содержать все что вам угодно; это не важно для RSpec, но имеет значение для вас и прочих читателей вашего кода. Затем spec (# далее по тексту - спек, спеки) говорит что при посещении Home страницы по адресу /static_pages/home, контент должен содержать слова “Sample App”. Как и в случае с первой строкой, происходящее внутри кавычек не имеет значения для RSpec и предназначено для повышения читабельности кода. Затем строка

visit '/static_pages/home'

использует Capybara функцию visit для симуляции посещения URL /static_pages/home в браузере, в то время как строка

expect(page).to have_content('Sample App')

использует переменную page (также предоставленную Capybara) для описания ожидания того что данная страница должна содержать правильный контент.

Для того чтобы правильно запустить тесты мы должны добавить строку в файл spec_helper.rb, как это показано в Листинге 3.10. (В полноценном третьем издании Rails Tutorial я планирую избавиться от этого требования с помощью более новой техники которая называется feature specs.)

Листинг 3.10. Добавление Capybara DSL во вспомогательный RSpec файл.
spec/spec_helper.rb
# This file is copied to spec/ when you run 'rails generate rspec:install'
.
.
.
RSpec.configure do |config|
  .
  .
  .
  config.include Capybara::DSL
end

Существует несколько способов запуска тестов, в том числе удобные, но более продвинутые инструменты описанные в Разделе 3.6. В данный момент мы будем использовать консольную команду rspec (выполняемую с bundle exec для вящей уверенности в том что RSpec запускает ее в окружении которое определено нашим Gemfile):6

$ bundle exec rspec spec/requests/static_pages_spec.rb

Что выдаст провальный тест. Внешний вид результата зависит от вашей системы; на моей системе красный провальный тест выглядит как на Рис. 3.3.7

red_failing_spec_4_0
Рис. 3.3: Красный (провальный) тест. (полный размер)

Для прохождения первого теста мы заменим текст дефолтной Home страницы на HTML из Листинга 3.11.

Листинг 3.11. Код необходимый для прохождения теста Home страницы.
app/views/static_pages/home.html.erb
<h1>Sample App</h1>
<p>
  This is the home page for the
  <a href="https://railstutorial.org/">Ruby on Rails Tutorial</a>
  sample application.
</p>

Что дает нам заголовок верхнего уровня (<h1>) с контентом Sample App, необходимый для прохождения теста. Мы также включили якорный тег a, который создает ссылку на конкретный URL (называемую “href”, или “гипертекстовая ссылка”, в контексте якорного тега):

<a href="https://railstutorial.org/">Ruby on Rails Tutorial</a>

Теперь перезапустим тест чтобы увидеть эффект:

$ bundle exec rspec spec/requests/static_pages_spec.rb

На моей системе проходящий тест выглядит как на Рис. 3.4.

green_passing_spec_4_0
Рис. 3.4: Зеленый (проходящий) тест. (полный размер)

Опираясь на пример Home страницы вы возможно предвидите аналогичный тест и код приложения для страницы Help. Мы начнем с тестирования на наличие значимого контента, в данном случае, строки ’Help’ (Листинг 3.12).

Листинг 3.12. Добавление кода для тестирования содержимого страницы Help.
spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "Static pages" do

  describe "Home page" do

    it "should have the content 'Sample App'" do
      visit '/static_pages/home'
      expect(page).to have_content('Sample App')
    end
  end

  describe "Help page" do

    it "should have the content 'Help'" do
      visit '/static_pages/help'
      expect(page).to have_content('Help')
    end
  end
end

Затем запускаем тесты:

$ bundle exec rspec spec/requests/static_pages_spec.rb

Один тест должен провалиться. (Поскольку системы будут развиваться, и в связи с тем, что при таком количестве тестов отслеживание этих изменений на каждой итерации учебника это ночной кошмар техподдержки, я буду опускать вывод RSpec с этого момента.)

Как видно из Листинга 3.13, код приложения аналогичен коду из Листинга 3.11.

Листинг 3.13. Код необходимый для прохождения теста страницы Help.
app/views/static_pages/help.html.erb
<h1>Help</h1>
<p>
  Get help on the Ruby on Rails Tutorial at the
  <a href="https://railstutorial.org/help">Rails Tutorial help page</a>.
  To get help on this sample app, see the
  <a href="https://railstutorial.org/book">Rails Tutorial book</a>.
</p>

Теперь тест должен пройти:

$ bundle exec rspec spec/requests/static_pages_spec.rb

3.2.2 Добавление страницы

Посмотрев на разработку через тестирование в действии на простом примере, мы используем ту же технику для решения немного более сложной задачи по добавлению новой страницы, а именно, страницы About которую мы умышленно пропустили в Разделе 3.1. Посредством написания теста и запуска RSpec на каждом шаге, мы увидим как TDD может играть роль проводника при разработке кода нашего приложения.

Красный

Мы доберемся до Красной части цикла Красный-Зеленый написав провальный тест для страницы About. Следуя модели из Листинга 3.12, вы, вероятно, можете предугадать правильный тест (Листинг 3.14).

Листинг 3.14. Добавление кода для тестирования содержимого About страницы.
spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "Static pages" do

  describe "Home page" do

    it "should have the content 'Sample App'" do
      visit '/static_pages/home'
      expect(page).to have_content('Sample App')
    end
  end

  describe "Help page" do

    it "should have the content 'Help'" do
      visit '/static_pages/help'
      expect(page).to have_content('Help')
    end
  end

  describe "About page" do

    it "should have the content 'About Us'" do
      visit '/static_pages/about'
      expect(page).to have_content('About Us')
    end
  end
end

Зеленый

Вспомним из Раздела 3.1 что в Rails мы можем генерировать статические страницы создавая действие и соответствующее ему представление с названием страницы. В нашем случае, странице About в первую очередь понадобится действие about в контроллере StaticPages. Имея написанный провальный тест, мы можем быть уверены в том, что получив его прохождение, мы действительно создали рабочую страницу About.

Если вы запускаете RSpec используя

$ bundle exec rspec spec/requests/static_pages_spec.rb

вывод содержит следующую жалобу:

No route matches [GET] "/static_pages/about"

Что намекает на необхоимость добавления правила для URL /static_pages/about в файле маршрутов, мы можем сделать это следуя паттерну из Листинга 3.5, как это показано в Листинге 3.15.

Листинг 3.15. Добавление about маршрута.
config/routes.rb
SampleApp::Application.routes.draw do
  get "static_pages/home"
  get "static_pages/help"
  get "static_pages/about"
  .
  .
  .
end

Теперь запуск

$ bundle exec rspec spec/requests/static_pages_spec.rb

выдает жалобу

The action 'about' could not be found for StaticPagesController

Для решения этой проблемы мы последуем примеру home и help из Листинга 3.6, добавив about действие в StaticPages контроллер (Листинг 3.16).

Листинг 3.16. Контроллер StaticPages с добавленным about действием.
app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController

  def home
  end

  def help
  end

  def about
  end
end

Теперь запуск

$ bundle exec rspec spec/requests/static_pages_spec.rb

говорит что нам не хватает “шаблона”, т.е., представления:

Missing template static_pages/about

для решения этой проблемы мы добавим about представление. Для этого потребуется создать новый файл about.html.erb в директории app/views/static_pages с контентом показанным в Листинге 3.17.

Листинг 3.17. Код для About страницы.
app/views/static_pages/about.html.erb
<h1>About Us</h1>
<p>
  The <a href="https://railstutorial.org/">Ruby on Rails Tutorial</a>
  is a project to make a book and screencasts to teach web development
  with <a href="https://rubyonrails.org/">Ruby on Rails</a>. This
  is the sample application for the tutorial.
</p>

Теперь запуск RSpec должен вернуть нас к Зеленому:

$ bundle exec rspec spec/requests/static_pages_spec.rb

Конечно, это никогда не плохая идея, чтобы посмотреть на страницу в браузере, чтобы убедиться, что наши тесты не являются абсолютно сумасшедшими (Рис. 3.5).

about_us_2nd_edition
Рис. 3.5: Новая страница About (/static_pages/about). (полный размер)

Реорганизация

Теперь, когда мы Позеленели, мы свободны, чтобы реорганизовать наш код, изменяя его форму не меняя функции. Часто код начинает “вонять”, что означает, что он становится уродливым, раздутым, или наполненным повторениями. Компьютеру, конечно, все равно, но не людям, поэтому важно хранить базу кода в чистоте, часто делая рефакторинг (реорганизацию). Наличие хорошего набора тестов является бесценным инструментом в этой связи, так как это резко снижает вероятность внесения ошибки в процессе рефакторинга.

Наш пример приложения слишком мал, чтобы реорганизовать его прямо сейчас, но вонь от кода проникает в каждую трещину, так что нам не придется долго ждать: мы займемся рефакторингом (реорганизацией) в Разделе 3.3.4.

3.3 Немного динамические страницы

Теперь, когда мы создали действия и представления для нескольких статических страниц, мы сделаем их чуть-чуть динамическими, добавив содержимое, которое будет меняться на каждой странице: мы получим заголовок (тайтл) каждой страницы, изменяющийся, отражая ее содержание. Является ли это действительно динамическим контентом — спорно, но это в любом случае закладывает необходимую основу для однозначно динамического содержимого в Главе 7.

Если вы пропустили TDD материал в Разделе 3.2, убедитесь что к этому моменту вы создали About страницу используя код из Листинга 3.15, Листинга 3.16 и Листинга 3.17.

3.3.1 Тестирование изменения заголовка

Наш план заключается в добавлении к страницам Home, Help, и About заголовка (тайтла) меняющегося на каждой странице. Это повлечет за собой использование <title> тега в представления наших страниц. Большинство браузеров отображают содержимое title тега в верхней части окна браузера (Google Chrome является странным исключением из этого правила), к тому же он важен для поисковой оптимизации. Мы начнем с написания тестов для заголовков, затем добавим сами заголовки, а затем используем layout файл для рефакторинга получившихся страниц и устранения повторяющегося кода.

Вы, возможно, заметили, что команда rails new уже создала layout файл. Мы вскоре узнаем о его назначении, но пока вам следует переименовать его прежде чем мы продолжим:

$ mv app/views/layouts/application.html.erb foobar   # временное изменение

(mv это Unix команда; на Windows вам возможно придется переименовать файл используя файловый браузер или команду rename.) Обычно этого не нужно делать в реальном приложении, но нам проще будет понять его назначение если мы предварительно выведем его из строя.

СтраницаURLБазовый заголовокИзменяющийся заголовок
Home/static_pages/home"Ruby on Rails Tutorial Sample App""Home"
Help/static_pages/help"Ruby on Rails Tutorial Sample App""Help"
About/static_pages/about"Ruby on Rails Tutorial Sample App""About"
Таблица 3.1: (В основном) статические страницы для примера приложения.

К концу этого раздела, все три наши статические страницы будут иметь заголовок (тайтл) вида “Ruby on Rails Tutorial Sample App | Home”, где последняя часть заголовка будет меняться в зависимости от страницы (Таблица 3.1). Мы достроим тесты из Листинга 3.14, добавив тесты заголовка (тайтла) в соответствии с моделью в Листинге 3.18.

Листинг 3.18. Тест заголовка.
it "should have the right title" do
  visit '/static_pages/home'
  expect(page).to have_title("Ruby on Rails Tutorial Sample App | Home")
end

Здесь используется метод have_title, который проверяет наличие HTML заголовка с данным контентом. Другими словами, код

expect(page).to have_title("Ruby on Rails Tutorial Sample App | Home")

проверяет что внутри тега title содержится

"Ruby on Rails Tutorial Sample App | Home"

Важно отметить что контент не должен быть абсолютно идентичным; достаточно совпадения лишь некоторой части текста, т.е.

expect(page).to have_title("Home")

сработает также как и полный текст заголовка.

Добавление новых тестов для каждой из трех статических страниц, следуя модели из Листинга 3.18, приводит нас к новому StaticPages тесту (Листинг 3.19). Обратите внимание на то что здесь имеется значительное повторение от которого мы избавимся в Разделе 5.3.4.

Листинг 3.19. StaticPages контроллер спек с тестами заголовков.
spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "Static pages" do

  describe "Home page" do

    it "should have the content 'Sample App'" do
      visit '/static_pages/home'
      expect(page).to have_content('Sample App')
    end

    it "should have the title 'Home'" do
      visit '/static_pages/home'
      expect(page).to have_title("Ruby on Rails Tutorial Sample App | Home")
    end
  end

  describe "Help page" do

    it "should have the content 'Help'" do
      visit '/static_pages/help'
      expect(page).to have_content('Help')
    end

    it "should have the title 'Help'" do
      visit '/static_pages/help'
      expect(page).to have_title("Ruby on Rails Tutorial Sample App | Help")
    end
  end

  describe "About page" do

    it "should have the content 'About Us'" do
      visit '/static_pages/about'
      expect(page).to have_content('About Us')
    end

    it "should have the title 'About Us'" do
      visit '/static_pages/about'
      expect(page).to have_title("Ruby on Rails Tutorial Sample App | About Us")
    end
  end
end

Поместив тесты из Листинга 3.19 куда следует, нужно запустить

$ bundle exec rspec spec/requests/static_pages_spec.rb

чтобы проверить что наш код в Красном (провальные тесты).

3.3.2 Прохождение тестов заголовка

Теперь мы заставим тесты заголовков пройти, и в то же время добавим полную структуру HTML чтобы сделать валидные веб-страницы. Разметка современной веб страницы соответствует форме:

<!DOCTYPE html>
<html>
  <head>
    <title>Greeting</title>
  </head>
  <body>
    <p>Hello, world!</p>
  </body>
</html>

Эта структура включает тип документа, или doctype - декларацию в самом верху страницы которая говорит браузерам какую версию HTML мы используем (в данном случае, HTML5);8 head раздел, в данном случае с “Greeting” внутри тега title; и раздел body, в данном случае с “Hello, world!” внутри тега p (параграф). (Отступы необязательны — HTML нечувствителен к пробелам и игнорирует и табы и пробелы — но они делают структуру документа более смотрибельной.)

Применение этой базовой структуры к странице Home приводит к Листингу 3.20.

Листинг 3.20. Представление для Home страницы с полной HTML структурой.
app/views/static_pages/home.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>Ruby on Rails Tutorial Sample App | Home</title>
  </head>
  <body>
    <h1>Sample App</h1>
    <p>
      This is the home page for the
      <a href="https://railstutorial.org/">Ruby on Rails Tutorial</a>
      sample application.
    </p>
  </body>
</html>

Листинг 3.20 использует заголовки протестированные в Листинге 3.19:

<title>Ruby on Rails Tutorial Sample App | Home</title>

Как результат, тесты для Home страницы теперь должны пройти. Мы все еще в Красном из-за провальных Contact и Help тестов, и мы можем попасть в Зеленый с кодом из Листинга 3.21 и Листинга 3.22.

Листинг 3.21. Представление для Help страницы с полной HTML структурой.
app/views/static_pages/help.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>Ruby on Rails Tutorial Sample App | Help</title>
  </head>
  <body>
    <h1>Help</h1>
    <p>
      Get help on the Ruby on Rails Tutorial at the
      <a href="https://railstutorial.org/help">Rails Tutorial help page</a>.
      To get help on this sample app, see the
      <a href="https://railstutorial.org/book">Rails Tutorial book</a>.
    </p>
  </body>
</html>
Листинг 3.22. Представление для About страницы с полной HTML структурой.
app/views/static_pages/about.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>Ruby on Rails Tutorial Sample App | About Us</title>
  </head>
  <body>
    <h1>About Us</h1>
    <p>
      The <a href="https://railstutorial.org/">Ruby on Rails Tutorial</a>
      is a project to make a book and screencasts to teach web development
      with <a href="https://rubyonrails.org/">Ruby on Rails</a>. This
      is the sample application for the tutorial.
    </p>
  </body>
</html>

3.3.3 Embedded (встроенный) Ruby

Мы уже многого добились в этом разделе, создав три валидные страницы с использованием Rails контроллеров и действий, но они содержат совершенно статический HTML и, следовательно, не раскрывают возможностей Rails. Кроме того, они страдают от ужасного дублирования:

  • Заголовки страниц почти (но не совсем) одинаковые.
  • “Ruby on Rails Tutorial Sample App” является общим для всех трех заголовков (тайтлов).
  • Весь скелет структуры HTML повторяется на каждой странице.

Этот повторяющийся код - нарушение важного, “Don’t Repeat Yourself” (DRY, “Не Повторяй Себя”) принципа; в этом и следующем разделах мы будем “DRY out” (# сушить) наш код, удаляя повторения.

Как это ни парадоксально, но первым шагом на пути устранения дублирования будет усиление дублирования: мы сделаем названия страниц, которые в настоящее время очень похожи, полностью совпадающими. Это позволит легко удалить все повторения одним махом.

Техника включает в себя использование Embedded Ruby в наших представлениях. Поскольку заголовки страниц Home, Help и About имеют переменную составляющую, мы будем использовать специальную Rails функцию называемую provide для установки отличающегося заголовка на каждой странице. Мы можем увидеть как это работает заменив буквальный заголовок “Home” в представлении home.html.erb кодом из Листинга 3.23.

Листинг 3.23. Представление для страницы Home с Embedded (встроенным) Ruby заголовком.
app/views/static_pages/home.html.erb
<% provide(:title, 'Home') %>
<!DOCTYPE html>
<html>
  <head>
    <title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>
  </head>
  <body>
    <h1>Sample App</h1>
    <p>
      This is the home page for the
      <a href="https://railstutorial.org/">Ruby on Rails Tutorial</a>
      sample application.
    </p>
  </body>
</html>

Листинг 3.23 это наш первый пример Embedded Ruby, также называемого ERb. (Теперь вы знаете, почему HTML представления имеют расширение .html.erb.) ERb это основная система шаблонов для включения динамического контента в веб-страницы.9 Код

<% provide(:title, 'Home') %>

указывает с помощью <% ... %> что Rails должны вызвать функцию provide и связать строку ’Home’ с заголовком :title.10 Затем, в заголовке, мы используем тесно связанную нотацию <%= ... %> для вставки заголовка в шаблон используя Ruby-функцию yield:11

<title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>

(Разница между этими двумя типами встроенного Ruby заключается в том, что <% ... %> исполняет код внутри, в то время как <%= ... %> исполняет и вставляет его в шаблон.) Получившаяся в результате страница совершенно не изменилась, вот только изменяющаяся часть заголовка динамически сгенерирована встроенным Руби.

Мы можем проверить что все это работает запустив тесты из Раздела 3.3.1 и увидев что они все еще проходят:

$ bundle exec rspec spec/requests/static_pages_spec.rb

Затем мы можем сделать соответствующие замены для Help и About страниц (Листинг 3.24 и Листинг 3.25).

Листинг 3.24. Представление для страницы Help с Embedded Ruby заголовком.
app/views/static_pages/help.html.erb
<% provide(:title, 'Help') %>
<!DOCTYPE html>
<html>
  <head>
    <title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>
  </head>
  <body>
    <h1>Help</h1>
    <p>
      Get help on the Ruby on Rails Tutorial at the
      <a href="https://railstutorial.org/help">Rails Tutorial help page</a>.
      To get help on this sample app, see the
      <a href="https://railstutorial.org/book">Rails Tutorial book</a>.
    </p>
  </body>
</html>
Листинг 3.25. Представление для страницы About с Embedded Ruby заголовком.
app/views/static_pages/about.html.erb
<% provide(:title, 'About Us') %>
<!DOCTYPE html>
<html>
  <head>
    <title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>
  </head>
  <body>
    <h1>About Us</h1>
    <p>
      The <a href="https://railstutorial.org/">Ruby on Rails Tutorial</a>
      is a project to make a book and screencasts to teach web development
      with <a href="https://rubyonrails.org/">Ruby on Rails</a>. This
      is the sample application for the tutorial.
    </p>
  </body>
</html>

3.3.4 Устранение дублирования шаблонами

Теперь, когда мы заменили переменную часть заголовков страниц на ERb, каждая из наших страниц выглядит примерно так:

<% provide(:title, 'Foo') %>
<!DOCTYPE html>
<html>
  <head>
    <title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>
  </head>
  <body>
      Contents
  </body>
</html>

Другими словами, все наши страницы имеют одинаковую структуру, в том числе, заголовки (из-за Embedded Ruby), исключая содержимое body тега.

Для того чтобы факторизировать ("вынести за скобки") эту повторяющуюся структуру, Rails предоставляет специальный layout файл называемый application.html.erb, который мы переименовали в Разделе 3.3.1 и который мы теперь восстановим:

$ mv foobar app/views/layouts/application.html.erb

Для того чтобы получить работающий макет, мы должны заменить дефолтный заголовок на Embedded Ruby из примеров выше:

<title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>

Получившийся макет представлен в Листинге 3.25.

Листинг 3.26. Макет сайта примера приложения.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
  <title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>
  <%= stylesheet_link_tag    "application", media: "all",
                                            "data-turbolinks-track" => true %>
  <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
  <%= csrf_meta_tags %>
</head>
<body>

<%= yield %>

</body>
</html>

Обратите здесь внимание на специальную строку

<%= yield %>

Этот код отвечает за вставку содержимого каждой страницы в макет. Нет необходимости понимать как это на самом деле работает; куда важнее то, что использование этого макета обеспечивает, например, преобразование содержимого home.html.erb в HTML и последующую его вставку на место <%= yield %>.

Также стоит отметить что дефолтный Rails-макет включает несколько дополнительных строк:

<%= stylesheet_link_tag ... %>
<%= javascript_include_tag "application", ... %>
<%= csrf_meta_tags %>

Этот код организует подключение стилей и JavaScript приложения, являющихся частью файлопровода (Раздел 5.2.1), совместно с Rails методом csrf_meta_tags, который предотвращает подделку межсайтовых запросов (CSRF), вид злонамеренной веб-атаки.

Конечно, представления в Листинге 3.23, Листинге 3.24, и Листинге 3.25 по-прежнему заполнены полной HTML структурой включенной в макет, так что мы должны удалить ее, оставив только внутреннее содержимое. Получившиеся после очистки представления представлены в Листинге 3.27, Листинге 3.28, и Листинге 3.29.

Листинг 3.27. Home страница с удаленной HTML структурой.
app/views/static_pages/home.html.erb
<% provide(:title, 'Home') %>
<h1>Sample App</h1>
<p>
  This is the home page for the
  <a href="https://railstutorial.org/">Ruby on Rails Tutorial</a>
  sample application.
</p>
Листинг 3.28. Страница Help с удаленной HTML структурой.
app/views/static_pages/help.html.erb
<% provide(:title, 'Help') %>
<h1>Help</h1>
<p>
  Get help on the Ruby on Rails Tutorial at the
  <a href="https://railstutorial.org/help">Rails Tutorial help page</a>.
  To get help on this sample app, see the
  <a href="https://railstutorial.org/book">Rails Tutorial book</a>.
</p>
Листинг 3.29. Страница About с удаленной HTML структурой.
app/views/static_pages/about.html.erb
<% provide(:title, 'About Us') %>
<h1>About Us</h1>
<p>
  The <a href="https://railstutorial.org/">Ruby on Rails Tutorial</a>
  is a project to make a book and screencasts to teach web development
  with <a href="https://rubyonrails.org/">Ruby on Rails</a>. This
  is the sample application for the tutorial.
</p>

С таким определением представлений, Home, Help, и About страницы точно такие же, как и прежде, но дублирования стало гораздо меньше. Проверка того что набор тестов по-прежнему проходит даст нам уверенность в том что этот рефакторинг кода прошел успешно:

$ bundle exec rspec spec/requests/static_pages_spec.rb

3.4 Заключение

При взгляде со стороны, в этой главе мы вряд ли чего то добились: мы начали со статических страниц, а закончили… в основном статическими страницами. Но внешность обманчива: разрабатывая в терминах Rails, контроллеры, действия и представления, мы теперь в состоянии добавить произвольное количество динамического содержимого на нашем сайте.

Прежде чем двигаться дальше, давайте потратим минуту, чтобы закоммитить наши изменения и объединить их с мастер веткой. Еще в Разделе 3.1 мы создали в Git ветку для разработки статических страниц. Если вы не делали коммиты во время чтения, сначала сделайте коммит указывающий что мы достигли точки остановки:

$ git add .
$ git commit -m "Finish static pages"

Затем объедините изменения с мастер веткой, используя ту же технику, что и в Разделе 1.3.5:

$ git checkout master
$ git merge static-pages

Как только вы достигаете точки остановки, такой как эта, обычно это хорошая идея, чтобы отправить (to push) ваш код в удаленное хранилище (которым, если вы следовали шагам в Разделе 1.3.4, будет GitHub):

$ git push

Если вы хотите, в этот момент вы даже можете развернуть обновленное приложение на Heroku:

$ git push heroku

3.5 Упражнения

  1. Создайте страницу Contact для примера приложения. Следуя модели в Листинге 3.19, сначала напишите тесты на существовани страницы по URL /static_pages/contact которые проверяют правильность тайтла “Ruby on Rails Tutorial Sample App | Contact”. Добейтесь прохождения тестов и затем заполните страницу Contact содержимым из Листинга 3.30. (Решение этого упражнения является частью Раздела 5.3.)
  2. Вы могли заметить некоторые повторения в спеке StaticPages контроллера (Листинг 3.19). В частности, базовый заголовок, “Ruby on Rails Tutorial Sample App” одинаков для каждого теста заголовка. Используя RSpec функцию let которая создает переменную соответствующую ее аргументу, проверьте что тесты в Листинге 3.31 все еще проходят. Листинг 3.31 вводит интерполяцию строк о которой будет рассказано в Разделе 4.2.2.
  3. (продвинутое) Как отмечается в Heroku page on using sqlite3 for development, хорошей идеей является использование одинаковой базы данных в девелопмент, тест и продакшн окружениях для минимизации возможных несовместимостей. Следуя инструкциям Heroku по локальной установке PostgreSQL установите базу данных PostgreSQL на вашу систему. Обновите ваш Gemfile как показано в Листинге 3.32 для устранения гема sqlite3 и использования только pg гема. Вам также следует разузнать о файле config/database.yml и локальном запуске PostgreSQL. (Обратите внимание - для обеспечения безопасности вам следует добавить database.yml в ваш файл .gitignore, как это показано в Листинге 1.7.) Вашей целью должно быть создание и конфигурирование девелопмент и тестовой баз данных для использования PostgreSQL. Я особенно рекомендую использовать Induction для подключения и просмотра локальной PostgreSQL базы данных. Предупреждение: Это упражнение является очень сложным и я рекомендую его только для продвинутых пользователей. Если вы застрянете, не стесняйтесь пропустить это упражнение. И не переживайте — как было отмечено ранее, пример приложения разрабатываемый в этом учебнике полностью совместим как со SQLite так и с PostgreSQL.
Листинг 3.30. Код для предложенной страницы Contact.
app/views/static_pages/contact.html.erb
<% provide(:title, 'Contact') %>
<h1>Contact</h1>
<p>
  Contact Ruby on Rails Tutorial about the sample app at the
  <a href="https://railstutorial.org/contact">contact page</a>.
</p>
Листинг 3.31. Спек StaticPages контроллера с базовым заголовком.
spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "Static pages" do

  let(:base_title) { "Ruby on Rails Tutorial Sample App" }

  describe "Home page" do

    it "should have the content 'Sample App'" do
      visit '/static_pages/home'
      expect(page).to have_content('Sample App')
    end

    it "should have the title 'Home'" do
      visit '/static_pages/home'
      expect(page).to have_title("#{base_title} | Home")
    end
  end

  describe "Help page" do

    it "should have the content 'Help'" do
      visit '/static_pages/help'
      expect(page).to have_content('Help')
    end

    it "should have the title 'Help'" do
      visit '/static_pages/help'
      expect(page).to have_title("#{base_title} | Help")
    end
  end

  describe "About page" do

    it "should have the content 'About Us'" do
      visit '/static_pages/about'
      expect(page).to have_content('About Us')
    end

    it "should have the title 'About Us'" do
      visit '/static_pages/about'
      expect(page).to have_title("#{base_title} | About Us")
    end
  end

  describe "Contact page" do

    it "should have the content 'Contact'" do
      visit '/static_pages/contact'
      expect(page).to have_content('Contact')
    end

    it "should have the title 'Contact'" do
      visit '/static_pages/contact'
      expect(page).to have_title("#{base_title} | Contact")
    end
  end
end
Листинг 3.32. Gemfile необходимый для использования PostgreSQL вместо SQLite.
source 'https://rubygems.org'
ruby '2.0.0'
#ruby-gemset=railstutorial_rails_4_0

gem 'rails', '4.0.2'
gem 'pg', '0.15.1'

group :development, :test do
  gem 'rspec-rails', '2.13.1'
end

group :test do
  gem 'selenium-webdriver', '2.35.1'
  gem 'capybara', '2.1.0'
end

gem 'sass-rails', '4.0.1'
gem 'uglifier', '2.1.1'
gem 'coffee-rails', '4.0.1'
gem 'jquery-rails', '3.0.4'
gem 'turbolinks', '1.1.1'
gem 'jbuilder', '1.0.2'

group :doc do
  gem 'sdoc', '0.3.20', require: false
end

group :production do
  gem 'rails_12factor', '0.0.2'
end

3.6 Продвинутые настройки

Как упоминалось в Разделе 3.2, непосредственное использование команды rspec не лишено недостатков. В этом разделе мы вначале обсудим способ устранения необходимости каждый раз набирать bundle exec, а затем настроим установку тестов для автоматизации запуска набора тестов с помошью Guard (Раздел 3.6.2) и, опционально, Spork (Раздел 3.6.3). Наконец мы упомянем о способе запуска тестов непосредственно внутри Sublime Text - технике, особенно полезной в паре со Spork.

Этот раздел предназначен для довольно подвинутых пользователей и может быть пропущен без каких либо потерь целостности. Помимо всего прочего, этот материал вероятно устареет гораздо быстрее чем остальные части учебника, так что не стоит ожидать что на вашей системе все будет происходить в точности как это показано в примерах, и вам возможно даже придется погуглить для того чтобы заставить все работать.

3.6.1 Избавляемся от bundle exec

Как было вкратце отмечено в Разделе 3.2.1, зачастую необходимо предварять такие команды как rake или rspec командой bundle exec для того чтобы программы запускались с набором гемов прописанных в вашем Gemfile. (По техническим причинам единственным исключением является сама команда rails.) Такая практика является довольно громоздкой и в этом разделе мы обсудим два способа устранения этой необходимости.

RVM Bundler интеграция

Первый и наиболее предпочтительный метод заключается в использовании RVM, который включает интеграцию с Bundler начиная с версии 1.11. Вы можете проверить что у вас достаточно свежая версия RVM следующим образом:

$ rvm get stable
$ rvm -v

rvm 1.19.5 (stable)

До тех пор пока версия больше чем 1.11.x, установленные гемы будут автоматически извлекаться в правильном Bundler окружении, так что вы можете писать (например)

$ rspec spec/

опуская bundle exec. Если это ваш случай, вам следует пропустить остальную часть этого раздела.

Если вы по каким-либо причинам ограничены более ранней версией RVM, вы все же можете избежать bundle exec с помощью RVM Bundler интеграции12 для конфигурирования Менеджера Версий Руби таким образом чтобы он в локальном окружении включал правильные программы автоматически. Шаги просты, хотя и немного загадочны. Для начала, выполните эти две команды:

$ rvm get head && rvm reload
$ chmod +x $rvm_path/hooks/after_cd_bundler

Затем выполните эти:

$ cd ~/rails_projects/sample_app
$ bundle install --without production --binstubs=./bundler_stubs

Эти команды комбинируют магию RVM и Bundler с тем чтобы обеспечить выполнение таких команд как rake и rspec в правильном окружении. Поскольку эти файлы специфичны для вашей локальной настройки, вы должны добавить bundler_stubs директорию в ваш .gitignore файл (Листинг 3.33).

Листинг 3.33. Добавление bundler_stubs в .gitignore файл.
# Игнорирование конфига bundler
/.bundle

# Игнорирование дефолтной базы данных SQLite.
/db/*.sqlite3
/db/*.sqlite3-journal

# Игнорирование всех логов и временных файлова.
/log/*.log
/tmp

# Игнорирование прочих ненужных файлов.
doc/
*.swp
*~
.project
.DS_Store
.idea
bundler_stubs/

При добавлении другой исполняемой задачи (такой как guard в Разделе 3.6.2), вам нужно перезапустить команду bundle install:

$ bundle --binstubs=./bundler_stubs

binstubs

Если вы не используете RVM, вы все же можете избежать набора bundle exec с помощью гема rubygems-bundler:

$ gem install rubygems-bundler
$ gem regenerate_binstubs

Эта команда создает все необходимые исполняемые файлы локально, таким образом что мы теперь можем запускать набор тестов следующим образом:

$ rspec spec/

Аналогично для rake, и т.д.:

$ rake db:migrate

При добавлении другой исполняемой задачи (такой как guard в Разделе 3.6.2), вам возможно придется перезапустить gem regenerate_binstubs. (Все должно работать нормально и без этого, я упомянул эту возможность просто на всякий случай.)

Ради читателей пропустивших этот раздел, остальная часть этого учебника будет ошибаться в сторону осторожности и явно использовать bundle exec, но, конечно же, вы запросто можете использовать более компактную версию если ваша система сконфигурирована должным образом.

3.6.2 Автоматизируем тесты с Guard

Одно из неудобств связанных с использованием команды rspec это необходимость переключаться на командную строку и запускать тесты вручную. (Второе неудобство - медленный запуск набора тестов рассматривается в Разделе 3.6.3.) В этом разделе мы покажем как использовать Guard для автоматизации запуска тестов. Guard отслеживает изменения в файловой системе так что, например, когда мы изменяем файл static_pages_spec.rb запустится только этот тест. Более того, мы можем настроить Guard таким образом, что при, скажем, изменении файла home.html.erb автоматически запустится static_pages_spec.rb.

Вначале мы добавим guard-rspec в Gemfile (Листинг 3.34).

Листинг 3.34. Gemfile включающий Guard для примера приложения.
source 'https://rubygems.org'
ruby '2.0.0'
#ruby-gemset=railstutorial_rails_4_0

gem 'rails', '4.0.2'

group :development, :test do
  gem 'sqlite3', '1.3.8'
  gem 'rspec-rails', '2.13.1'
  gem 'guard-rspec', '2.5.0'
end

group :test do
  gem 'selenium-webdriver', '2.35.1'
  gem 'capybara', '2.1.0'

  # OS X: раскомментируйте эти строки.
  # gem 'growl', '1.0.3'

  # Linux: раскомментируйте эти строки.
  # gem 'libnotify', '0.8.0'

  # Windows: раскомментируйте эти строки.
  # gem 'rb-notifu', '0.0.4'
  # gem 'win32console', '1.3.2'
  # gem 'wdm', '0.1.0'
end

gem 'sass-rails', '4.0.1'
gem 'uglifier', '2.1.1'
gem 'coffee-rails', '4.0.1'
gem 'jquery-rails', '3.0.4'
gem 'turbolinks', '1.1.1'
gem 'jbuilder', '1.0.2'

group :doc do
  gem 'sdoc', '0.3.20', require: false
end

group :production do
  gem 'pg', '0.15.1'
  gem 'rails_12factor', '0.0.2'
end

Убедитесь в том что вы раскомментировали строки соответствующие вашей системе в тестовой группе. (Обратите внимание - если вы хотите использовать уведомления Growl, вам придется приобрести Growl, который доступен в Apple App Store по весьма скромной цене.)

Затем мы устанавливаем гемы запустив bundle install:

$ bundle install

Затем инициализируем Guard для работы с RSpec:

$ bundle exec guard init rspec
Writing new Guardfile to /Users/mhartl/rails_projects/sample_app/Guardfile
rspec guard added to Guardfile, feel free to edit it

Теперь отредактируем сгенерированный Guardfile так чтобы Guard запускал правильные тесты после обновления интеграционных тестов и представлений (Листинг 3.35).

Листинг 3.35. Дополнения к дефолтному Guardfile. Обратите внимание на добавленный require.
require 'active_support/inflector'

guard 'rspec', all_after_pass: false do
  .
  .
  .
  watch('config/routes.rb')
  # Custom Rails Tutorial specs
  watch(%r{^app/controllers/(.+)_(controller)\.rb$}) do |m|
    ["spec/routing/#{m[1]}_routing_spec.rb",
     "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb",
     "spec/acceptance/#{m[1]}_spec.rb",
     (m[1][/_pages/] ? "spec/requests/#{m[1]}_spec.rb" :
                       "spec/requests/#{m[1].singularize}_pages_spec.rb")]
  end
  watch(%r{^app/views/(.+)/}) do |m|
    (m[1][/_pages/] ? "spec/requests/#{m[1]}_spec.rb" :
                      "spec/requests/#{m[1].singularize}_pages_spec.rb")
  end
  watch(%r{^app/controllers/sessions_controller\.rb$}) do |m|
    "spec/requests/authentication_pages_spec.rb"
  end
  .
  .
  .
end

Здесь строка

guard 'rspec', all_after_pass: false do

обеспечивает незапуск полного набора тестов после прохождения провальных тестов (для ускорения цикла Красный-Зеленый-Реорганизация).

Теперь мы можем запустить guard следующим образом:

$ bundle exec guard

Для того чтобы избавиться от необходимости запускать команду с префиксом bundle exec, повторите шаги из Раздела 3.6.1.

Кстати, если Guard будет жаловаться на отсутствие директории spec/routing, вы можете исправить ситуацию создав пустую директорию с соответствующим названием:

$ mkdir spec/routing

3.6.3 Ускоряем тесты с помощью Spork

При запуске bundle exec rspec вы могли заметить что перед началом запуска тестов проходит несколько секунд, тогда как выполнение самих тестов происходит довольно быстро. Это связано с тем, что при каждом запуске тестов RSpec должен перезагрузить все Рельсовое окружение. Тестовый сервер Spork 13 предназначен для решения этой проблемы. Spork загружает окружение однократно, а затем поддерживает пул процессов для запуска следующих тестов. Spork особенно полезен в комбинации с Guard (Раздел 3.6.2).

Первый шаг это добавление spork гем зависимости в Gemfile (Листинг 3.36).

Листинг 3.36. Gemfile для примера приложения.
source 'https://rubygems.org'
ruby '2.0.0'
#ruby-gemset=railstutorial_rails_4_0

gem 'rails', '4.0.2'

group :development, :test do
  .
  .
  .
  gem 'spork-rails', '4.0.0'
  gem 'guard-spork', '1.5.0'
  gem 'childprocess', '0.3.6'
end
.
.
.

Затем установите Spork используя bundle install:

$ bundle install

Затем сделайте начальную загрузку конфигурации Spork:

$ bundle exec spork --bootstrap

Теперь нам необходимо отредактировать конфигурационный файл RSpec spec/spec_helper.rb таким образом, чтобы окружение загружалось в блоке prefork который обеспечит его однократную загрузку (Листинг 3.37).

Листинг 3.37. Добавление загрузки окружения в блок Spork.prefork.
spec/spec_helper.rb
require 'rubygems'
require 'spork'

Spork.prefork do
  ENV["RAILS_ENV"] ||= 'test'
  require File.expand_path("../../config/environment", __FILE__)
  require 'rspec/rails'
  require 'rspec/autorun'

  # Requires supporting ruby files with custom matchers and macros, etc,
  # in spec/support/ and its subdirectories.
  Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}

  # Checks for pending migrations before tests are run.
  # If you are not using ActiveRecord, you can remove this line.
  ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration)

  RSpec.configure do |config|
    # ## Mock Framework
    #
    # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
    #
    # config.mock_with :mocha
    # config.mock_with :flexmock
    # config.mock_with :rr

    # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
    config.fixture_path = "#{::Rails.root}/spec/fixtures"

    # If you're not using ActiveRecord, or you'd prefer not to run each of your
    # examples within a transaction, remove the following line or assign false
    # instead of true.
    config.use_transactional_fixtures = true

    # If true, the base class of anonymous controllers will be inferred
    # automatically. This will be the default behavior in future versions of
    # rspec-rails.
    config.infer_base_class_for_anonymous_controllers = false

    # Run specs in random order to surface order dependencies. If you find an
    # order dependency and want to debug it, you can fix the order by providing
    # the seed, which is printed after each run.
    #     --seed 1234
    config.order = "random"
    config.include Capybara::DSL
  end
end

Spork.each_run do
  # This code will be run each time you run your specs.

end

Перед запуском Spork мы можем сделать контрольный замер времени необходимого на прохождения тестов следующим образом:

$ time bundle exec rspec spec/requests/static_pages_spec.rb
......

6 examples, 0 failures

real 0m8.633s
user 0m7.240s
sys  0m1.068s

Здесь набор тестов занимает более семи секунд, даже при том, что фактически тесты отрабатывают менее чем за одну десятую секунды. Чтобы ускорить это мы можем открыть отдельное окно терминала, переместиться в корневой Rails каталог, а затем запустить сервер Spork:

$ bundle exec spork
Using RSpec
Loading Spork.prefork block...
Spork is ready and listening on 8989!

(Для того чтобы избавиться от необходимости запускать команду с префиксом bundle exec, повторите шаги из Раздела 3.6.1.) В другом терминальном окне, мы теперь можем запустить наш набор тестов с --drb опцией (“распределенный Ruby”) и проверить что издержки, связанные с постоянной перезагрузкой окружения значительно сократились:

$ time bundle exec rspec spec/requests/static_pages_spec.rb --drb
......

6 examples, 0 failures

real 0m2.649s
user 0m1.259s
sys  0m0.258s

Включать опцию --drb при каждом запуске rspec довольно неудобно, поэтому я рекомендую добавить ее в .rspec файл расположенный в корневой директории проекта, как это показано в Листинге 3.38.

Листинг 3.38. Конфигурирование Rspec для автоматического использования Spork.
.rspec
--colour
--drb

Небольшой совет касательно использования Spork: если ваши тесты провальны, а вы думаете, что они должны проходить, проблема может быть связана со Spork, который иногда может не перегружать необходимые файлы. (Например, это часто происходит при изменении конфигурационных файлов, таких как routes.rb.) При возникновении подобных сомнений, отключите Spork сервер командой Ctrl-C и рестартуйте его:

$ bundle exec spork
Using RSpec
Loading Spork.prefork block...
Spork is ready and listening on 8989!
^C
$ bundle exec spork

Guard и Spork

Спорк особенно полезен в комбинации с Guard, мы можем подружить их следующим образом:

$ bundle exec guard init spork

Затем нам необходимо изменить Guardfile как в Листинге 3.39.

Листинг 3.39. Guardfile обновленный для использования Spork.
Guardfile
require 'active_support/inflector'

guard 'spork', :cucumber_env => { 'RAILS_ENV' => 'test' },
               :rspec_env    => { 'RAILS_ENV' => 'test' } do
  watch('config/application.rb')
  watch('config/environment.rb')
  watch('config/environments/test.rb')
  watch(%r{^config/initializers/.+\.rb$})
  watch('Gemfile')
  watch('Gemfile.lock')
  watch('spec/spec_helper.rb') { :rspec }
  watch('test/test_helper.rb') { :test_unit }
  watch(%r{features/support/}) { :cucumber }
end

guard 'rspec', all_after_pass: false, cli: '--drb' do
  .
  .
  .
end

Обратите внимание что мы обновили аргументы guard включив cli: '--drb', которая обеспечивает использование Guard-ом интерфейса командной строки (cli) для Spork сервера. Мы также добавили команду для слежения за директорией features/support/, которую мы начнем менять начиная с Главы 5.

Закончив с конфигурацией, мы можем одновременно стартовать Guard и Spork командой guard:

$ bundle exec guard

Guard автоматически стартует Spork сервер, кардинально уменьшая издержки при запуске тестов.

Хорошо сконфигурированное тестовое окружение с Guard, Spork и (опционально) уведомлениями вызывают привыкание к разработке через тестирование. См. более подробно об этом в скринкастах Rails Tutorial14.

3.6.4 Запускаем тесты внутри Sublime Text

Если вы используете Sublime Text, то у вас есть большое количество вспомогательных команд для запуска тестов непосредственно в редакторе. Для того чтобы научиться пользоваться ими, следуйте инструкциям для своей платформы на Sublime Text 2 Ruby Tests.15 На моей платформе (Macintosh OS X), я могу установить команды следующим образом:

$ cd ~/Library/Application\ Support/Sublime\ Text\ 2/Packages
$ git clone https://github.com/maltize/sublime-text-2-ruby-tests.git RubyTest

Вы также можете пока использовать инструкции по настройке для Rails Tutorial Sublime Text.16

После перезапуска Sublime Text, пакет RubyTest предоставляет следующие команды:

  • Command-Shift-R: запуск одного теста (если запускается на it блоке) или группы тестов (если выполняется на describe блоке)
  • Command-Shift-E: запуск последнего теста(-ов)
  • Command-Shift-T: запуск всех тестов в текущем файле

Поскольку набор тестов может стать довольно медленным даже для относительно небольших проектов, возможность запускать один тест (или небольшую группу тестов) за раз может сильно упростить жизнь разработчика. Даже один единственный тест требует загрузки Rails окружения, вот почему эти команды отлично работают вместе со Spork: запуск одного теста устраняет необходимость выполнения всего файла с тестами, в то время как Spork устраняет издержки связанные с рестартом тестового окружения. Вот последовательность команд которую я рекомендую:

  1. Стартовать Spork в терминале.
  2. Написать один тест или небольшую группу тестов.
  3. Выполнить Command-Shift-R чтобы убедиться что тест или группа тестов в красном.
  4. Написать соответствующий код приложения.
  5. Выполнить Command-Shift-E для повторного запуска того же теста/группы тестов для проверки того что они позеленели.
  6. Повторить шаги 2 – 5 при необходимости.
  7. По достижении естественной точки остановки (например, перед коммитом), запустить rspec spec/ в командной строке для того, чтобы убедиться что полный набор тестов по-прежнему в зеленом.

Даже с возможностью запуска тестов внутри Sublime Text, я по-прежнему предпочитаю использовать Guard, но в настоящий момент меня кормит TDD техника описанная выше.

  1. Преемник Webrat, Capybara назван в честь крупнейшего грызуна в мире. 
  2. Фактически, вы можете даже опустить installbundle сам является алиасом для bundle install
  3. Как и прежде, увеличенный файл из Листинга 1.7 может оказаться более удобным. 
  4. https://github.com/railstutorial/sample_app_rails_4 
  5. Наш метод создания статических страниц является, вероятно, самым простым, но это не единственный путь. Оптимальный метод действительно зависит от ваших потребностей; если вы ожидаете большое количество статических страниц, использование контроллера StaticPages может стать довольно громоздким, но в нашем демонстрационном приложении нам понадобятся лишь несколько. 
  6. Постоянно писать bundle exec это довольно обременительно; в разделе 3.6 описано несколько способов избежать этого. 
  7. Я на самом деле использую черный фон и для терминала и для редактора, но светлый фон лучше выглядит на скриншотах. 
  8. HTML меняется со времненем; явно указав doctype мы увеличили вероятность того что браузеры будут правильно отображать наши страницы в будущем. Экстремально простой doctype <!DOCTYPE html> характерен для самого последнего HTML стандарта - HTML5. 
  9. Существует еще одна популярная система темплейтов которая называется Haml, которую лично мне очень нравится, но она пока не настолько популярна чтобы использовать ее во вводном учебнике. 
  10. Опытные Rails программисты возможно ожидали увидеть здесь content_for, но он плохо работает с файлопроводом. Функция provide это его замена. 
  11. Если вы изучали Ruby прежде, вы могли бы подозревать, что Rails передает содержимое в блок, и ваша догадка была бы правильной. Но для разработки веб-приложений с Rails это не имеет значения. 
  12. http://rvm.io/integration/bundler/ 
  13. Spork это комбинация spoon-fork (ложка-вилка). Название проекта обыгрывает использование Spork-ом форков POSIX-а
  14. https://railstutorial.org/screencasts 
  15. https://github.com/maltize/sublime-text-2-ruby-tests 
  16. https://github.com/mhartl/rails_tutorial_sublime_text