Ruby on Rails Tutorial

Изучение Веб Разработки на Rails

Michael Hartl

Содержание

  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 Статические страницы
      1. 3.1.1 Истинно статические страницы
      2. 3.1.2 Статические страницы с Rails
    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 URI для регистрации
    5. 5.5 Заключение
    6. 5.6 Упражнения
  7. Глава 6 Моделирование пользователей
    1. 6.1 Модель User
      1. 6.1.1 Миграции базы данных
      2. 6.1.2 Файл модели
        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. Глава 7 Регистрация
    1. 7.1 Демонстрация пользователей
      1. 7.1.1 Отладка и окружения Rails
      2. 7.1.2 Ресурс 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 Сообщения об ошибках при регистрации
    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. Возвращение к attr_accessible
      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 Упражнения

Foreword

Моя компания (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
В настоящее время: основатель Thoughts Ltd.

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

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

Я хотел бы поблагодарить огромное количество Рубистов Rubyists учивших и вдохновлявших меня на протяжении многих лет: 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) 2012 Michael Hartl

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

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

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

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

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

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

В этой главе довольно много кода, особенно в Разделе 3.2 и Разделе 3.3 и если вы новичок в Руби, вам не стоит беспокоиться о полном понимании происходящего. Как отмечалось вn Разделе 1.1.1, один из возможных вариантов - копипастинг тестов и использование их для проверки кода приложения, не особо заботясь в этой точке о понимании того как они работают. К тому же, Глава 4 раскрывает тему Руби более полно, так что у этих идей будет масса возможностей осесть в вашей голове. Наконец, 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.49.)

Листинг 3.1. Gemfile для примера приложения.
source 'https://rubygems.org'

gem 'rails', '3.2.13'

group :development, :test do
  gem 'sqlite3', '1.3.5'
  gem 'rspec-rails', '2.11.0'
end

# Gems used only for assets and not required
# in production environments by default.
group :assets do
  gem 'sass-rails',   '3.2.5'
  gem 'coffee-rails', '3.2.2'
  gem 'uglifier', '1.2.3'
end

gem 'jquery-rails', '2.0.2'

group :test do
  gem 'capybara', '1.1.2'
end

group :production do
  gem 'pg', '0.12.2'
end

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

group :production do
  gem 'pg', '0.12.2'
end

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

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

$ bundle update
$ bundle install --without production

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

Затем нам необходимо сконфигурировать 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.2.

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

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

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

$ git mv README.rdoc README.md
$ git commit -a -m "Improve the README"
create_repository
Рис. 3.1: Создание sample_app репозитория на GitHub. (полный размер)

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

$ git remote add origin [email protected]:<username>/sample_app.git
$ git push -u origin master

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

Конечно, можно дополнительно развернуть приложение на Heroku даже на этом раннем этапе:

$ heroku create --stack cedar
$ git push heroku master

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

$ git push
$ git push heroku

Что обеспечит вас удаленным резервным хранилищем вашего кода и позволит быстро отлавливать любые продакшн-ошибки. Если вы столкнетесь с проблемами на Хероку, прежде всего просмотрите продакшн логи для того чтобы попробовать установить их источник:

$ heroku logs

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

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

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

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

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

$ cd ~/rails_projects/sample_app
$ <editor name> .

Например, для того чтобы открыть пример приложения в Sublime Text, нужно набирать

$ subl .

Для Vim вы наберете vim ., gvim ., или mvim . в зависимости от используемого вами Vim-а.

3.1.1 Истинно статические страницы.

Мы начнем с по-настоящему статических страниц. Напомно из Раздела 1.2.5 что, благодаря rails сценарию, каждый новый Rails - проект поставляется как минимально рабочее приложение с дефолтной (по умолчанию) страницей приветствия по адресу //localhost:3000/ (Рис. 1.3).

public_index_rails_3
Рис. 3.2: Файл public/index.html(полный размер)

Чтобы узнать, откуда берется эта страница, взгляните на файл public/index.html (Рис. 3.2). Он немного грязноват, так как содержит свою собственную информацию о стилях, но он выполняет свою работу: по умолчанию, Rails обрабатывает любые файлы из public каталога, отправляя их непосредственно в браузер.5 В случае специального index.html файла, по умолчанию, вы можете даже не указывать файл в URI, как index.html Хотя вы можете включить его, если хотите; адреса

//localhost:3000/
и
//localhost:3000/index.html
эквивалентны.

Как и следовало ожидать, если захотим, мы можем сделать наши собственные статические файлы HTML и положить их в тот же public каталог как и index.html. Например, давайте создадим файл с дружеским приветствием (Листинг 3.3):6

$ subl public/hello.html
Листинг 3.3. Обычный файл HTML, с дружеским приветствием.
public/hello.html
<!DOCTYPE html>
<html>
  <head>
    <title>Greeting</title>
  </head>
  <body>
    <p>Hello, world!</p>
  </body>
</html>

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

Теперь запустите локальный сервер с помощью

$ rails server

и перейдите на //localhost:3000/hello.html. Как и было обещано, Rails незамедлительно отобразил страницу (Рис. 3.3). Обратите внимание, что название, отображаемое в верхней части окна браузера в Рис. 3.3 это просто содержимое title тега — “Greeting”.

hello_world
Рис. 3.3: Новый статический HTML файл. (полный размер)

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

$ rm public/hello.html

Пока мы оставим index.html файл, но, конечно, в итоге мы удалим и его: мы ведь не хотим, чтобы главной страницей нашего приложения была дефолтная страница Rails показанная на Рис. 1.3. В Разделе 5.3 мы увидим, что нужно изменить, чтобы адрес //localhost:3000/ указывал на нечто иное, нежели public/index.html.

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

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

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

$ git checkout -b static-pages

Rails поставляется со скриптом для создания контроллеров называемым generate; имя контроллера это все, что ему (скрипту) нужно для его магической работы. Для того чтобы использовать generate совместно с RSpec, вам необходимо выполнить команду RSpec generator если вы этого не сделали во введении к этой главе:

$ rails generate rspec:install

Так как мы делаем этот контроллер для обработки статических страниц, мы назовем его StaticPages контроллер Мы также запланируем сделать actions (действия) для 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.1 несколько техник переделывания в Rails.

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

config_directory_rails_3
Рис. 3.4: Содержимое 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"

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

raw_home_view_31
Рис. 3.5: 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-специфичных функций. (Мы узнаем больше об этих двух классах и наследовании в Section 4.4.)

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

def home
end

def help
end

В переводе на простой Ruby, эти методы просто ничего не делают. В Rails, ситуация иная; StaticPagesController это класс Ruby, но благодаря тому, что он наследует от ApplicationController поведение его методов специфично для Rails: при посещении URI /static_pages/home, Rails смотрит в контроллер StaticPages и выполняет код в home действии, а затем рендерит представление ( 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. Что касается браузера, то, чистые HTML файлы из Раздела 3.1.1 и контроллер / действие метод доставки страниц неразличимы: все, что видит браузер, это HTML.

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

Прежде чем двинуться дальше, если вы используете 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'
      page.should 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'
    page.should have_content('Sample App')
  end
end

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

visit '/static_pages/home'

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

page.should have_content('Sample App')

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

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

$ bundle exec rspec spec/requests/static_pages_spec.rb

Что выдаст провальный тест. Внешиний вид результата зависит от вашей системы; на моей системе красный провальный тест выглядит как на Рис. 3.6.10 (Скриншот показывает работу в master ветке вместо static-pages, но это не должно быть для вас поводом для беспокойства.)

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

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

Листинг 3.10. Код необходимый для прохождения теста 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, который создает ссылку на конкретный URI (называемую “href”, или “гипертекстовая ссылка”, в контексте якорного тега):

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

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

$ bundle exec rspec spec/requests/static_pages_spec.rb

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

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

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

Листинг 3.11. Добавление кода для тестирования содержимого страницы 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'
      page.should have_content('Sample App')
    end
  end

  describe "Help page" do

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

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

$ bundle exec rspec spec/requests/static_pages_spec.rb

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

Как видно из Листинга 3.12, код приложения (который пока является чистым HTML) аналогичен коду из Листинга 3.10.

Листинг 3.12. Код необходимый для прохождения теста страницы 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.2. Посредством написания теста и запуска RSpec на каждом шаге, мы увидим как TDD может играть роль проводника при разработке кода нашего приложения.

Red

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

Листинг 3.13. Добавление кода для тестирования содержимого 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'
      page.should have_content('Sample App')
    end
  end

  describe "Help page" do

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

  describe "About page" do

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

Зеленый

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

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

$ bundle exec rspec spec/requests/static_pages_spec.rb

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

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

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

Листинг 3.14. Добавление 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.15).

Листинг 3.15. Контроллер 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

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

ActionView::MissingTemplate:
  Missing template static_pages/about

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

Листинг 3.16. Код для 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.8).

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

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

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

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

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

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

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

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

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

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

$ mv app/views/layouts/application.html.erb foobar   # temporary change

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

СтраницаURIБазовый заголовокИзменяющийся заголовок
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.13, , добавив тесты заголовка (тайтла) в соответствии с моделью в Листинге 3.17.

Листинг 3.17. Тест заголовка.
it "should have the right title" do
  visit '/static_pages/home'
  page.should have_selector('title',
                    :text => "Ruby on Rails Tutorial Sample App | Home")
end

Здесь используется метод have_selector, который проверяет наличие в HTML элементе (“селекторе”) данного контента. Другими словами, код

page.should have_selector('title',
                  :text => "Ruby on Rails Tutorial Sample App | Home")

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

"Ruby on Rails Tutorial Sample App | Home"

Разделе 4.3.3 мы узнаем что синтаксическая конструкция :text => "…" это хэш использующий символ в качестве ключа.) Важно отметить что контент не должен быть абсолютно идентичным; достаточно совпадения лишь некоторой части текста, т.е.

page.should have_selector('title', :text => " | Home")

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

Обратите внимание, что в Листинге 3.17 мы разбили материал внутри have_selector на две строки; это говорит вам нечто важное о синтаксисе Ruby: Ruby не заботится о новых строках.11 Причиной по которой я решил разорвать код на куски является то, что я предпочитаю не применять строки исходного кода длиной более 80 символов — для читабельности.12 В настоящий момент я все еще нахожу этот формат кода довольно уродливым; в Разделе 3.5 есть упражнение по рефакторингу, которое сделает его намного более симпатичным, и Раздел 5.3.4 полностью переписывает StaticPages тесты в соответствии с последними веяниями RSpec.

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

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

describe "Static pages" do

  describe "Home page" do

    it "should have the h1 'Sample App'" do
      visit '/static_pages/home'
      page.should have_selector('h1', :text => 'Sample App')
    end

    it "should have the title 'Home'" do
      visit '/static_pages/home'
      page.should have_selector('title',
                        :text => "Ruby on Rails Tutorial Sample App | Home")
    end
  end

  describe "Help page" do

    it "should have the h1 'Help'" do
      visit '/static_pages/help'
      page.should have_selector('h1', :text => 'Help')
    end

    it "should have the title 'Help'" do
      visit '/static_pages/help'
      page.should have_selector('title',
                        :text => "Ruby on Rails Tutorial Sample App | Help")
    end
  end

  describe "About page" do

    it "should have the h1 'About Us'" do
      visit '/static_pages/about'
      page.should have_selector('h1', :text => 'About Us')
    end

    it "should have the title 'About Us'" do
      visit '/static_pages/about'
      page.should have_selector('title',
                    :text => "Ruby on Rails Tutorial Sample App | About Us")
    end
  end
end

Обратите внимание на то, что мы заменили have_content на have_selector(’h1’, …). Посмотрим сможете ли вы понять зачем. (Подсказка: Что произойдет в случае, если заголовок содержит, например, ‘Help’, но контент внутри h1 тега вместо этого содержит ‘Helf’?)

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

$ bundle exec rspec spec/requests/static_pages_spec.rb

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

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

Теперь мы заставим тесты заголовков пройти, и в то же время добавим полную структуру HTML чтобы сделать валидные веб-страницы. Давайте начнем с Home страницы (Листинг 3.19), используя тот же основной скелет HTML, что и в “hello” странице из Листинга 3.3.

Листинг 3.19. Представление для 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.19 использует заголовки протестированные в Листинге 3.18:

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

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

Листинг 3.20. Представление для 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.21. Представление для 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.22.

Листинг 3.22. Представление для страницы 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.22 это наш первый пример Embedded Ruby, также называемого ERb. (Теперь вы знаете, почему HTML представления имеют расширение .html.erb.) ERb это основная система шаблонов для включения динамического контента в веб-страницы.13 Код

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

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

<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.23 и Листинг 3.24).

Листинг 3.23. Представление для страницы 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.24. Представление для страницы 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.25. Макет сайта примера приложения.
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" %>
    <%= javascript_include_tag "application" %>
    <%= csrf_meta_tags %>
  </head>
  <body>
    <%= yield %>
  </body>
</html>

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

<%= yield %>

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

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

<%= stylesheet_link_tag    "application", :media => "all" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>

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

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

Листинг 3.26. 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.27. Страница 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.28. Страница 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.2 мы создали в 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.18, сначала напишите тесты на существовани страницы по URI /static_pages/contact которые проверяют правильность содержимого тега h1, а затем напишите второй тест для тайтла “Ruby on Rails Tutorial Sample App | Contact”. Добейтесь прохождения тестов и затем заполните страницу Contact содержимым из Листинга 3.29. (Решение этого упражнения является частью Раздела 5.3.)
  2. Вы могли заметить некоторые повторения в спеке StaticPages контроллера (Листинг 3.18). В частности, базовый заголовок, “Ruby on Rails Tutorial Sample App”, одинаков для каждого теста заголовка. Используя RSpec функцию let которая создает переменную соответствующую ее аргументу, проверьте что тесты в Листинге 3.30 все еще проходят. Листинг 3.30 вводит интерполяцию строк о которой будет рассказано в Разделе 4.2.2.
  3. (продвинутое) Как отмечается в Heroku page on using sqlite3 for development, , хорошей идеей является использование одинаковой базы данных в девелопмент, тест и продакшн окружениях для минимизации возможных несовместимостей. Следуя Heroku instructions for local PostgreSQL installation установите базу данных PostgreSQL на вашей локальной системе. Обновите ваш Gemfile как показано в Листинге 3.31 для устранения гема sqlite3 и использования только pg гема. Вам также следует разузнать о файле config/database.yml и локальном запуске PostgreSQL. Вашей целью должно быть создание и конфигурирование девелопмент и тестовой баз данных для использования PostgreSQL. Предупреждение: Это упражнение является крайне сложным и может разрушить вашу жизнь. На самом деле безумием будет даже попытка сделать это. Если вы застрянете, не стесняйтесь пропустить это упражнение. И не переживайте — как было отмечено ранее, пример пиложения разрабатываемый в этом учебнике полностью совместим как со SQLite так и с PostgreSQL.
Листинг 3.29. Код для предложенной страницы 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.30. Спек 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 h1 'Sample App'" do
      visit '/static_pages/home'
      page.should have_selector('h1', :text => 'Sample App')
    end

    it "should have the title 'Home'" do
      visit '/static_pages/home'
      page.should have_selector('title', :text => "#{base_title} | Home")
    end
  end

  describe "Help page" do

    it "should have the h1 'Help'" do
      visit '/static_pages/help'
      page.should have_selector('h1', :text => 'Help')
    end

    it "should have the title 'Help'" do
      visit '/static_pages/help'
      page.should have_selector('title', :text => "#{base_title} | Help")
    end
  end

  describe "About page" do

    it "should have the h1 'About Us'" do
      visit '/static_pages/about'
      page.should have_selector('h1', :text => 'About Us')
    end

    it "should have the title 'About Us'" do
      visit '/static_pages/about'
      page.should have_selector('title', :text => "#{base_title} | About Us")
    end
  end

  describe "Contact page" do

    it "should have the h1 'Contact'" do
      visit '/static_pages/contact'
      page.should have_selector('h1', :text => 'Contact')
    end

    it "should have the title 'Contact'" do
      visit '/static_pages/contact'
      page.should have_selector('title', :text => "#{base_title} | Contact")
    end
  end
end
Листинг 3.31. Gemfile необходимый для использования PostgreSQL вместо SQLite.
source 'https://rubygems.org'

gem 'rails', '3.2.13'
gem 'pg', '0.12.2'

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

# Gems used only for assets and not required
# in production environments by default.
group :assets do
  gem 'sass-rails',   '3.2.5'
  gem 'coffee-rails', '3.2.2'
  gem 'uglifier', '1.2.3'
end

gem 'jquery-rails', '2.0.2'

group :test do
  gem 'capybara', '1.1.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 head && rvm reload
$ rvm -v

rvm 1.15.6 (master)

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

$ rspec spec/

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

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

$ 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.32).

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

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

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

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

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

$ bundle --binstubs=./bundler_stubs

binstubs

Если вы не используете RVM, вы все же можете избежать набора bundle exec. Bundler позволяет создавать связанные бинарники следующим образом:

$ bundle --binstubs

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

$ bin/rspec spec/

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

$ bin/rake db:migrate

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

Ради читателей пропустивших этот раздел, остальная часть этого учебника будет ошибаться в сторону осторожности и явно использовать 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.33).

Листинг 3.33. Gemfile включающий Guard для примера приложения.
source 'https://rubygems.org'

gem 'rails', '3.2.13'

group :development, :test do
  gem 'sqlite3', '1.3.5'
  gem 'rspec-rails', '2.11.0'
  gem 'guard-rspec', '1.2.1'
end

# Gems used only for assets and not required
# in production environments by default.
group :assets do
  gem 'sass-rails',   '3.2.5'
  gem 'coffee-rails', '3.2.2'
  gem 'uglifier', '1.2.3'
end

gem 'jquery-rails', '2.0.2'

group :test do
  gem 'capybara', '1.1.2'
  # System-dependent gems
end

group :production do
  gem 'pg', '0.12.2'
end

Затем мы должны заменить комментарий в конце test группы на системозависимые гемы: (пользователям возможно придется установить OS X Growl и growlnotify):

# Тестовые гемы для Macintosh OS X
group :test do
  gem 'capybara', '1.1.2'
  gem 'rb-fsevent', '0.9.1', :require => false
  gem 'growl', '1.0.3'
end
# Тестовые гемы для Linux
group :test do
  gem 'capybara', '1.1.2'
  gem 'rb-inotify', '0.8.8'
  gem 'libnotify', '0.5.9'
end
# Тестовые гемы для Windows
group :test do
  gem 'capybara', '1.1.2'
  gem 'rb-fchange', '0.0.5'
  gem 'rb-notifu', '0.0.4'
  gem 'win32console', '1.3.0'
end

Затем мы устанавливаем гемы запустив 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.34).

Листинг 3.34. Дополнения к дефолтному Guardfile.
require 'active_support/core_ext'

guard 'rspec', :version => 2, :all_after_pass => false do
  .
  .
  .
  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
  .
  .
  .
end

Здесь строка

guard 'rspec', :version => 2, :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 17 предназначен для решения этой проблемы. Spork загружает окружение однократно, а затем поддерживает пул процессов для запуска следующих тестов. Spork особенно полезен в комбинации с Guard (Раздел 3.6.2).

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

Листинг 3.35. Gemfile для примера приложения.
source 'https://rubygems.org'

gem 'rails', '3.2.13'
.
.
.
group :development, :test do
  .
  .
  .
  gem 'guard-spork', '1.2.0'
  gem 'spork', '0.9.2'
end
.
.
.

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

$ bundle install

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

$ bundle exec spork --bootstrap

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

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

Spork.prefork do
  # Loading more in this block will cause your tests to run faster. However, 
  # if you change any configuration or code from libraries loaded here, you'll
  # need to restart spork for it take effect.
  # This file is copied to spec/ when you run 'rails generate rspec:install'
  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}

  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
    config.mock_with :rspec

    # 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
  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.37.

Листинг 3.37. Конфигурирование 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 with Spork

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

$ bundle exec guard init spork

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

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

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

guard 'rspec', :version => 2, :all_after_pass => false, :cli => '--drb' do
  .
  .
  .
end

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

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

$ bundle exec guard

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

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

3.6.4 Тесты в Sublime Text

Если вы используете Sublime Text, то у вас есть большое количество вспомогательных команд для запуска тестов непосредственно в редакторе. Для того чтобы научиться пользоваться ими, следуйте инструкциям для своей платформы на Sublime Text 2 Ruby Tests.19 На моей платформе (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.20

После перезапуска 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_2nd_ed 
  5. Фактически, Rails обеспечивает непопадание запросов на такие файлы в основной стек Rails; они поставляются непосредственно из файловой системы. (См. The Rails 3 Way.) 
  6. Как обычно, замените subl на команду для своего текстового редактора. 
  7. HTML изменяется со временем; явно декларируя doctype мы даем браузерам возможность отображать наши страницы должным образом в будущем. Чрезвычайно простой doctype <!DOCTYPE html> характерен для последнего стандарта HTML - HTML5. 
  8. Наш метод создания статических страниц является, вероятно, самым простым, но это не единственный путь. Оптимальный метод действительно зависит от ваших потребностей; если вы ожидаете, большое количество статических страниц, использование контроллера Pages может стать довольно громоздким, но в нашем демонстрационном приложении нам понадобятся лишь несколько. См. этот blog post on simple pages at has_many :through для обзора методов, изготовления статических страниц с Rails. Предупреждение: обсуждение довольно продвинутое, так что, вы можете захотеть подождать некоторое время прежде, чем попытаться понять его. 
  9. Постоянно писать bundle exec это довольно обременительно; в разделе 3.6 описано несколько способов избежать этого. 
  10. Я на самом деле использую черный фон и для терминала и для редактора, но светлый фон лучше выглядит на скриншотах. 
  11. Символ новой строки это то, чем заканчивается строка, тем самым начиная новую строку. В коде это представлено символами \n
  12. Подсчет столбцов вручную можг бы свести вас с ума, вот почему у многих текстовых редакторов есть визуальное средство для помощи вам. Например, если вы присмотритесь к Рис. 1.1, вы увидите небольшую вертикальную линию справа, помогающую сохранить код в размере не более 80 символов. (Фактически это 78 столбцов, что дает вам небольшое поле для ошибки.) Если вы используете TextMate, можно найти эту функцию в View > Wrap Column > 78. В Sublime Text, вы можете использовать View > Ruler > 78 или View > Ruler > 80
  13. Есть вторая популярная система шаблонов под названием Haml, которую я лично люблю, но она все же достаточно нестандартна для использования во вводном учебном руководстве. 
  14. Опытные Rails программисты возможно ожидали увидеть здесь content_for, но он плохо работает с файлопроводом. Функция provide это его замена. 
  15. Если вы изучали Ruby прежде, вы могли бы подозревать, что передает содержимое в блок, и ваша догадка была бы правильной. Но для разработки веб-приложений с Rails это не имеет значения. 
  16. http://rvm.io/integration/bundler/ 
  17. Spork это комбинация spoon-fork (ложка-вилка). Название проекта обыгрывает использование Spork-ом форков POSIX-а
  18. https://railstutorial.org/screencasts 
  19. https://github.com/maltize/sublime-text-2-ruby-tests 
  20. https://github.com/mhartl/rails_tutorial_sublime_text