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):
 * Весь код написан Майклом Хартлом. До тех пор пока вы осознаете это,
 * вы можете делать с ним все что захотите. Если мы когда нибудь
 * встретимся, и если это того стоило, вы можете купить мне
 * пиво в ответ.
 * ----------------------------------------------------------------------------
 */

Глава 5 Заполнение шаблона

В процессе краткого обзора Ruby в Главе 4 мы узнали о подключении таблиц стилей к примеру приложения, но, как было отмечено в Разделе 4.3.4, эти таблицы стилей в настоящий момент пусты. В этой главе мы это изменим, включив фреймворк Bootstrap в наше приложение и добавив затем немного своих стилей.1 Мы также начнем заполнять шаблон ссылками на страницы (например Home и About), которые мы создали ранее (Раздел 5.1). В процессе мы узнаем о частичных шаблонах (partials), Rails маршрутах и файлопроводе, включая небольшое введение в Sass (Раздел 5.2). Мы также отрефакторим тесты из Главы 3 используя последние веяния RSpec. Мы закончим, сделав первый важный шаг на пути создания опции регистрации пользователей на нашем сайте.

5.1 Добавление некоторых структур

Rails Учебник это книга по веб-разработке, а не по веб-дизайну, но работа над приложением, которое выглядит как полное дерьмо - удручает, поэтому в этом разделе мы добавим некоторые структуры в шаблон и придадим ему минимальный дизайн с CSS. В дополнение к нашим собственным CSS правилам, мы будем использовать Bootstrap - CSS фреймворк с открытым исходным кодом от Twitter. Мы также придадим нашему коду немного стиля, так сказать, используя частичные шаблоны почистим шаблон, поскольку он немного загроможден.

При создании веб-приложений, часто бывает полезным получить общий вид пользовательского интерфейса как можно раньше. Таким образом, на протяжении остальной части книги, я буду часто использовать макеты (в контексте Веб часто называемыми каркасами), которые являются грубыми набросками того, как приложение, возможно, будет выглядеть.2 В этой главе мы будем главным образом разрабатывать статические страницы введеные в Разделе 3.1, включая логотип сайта, заголовок с навигацией по сайту, и подвал сайта. Каркас для наиболее важной из страниц — Home страницы, представлен на Рис. 5.1. Вы можете увидеть конечный результат на Рис. 5.7. Он отличается в некоторых деталях, но это нормально, так как каркас и не должен быть абсолютно точным.

home_page_mockup_bootstrap
Рис. 5.1: Каркас Home страницы примера приложения. (полный размер)

Как обычно, если вы используете Git для управления версиями, то сейчас самое время создать новую ветку:

$ git checkout -b filling-in-layout

5.1.1 Навигация по сайту

В качестве первого шага к добавлению ссылок и стилей к примеру приложения, мы добавим в файл шаблона сайта application.html.erb (последний раз мы его видели в Листинге 4.3) дополнительные HTML структуры. Это подразумевает добавление нескольких дополнительных разделителей, немного CSS классов и запуск навигации по сайту. Полный файл представлен в Листинге 5.1; объяснения различных частей последуют сразу за ним. Если вы не хотите откладывать удовольствие, вы можете посмотреть на результаты представленные на Рис. 5.2. (Примечание: это (пока) не очень приятно.)

Листинг 5.1. Шаблон сайта с добавленными структурами.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= stylesheet_link_tag "application", media: "all",
                                           "data-turbolinks-track" => true %>
    <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
    <%= csrf_meta_tags %>
    <!--[if lt IE 9]>
    <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]-->
  </head>
  <body>
    <header class="navbar navbar-fixed-top navbar-inverse">
      <div class="navbar-inner">
        <div class="container">
          <%= link_to "sample app", '#', id: "logo" %>
          <nav>
            <ul class="nav pull-right">
              <li><%= link_to "Home",    '#' %></li>
              <li><%= link_to "Help",    '#' %></li>
              <li><%= link_to "Sign in", '#' %></li>
            </ul>
          </nav>
        </div>
      </div>
    </header>
    <div class="container">
      <%= yield %>
    </div>
  </body>
</html>

Давайте просмотрим на новые элементы в Листинге 5.1 сверху вниз. Как вкратце отмечалось в Разделе 3.3.2, Rails 4 по умолчанию использует HTML5 (о чем говорит doctype <!DOCTYPE html>); т.к. HTML5 — новый стандарт и некоторые браузеры (особенно старые версии Internet Explorer) пока не полностью его поддерживают, мы включим JavaScript код (известный как “HTML5 shim”) чтобы обойти это затруднение:

<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->

Немного странный синтаксис

<!--[if lt IE 9]>

включает закомментированную строку, только если версия Microsoft Internet Explorer (IE) ниже чем 9 (if lt IE 9). Чудной [if lt IE 9] синтаксис не является частью Rails; это условный комментарий поддерживаемый браузерами Internet Explorer специально для подобных ситуаций. Это хорошая вещь, хотя бы потому, что это означает, что мы можем включить дополнительную таблицу стилей только для IE браузеров версии менее 9, не затрагивая такие браузеры как Firefox, Chrome и Safari.

Следующий раздел включает header для (текстового) логотипа сайта, несколько разделителей (использующих div тег) и список элементов с навигационными ссылками:

<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="navbar-inner">
    <div class="container">
      <%= link_to "sample app", '#', id: "logo" %>
      <nav>
        <ul class="nav pull-right">
          <li><%= link_to "Home",    '#' %></li>
          <li><%= link_to "Help",    '#' %></li>
          <li><%= link_to "Sign in", '#' %></li>
        </ul>
      </nav>
    </div>
  </div>
</header>

Здесь тег header обозначает элементы которые должны находиться вверху страницы. Мы добавили тегу header три CSS класса,3 - navbar, navbar-inverse и navbar-fixed-top, разделенных пробелом:

<header class="navbar navbar-fixed-top navbar-inverse">

Всем HTML элементам могут быть назначены как классы, так и id (идентификаторы); это всего лишь ярлыки и они используются для стилевания сайта с помощью CSS (Раздел 5.1.2). Основное различие между классами и идентификаторами, в том, что классы могут быть использованы несколько раз на странице, а идентификаторы могут быть использованы лишь один раз. В данном случае, и navbar и navbar-fixed-top классы имеют особое значение для фреймворка Bootstrap, который мы установим и начнем использовать в Разделе 5.1.2.

Внутри тега header мы видим несколько div тегов:

<div class="navbar-inner">
  <div class="container">

Тег div это разделитель; он не делает ничего, кроме разделения документа на отдельные части. В старом стиле HTML, div теги использовали почти для всех разделов сайта, но HTML5 добавил header, nav и section элементы для разделов, общих для многих приложений. В данном случае каждый div снабжен CSS классом. Как и в случае с тегом header, эти классы имеют особое значение для Bootstrap.

После дивов мы неожиданно наталкиваемся на встроенный Ruby:

<%= link_to "sample app", '#', id: "logo" %>
<nav>
  <ul class="nav pull-right">
    <li><%= link_to "Home",    '#' %></li>
    <li><%= link_to "Help",    '#' %></li>
    <li><%= link_to "Sign in", '#' %></li>
  </ul>
</nav>

Здесь используется Rails хелпер link_to для создания ссылок (которые мы создавали непосредственно с якорным (анкорным) тегом a в Разделе 3.3.2); первый аргумент в link_to это текст ссылки, а второй это URL. Мы заменим URL на именованные маршруты в Разделе 5.3.3, но сейчас мы используем заглушки URL ’#’ обычно применяемые в веб-дизайне. Третий аргумент это хэш опций, в данном случае добавление CSS id logo для ссылки на пример приложения. (У остальных трех ссылок хэш опций отсутствует, и это нормально поскольку это необязательный аргумент.) Rails хелперы часто принимают хэш опций подобным образом, что дает нам возможность добавлять произвольные HTML опции оставаясь в рамках Rails.

Второй элемент внутри дивов это список навигационных ссылок, созданный с помощью тега ненумерованного списка ul, совместно с тегом элемента списка li:

<nav>
  <ul class="nav pull-right">
    <li><%= link_to "Home",    '#' %></li>
    <li><%= link_to "Help",    '#' %></li>
    <li><%= link_to "Sign in", '#' %></li>
  </ul>
</nav>

Тег nav, хотя формально здесь и не нужен, служит для обозначения того что это именно навигационные ссылки. Классы nav и pull-right тега ul имеют особое значение для Bootstrap. После того как Rails обработает этот шаблон и оценит встроенный Ruby, список будет выглядеть следующим образом:

<nav>
  <ul class="nav pull-right">
    <li><a href="#">Home</a></li>
    <li><a href="#">Help</a></li>
    <li><a href="#">Sign in</a></li>
  </ul>
</nav>

Завершающая часть шаблона это div для основного контента:

<div class="container">
  <%= yield %>
</div>

Как и прежде, класс container имеет специальное значение для Bootstrap. Как мы узнали в Разделе 3.3.4, метод yield вставляет содержимое каждой страницы в шаблон сайта.

Если не брать в расчет подвал сайта, который мы добавим в Разделе 5.1.3, наш шаблон теперь завершен, и мы можем посмотреть на результаты, посетив, например, Home страницу. Для того чтобы извлечь пользу из ожидающих своего часа элементов стиля, мы добавим несколько дополнительных элементов к home.html.erb представлению (Листинг 5.2).

Листинг 5.2. Home страница со ссылкой на страницу регистрации.
app/views/static_pages/home.html.erb
<div class="center hero-unit">
  <h1>Welcome to the Sample App</h1>

  <h2>
    This is the home page for the
    <a href="https://railstutorial.org/">Ruby on Rails Tutorial</a>
    sample application.
  </h2>

  <%= link_to "Sign up now!", '#', class: "btn btn-large btn-primary" %>
</div>

<%= link_to image_tag("rails.png", alt: "Rails"), 'https://rubyonrails.org/' %>

В ожидании добавления пользователей к нашему сайту в Главе 7, первый link_to создает ссылку-заглушку вида

<a href="#" class="btn btn-large btn-primary">Sign up now!</a>

В теге div CSS класс hero-unit предназначен для Bootstrap, также как и btn, btn-large и btn-primary классы в кнопке signup.

Второй link_to показывает нам image_tag хелпер, который принимает в качестве аргумента путь к изображению и необязательный хэш опций, в данном случае, alt атрибут тега image с помощью символа. Чтобы внести некоторую ясность, давайте взглянем на HTML производимый этим тегом:4

<img alt="Rails" src="/assets/rails.png" />

Alt атрибут, это то, что будет отображаться, если нет изображения, также его используют читатели экрана для слабовидящих. Хотя люди иногда пренебрежительно относятся к включению атрибута alt для изображений, на самом деле его включенин требуется HTML стандартом. К счастью, Rails включает дефолтный атрибут alt; если вы не укажете его явно при вызове image_tag, Rails пропишет в него название файла изображения (за вычетом расширения). В данном случае мы все же явно установили alt текст для того чтобы капитализировать “Rails”.

В предыдущих версиях Rails логотип rails.png включался автоматически в каждый проект, но в крайней версии он не генерируется при вызове команды rails new, так что вам следует загрузить его с главной страницы Ruby on Rails (https://rubyonrails.org/images/rails.png) и поместить его в директорию app/assets/images/. (Вам, возможно, также потребуется создать эту директорию, или командой mkdir или с помощью графического менеджера файлов.) Поскольку мы использовали хелпер image_tag в Листинге 5.2, Rails автоматически будет находить любые изображения в этой директории с помощью файлопровода (Раздел 5.2).

Теперь мы, наконец, готовы увидеть плоды наших трудов (Рис. 5.2). Говорите "Не впечатляет"? Может быть. К счастью, однако, мы проделали хорошую работу, прописав для наших HTML элементов внятные классы и идентификаторы, которые позволят нам отстилить наш сайт с помощью CSS.

layout_no_logo_or_custom_css_bootstrap_rails_4
Рис. 5.2: Страница Home (/static_pages/home) без кастомного CSS. (полный размер)

5.1.2 Bootstrap и кастомные CSS

В Разделе 5.1.1 мы связали многие HTML элементы с CSS классами, которые дают нам большую гибкость при построении шаблона с помощью CSS. Как отмечалось в Разделе 5.1.1, многие из этих классов специфичны для Bootstrap - фреймворка от Twitter который делает легким создание приятного веб дизайна и элементов пользовательского интерфейса для HTML5 приложений. В этом разделе мы будем комбинировать Bootstrap с небольшим количеством кастомных CSS правил для того чтобы придать некоторый стиль примеру приложения.

Нашим первым шагом будет добавление Bootstrap, что может быть сделано в Rails приложениях с помощью bootstrap-sass гема, как это показано в Листинге 5.3. Фреймворк Bootstrap изначально использует язык LESS CSS для создания динамических таблиц стилей, но файлопровод в Rails по умолчанию поддерживает (очень похожий) язык Sass (Раздел 5.2), bootstrap-sass конвертирует LESS в Sass и делает все необходимые файлы Bootstrap доступными для текущего приложения.5

Листинг 5.3. Добавление гема bootstrap-sass в Gemfile.
source 'https://rubygems.org'
ruby '2.0.0'
#ruby-gemset=railstutorial_rails_4_0

gem 'rails', '4.0.2'
gem 'bootstrap-sass', '2.3.2.0'
.
.
.

Для установки Bootstrap мы, как обычно запускаем bundle install:

$ bundle install

Затем рестартуем веб-сервер для того чтобы изменения вступили в силу. (На большинстве систем рестарт сервера подразумевает нажатие Ctrl-C и последующий запуск rails server.) Наконец, (для Rails 4.0) нам нужно добавить одну строку в config/application.rb для того чтобы сделать bootstrap-sass совместимым с файлопроводом, как это показано в Листинге 5.4.

Листинг 5.4. Добавление строки обеспечивающей совместимость с файлопроводом.
config/application.rb
require File.expand_path('../boot', __FILE__)
.
.
.
module SampleApp
  class Application < Rails::Application
    .
    .
    .
    config.assets.precompile += %w(*.png *.jpg *.jpeg *.gif)
  end
end

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

app/assets/stylesheets/custom.css.scss

Здесь важны как название директории так и название файла. Директория

app/assets/stylesheets

является частью файлопровода (Раздел 5.2) и любые таблицы стилей в этой директории будут автоматически включены как часть файла application.css включенного в шаблон сайта. Кроме того, название файла custom.css.scss включает расширение .css, которое обозначает файл CSS, и расширение .scss которое указывает на то что это “Sassy CSS” файл и говорит файлопроводу что этот файл необходимо обрабатывать с помошью Sass. (Мы не будем использовать Sass до Раздела 5.2.2, но сейчас он необходим bootstrap-sass гему.)

Внутри файла для кастомных CSS мы можем использовать функцию @import для включения Bootstrap, как это показано в Листинге 5.5.

Листинг 5.5. Добавление Bootstrap CSS.
app/assets/stylesheets/custom.css.scss
@import "bootstrap";

Эта единственная строка включает весь Bootstrap CSS фреймворк, результат показан на Рис. 5.3. (Вам, возможно, придется рестартовать веб сервер. Стоит также отметить что скриншоты используют Bootstrap 2.0, в то время как учебник использует Bootstrap 2.3, так что могут быть небольшие отличия во внешнем виде, но это не повод для беспокойства.) Текст расположен плохо и логотип оказался неотстиленым, но цвета и кнопка регистрации выглядят многообещающе.

sample_app_only_bootstrap_4_0
Рис. 5.3: Пример приложения с Bootstrap CSS. (полный размер)

Затем мы добавим немного CSS который будет использован для стилизации шаблона и каждой отдельной страницы, как это показано в Листинге 5.6. В Листинге 5.6 довольно много правил; для того чтобы понять что делает CSS правило, часто бывает полезным закомментировать его с помощью CSS комментариев, т.e., поместив его внутрь /* … */ и посмотреть что изменилось. Результат CSS из Листинга 5.6 показан на Рис. 5.4.

Листинг 5.6. Добавление CSS для придания некого универсального стиля применяемого ко всем страницам.
app/assets/stylesheets/custom.css.scss
@import "bootstrap";

/* universal */

html {
  overflow-y: scroll;
}

body {
  padding-top: 60px;
}

section {
  overflow: auto;
}

textarea {
  resize: vertical;
}

.center {
  text-align: center;
}

.center h1 {
  margin-bottom: 10px;
}
sample_app_universal_4_0
Рис. 5.4: Добавление нескольких отступов и прочих универсальных стилей. (полный размер)

Обратите внимание что CSS в Листинге 5.6 имеют последовательный вид. В целом, CSS правила относятся либо к классу, id, HTML тегу или к какой либо их комбинации за которыми идет список стилевых команд. Например,

body {
  padding-top: 60px;
}

задает отступ в 60 пикселей от верхнего края страницы. Благодаря классу navbar-fixed-top в теге header, Bootstrap фиксирует панель навигации вверху страницы, таким образом отступ служит для разделения основного текста и навигации. (Поскольку дефолтный цвет навигационной панели в Boostrap 2.1 отличается от того что ранее использовался в версии 2.0, нам пришлось добавить класс navbar-inverse для того чтобы сделать его черным вместо (теперь) дефолтного белого.) Между тем, CSS в правиле

.center {
  text-align: center;
}

связывает класс center со свойством text-align: center. Другими словами, точка . в .center указывает на то, что правило стилизует класс. (Как мы увидим в Листинге 5.8, знак фунта # указывает на правило для стилизации CSS id.) Это означает что этот элемент внутри любого тега (такого как div) с классом center будет отцентрован на странице. (Мы увидим пример этого класса в Листинге 5.2.)

Хотя Bootstrap поставляется с CSS правилами для приятной типографики, мы также добавим немного кастомных правил для представления текста на нашем сайте, как это показано в Листинге 5.7. (Не все эти правила применяются к странице Home page, но каждое правило будет использовано в какой-то части примера приложения.) Результат Листинга 5.7 показан на Рис. 5.5.

Листинг 5.7. Добавление CSS для хорошей типографики.
app/assets/stylesheets/custom.css.scss
@import "bootstrap";
.
.
.

/* typography */

h1, h2, h3, h4, h5, h6 {
  line-height: 1;
}

h1 {
  font-size: 3em;
  letter-spacing: -2px;
  margin-bottom: 30px;
  text-align: center;
}

h2 {
  font-size: 1.2em;
  letter-spacing: -1px;
  margin-bottom: 30px;
  text-align: center;
  font-weight: normal;
  color: #999;
}

p {
  font-size: 1.1em;
  line-height: 1.7em;
}
sample_app_typography_4_0
Рис. 5.5: Добавление нескольких стилей для типографики. (полный размер)

Наконец, мы добавим несколько правил для придания стиля логотипу сайта, который представляет собой текст “sample app”. CSS в Листинге 5.8 конвертирует текст в верхний регистр и изменяет его размер, цвет и положение. (Мы использовали CSS id потому что предполагаем что логотип сайта будет на странице в единственном экземпляре, но вы можете использовать класс вместо него.)

Листинге 5.8. Добавление CSS для логотипа сайта.
app/assets/stylesheets/custom.css.scss
@import "bootstrap";
.
.
.

/* header */

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: #fff;
  text-transform: uppercase;
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
  line-height: 1;
}

#logo:hover {
  color: #fff;
  text-decoration: none;
}

Здесь color: #fff изменяет цвет логотипа на белый. HTML цвета могут быть закодированы шестью base-16 (шестнадцатеричными) числами, по одному для каждого из основных цветов красного, зеленого и голубого (именно в этом порядке). Код #ffffff максимизирует все три цвета, приводя к чистому белому и #fff это сокращение для #ffffff. CSS стандарт также определяет большое количество синонимов для часто употребляемых HTML цветов, включая white для #fff. Результат CSS из Листинга 5.8 показан на Рис. 5.6.

sample_app_logo_4_0
Рис. 5.6: Пример приложения с приятно отстиленным логотипом. (полный размер)

5.1.3 Частичные шаблоны (partials)

Хотя шаблон в Листинге 5.1 и выполняет свою работу, но он становится немного суматошным. HTML shim занимает три строки и использует странный IE-специфичный синтаксис, и было бы хорошо вынести его в какое нибудь отдельное место. К тому же заголовок формирует отдельную логическую единицу и он весь должен быть упакован в одном месте. Мы можем достигнуть этого, используя удобное Rails-приспособление называемое partials (# партиалы, частичные шаблоны). Давайте сначала посмотрим, как шаблон выглядит после определения партиалов (Листинг 5.9).

Листинг 5.9. Шаблон сайта с партиалами для таблицы стилей и header-а.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= stylesheet_link_tag "application", media: "all",
                                           "data-turbolinks-track" => true %>
    <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
    <%= csrf_meta_tags %>
    <%= render 'layouts/shim' %>
  </head>
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
    </div>
  </body>
</html>

В Листинге 5.9 мы заменили HTML shim строки одним вызовом Rails помощника называемого render:

<%= render 'layouts/shim' %>

Эта строка приводит к поиску файла, называемого app/views/layouts/_shim.html.erb, оценке его содержимого и вставке результата оценки в представление.6 (Напомним, что <%= ... %> это Embedded Ruby синтаксис, необходимый для оценки выражения Ruby и последующей вставки результата в шаблон. Обратите внимание на символ подчеркивания в имени файла _shim.html.erb; это подчеркивание - универсальное соглашение для именования частичных шаблонов, которое, среди прочего, позволяет идентифицировать все партиалы в каталоге с первого взгляда.

Конечно, чтобы заполучить частичные шаблоны на работу, мы должны заполнить их чем-нибудь; в случае shim-партиала это просто три строки shim-кода из Листинга 5.1; результат представлен в Листинге 5.10.

Листинг 5.10. Частичный шаблон для HTML shim.
app/views/layouts/_shim.html.erb
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->

Аналогичным образом, мы можем переместить материал header-а в частичный шаблон показанный в Листинге 5.11 и вставить его в шаблон другим вызовом render.

Листинг 5.11. Партиал для header-а сайта.
app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="navbar-inner">
    <div class="container">
      <%= link_to "sample app", '#', id: "logo" %>
      <nav>
        <ul class="nav pull-right">
          <li><%= link_to "Home",    '#' %></li>
          <li><%= link_to "Help",    '#' %></li>
          <li><%= link_to "Sign in", '#' %></li>
        </ul>
      </nav>
    </div>
  </div>
</header>

Теперь, когда мы знаем как сделать частичные шаблоны, давайте добавим подвал (footer) для каждой страницы сайта, чтобы он шел вместе с шапкой (header). К этому моменту вы, вероятно, догадались, что мы будем называть его _footer.html.erb и поместим его в layouts каталог (Листинг 5.12).7

Листинг 5.12. Партиал для подвала сайта.
app/views/layouts/_footer.html.erb
<footer class="footer">
  <small>
    <a href="https://railstutorial.org/">Rails Tutorial</a>
    by Michael Hartl
  </small>
  <nav>
    <ul>
      <li><%= link_to "About",   '#' %></li>
      <li><%= link_to "Contact", '#' %></li>
      <li><a href="http://news.railstutorial.org/">News</a></li>
    </ul>
  </nav>
</footer>

Как и в шапке (header), в подвале (footer) мы использовали link_to для внутренних ссылок на About и Contact страницы и заглушили URL-адреса символом ’#’ до лучших времен. (Как и header, footer - новый, HTML5, тег.)

Мы можем вставить партиал подвала в шаблон тем же образом, что и shim и header партиалы (Листинг 5.13).

Листинг 5.13. Шаблон сайта с партиалом подвала.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= stylesheet_link_tag "application", media: "all",
                                           "data-turbolinks-track" => true %>
    <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
    <%= csrf_meta_tags %>
    <%= render 'layouts/shim' %>
  </head>
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
      <%= render 'layouts/footer' %>
    </div>
  </body>
</html>

Конечно, подвал будет уродливым, если его немного не отстилить (Листинг 5.14). Результаты представлены на Рис. 5.7.

Листинг 5.14. Добавление CSS для подвала сайта.
app/assets/stylesheets/custom.css.scss
.
.
.

/* footer */

footer {
  margin-top: 45px;
  padding-top: 5px;
  border-top: 1px solid #eaeaea;
  color: #999;
}

footer a {
  color: #555;
}

footer a:hover {
  color: #222;
}

footer small {
  float: left;
}

footer ul {
  float: right;
  list-style: none;
}

footer ul li {
  float: left;
  margin-left: 10px;
}
site_with_footer_bootstrap_4_0
Рис. 5.7: Home страница (/static_pages/home) с добавленным подвалом. (полный размер)

5.2 Sass и файлопровод (asset pipeline)

Одним из наиболее значитильных нововведений в последних версиях Rails является файлопровод (asset pipeline), который значительно улучшает создание и управление статическими файлами (# assets, активы) такими как CSS, JavaScript и изображения. Этот раздел даст вам общее понимание файлопровода, а затем покажет как использовать удивительный инструмент для создания CSS называемый Sass, теперь уже по умолчанию включенный в Rails как часть файлопровода.

5.2.1 Файлопровод

Файлопровод включает в себя множество подкапотных изменений в Rails’, но с точки зрения типичного Rails разработчика есть три принципиальных момента в которых нужно разобраться: директории ассетов (активов, файлов), файлы-манифесты и препроцессоры.8 Давайте рассмотрим каждый из них по очереди.

Директории ассетов

В версиях Rails до 3.0 включительно, статические ассеты жили в директории public/ следующим образом:

  • public/stylesheets
  • public/javascripts
  • public/images

Файлы в этих директориях (даже в версиях после-3.0) автоматически выдаются через запрос к http://example.com/stylesheets, и т.д..

Начиная с Rails 3.1 и продолжая в Rails 4, существуют три каноничные директории для статических ассетов, каждая со своим собственным назначением:

  • app/assets: ассеты специфичные для данного приложения
  • lib/assets: ассеты для библиотек написанных вашей командой разработчиков
  • vendor/assets: ассеты сторонних поставщиков (програмного обеспечения)

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

$ ls app/assets/
images      javascripts stylesheets

Теперь мы в состоянии понять причины по которым мы поместили файл custom.css.scss в директорию app/assets в Разделе 5.1.2: custom.css.scss специфичен для примера приложения, поэтому он отправляется в app/assets/stylesheets.

Файлы-манифесты

После того как вы поместили ваши ассеты в предназначенные им директории, вы можете использовать файлы-манифесты для того, чтобы сказать Rails (с помошью гема Sprockets) как скомбинировать их для того чтобы сформировать один единственный файл. (Это относится к CSS и JavaScript но не к изображениям.) В качестве примера, давайте взглянем на дефолтный манифест файл для таблиц стилей приложения (Листинг 5.15).

Листинг 5.15. Манифест файл для app-specific CSS.
app/assets/stylesheets/application.css
/*
 * This is a manifest file that'll automatically include all the stylesheets
 * available in this directory and any sub-directories. You're free to add
 * application-wide styles to this file and they'll appear at the top of the
 * compiled file, but it's generally better to create a new file per style
 * scope.
 *= require_self
 *= require_tree .
*/

Ключевые строками здесь являются CSS комментарии, но именно они используются Sprockets для включения правильных файлов:

/*
 .
 .
 .
 *= require_self
 *= require_tree .
*/

Здесь

*= require_tree .

обеспечивает включение всех CSS файлов из директории app/assets/stylesheets (в том числе вложенные субдиректории) в CSS приложения. Строка

*= require_self

указывает где в загрузочной последовательности будет включен CSS код самого application.css файла.

Rails поставляется с вполне вменяемыми дефолтными манифест-файлами и в Rails Tutorial мы не будем изменять их, но в случае если вам понадобится их изменить, см. (rus)Rails Guides введение в asset pipeline чтобы узнать об этом подробнее.

Препроцессоры

После того как вы собрали ваши активы, Rails подготавливает их для шаблона сайта, прогоняя их через несколько препроцессоров и комбинирует их с помощью манифест-файлов для доставки в браузер. Мы говорим Rails какой препроцессор использовать с помощью расширения файла; наиболее употребительными являются .scss для Sass, .coffee для CoffeeScript и .erb для встроенного Ruby (ERb). Мы впервые рассказыали о ERb в Разделе 3.3.3 и о Sass в Разделе 5.2.2. В этом учебнике нам не понадобится CoffeeScript. Если вам интересно узнать об этом элегантном маленьком языке который компилируется в JavaScript, советую начать с RailsCast on CoffeeScript basics.

Препроцессоры могут быть сцеплены, например

foobar.js.coffee

прогоняется через препроцессор CoffeeScript, а

foobar.js.erb.coffee

прогоняется и через CoffeeScript и через ERb (справа налево, т.е. CoffeeScript первым).

Производительность в продакшен

Одно из самых замечательных свойств файлопровода, в том что он автоматически приводит к повышению производительности в продакшен приложении. Традиционный подход к организации CSS и JavaScript подразумевает разбиение функциональности между отдельными файлами и использование приятного глазу форматирования (с большим количеством отступов). Что является удобным для программистов, но неэффективно в продакшен; включение множества крупноразмерных файлов может серьезно увеличить время загрузки страниц (а это один из наиболее значимых факторов влияющих на впечатление получаемое пользователями от сайта). С файлопроводом, в продакшен все таблицы стилей приложения заворачиваются в один CSS файл (application.css), весь JavaScript код приложения заворачивается в один JavaScript файл (javascripts.js) и все подобные файлы (включая и те что хранятся в lib/assets и vendor/assets) минимизируются для удаления ненужных пробелов увеличивающих размер файла. В результате мы получаем лучшее для обоих миров: множество хорошо отформатированных файлов для удобства программистов и объединенные в один, оптимизированные файлы для продакшен.

5.2.2 Синтаксически обалденные таблицы стилей

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

Как было вкратце отмечено в Разделе 5.1.2, Sass поддерживает формат называемый SCSS (обозначается расширением файла .scss), который является расширерением самого CSS; то есть, SCSS только добавляет возможности к CSS, вместо определения совершенного синтаксиса.9 Это означает, что любой валидный CSS файл будет валидным SCSS файлом, что удобно для проектов с уже написанным стилем. В нашем случае мы использовали SCSS с самого начала для совместимости с Bootstrap. Поскольку файлопровод в Rails автоматически использует Sass для обработки файлов с .scss расширением, файл custom.css.scss будет пропущен через препроцессор Sass прежде чем он будет упакован для доставки в браузер.

Вложение

Общепринятым паттерном в написании таблиц стилей являются правила применяемые ко вложенным элементам. Например, в Листинге 5.6 у нас есть правило применяемое и для .center и для .center h1:

.center {
  text-align: center;
}

.center h1 {
  margin-bottom: 10px;
}

В Sass мы можем заменить это на

.center {
  text-align: center;
  h1 {
    margin-bottom: 10px;
  }
}

Здесь вложенное правило h1 наследует контекст правила .center.

Здесь есть второй кандидат на вложение, который требует несколько иного синтаксиса. В Листинге 5.8, мы имеем код

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: #fff;
  text-transform: uppercase;
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
  line-height: 1;
}

#logo:hover {
  color: #fff;
  text-decoration: none;
}

Здесь id логотипа #logo используется дважды, в первый раз сам по себе и второй раз с атрибутом hover (который отвечает за его внешний вид при наведении курсора). Для того чтобы вложить второе правило нам необходимо сослаться на родительский элемент #logo; в SCSS это достигается с помощью символа & следующим образом:

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: #fff;
  text-transform: uppercase;
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
  line-height: 1;
  &:hover {
    color: #fff;
    text-decoration: none;
  }
}

Sass заменяет &:hover на #logo:hover при конвертации из SCSS в CSS.

Обе эти техники вложения элементов применяются в CSS подвала в Листинге 5.14, который может быть трансформирован в следующее:

footer {
  margin-top: 45px;
  padding-top: 5px;
  border-top: 1px solid #eaeaea;
  color: #999;
  a {
    color: #555;
    &:hover {
      color: #222;
    }
  }
  small {
    float: left;
  }
  ul {
    float: right;
    list-style: none;
    li {
      float: left;
      margin-left: 10px;
    }
  }
}

Конвертирование Листинга 5.14 вручную это хорошее упражнение и вы должны убедиться что CSS по прежнему работает как надо после преобразования.

Переменные

Sass позволяет нам определять переменные для устранения дублирования и написания более выразительного кода. Для примера, взгляните на Листинг 5.7 и Листинг 5.14, мы видим, что здесь есть повторяющийся вызов одного и того же цвета:

h2 {
  .
  .
  .
  color: #999;
}
.
.
.
footer {
  .
  .
  .
  color: #999;
}

В данном случае, #999 это светло серый цвет, и мы можем дать ему имя, определив переменную следующим образом:

$lightGray: #999;

Это позволяет нам переписать наш SCSS следующим образом:

$lightGray: #999;
.
.
.
h2 {
  .
  .
  .
  color: $lightGray;
}
.
.
.
footer {
  .
  .
  .
  color: $lightGray;
}

Поскольку названия переменных, такие как $lightGray являются более говорящими, нежели #999, часто бывает полезным определять переменные даже для не повторяющихся значений. Действительно, фреймворк Bootstrap определяет большое количество переменных для цветов, вы можете ознакомиться с ними онлайн на странице LESS variables. Эта страница определяеет переменные используя LESS, а не Sass, но гем bootstrap-sass обеспечивает нас их Sass эквивалентами. Угадать соответствие несложно; там где LESS использует знак “at” ( @), Sass использует знак доллара $. Глядя на страницу Bootstrap с переменными, мы видим, что переменная для светло-серого цвета уже существует:

 @grayLight: #999;

Это означает, что, благодаря гему bootstrap-sass должна существовать соответствующая SCSS переменная $grayLight. Мы можем использовать ее для замены нашей кастомной переменной, $lightGray, что дает нам

h2 {
  .
  .
  .
  color: $grayLight;
}
.
.
.
footer {
  .
  .
  .
  color: $grayLight;
}

Применение Sass вложения и определения переменных ко всему SCSS файлу дает нам файл представленный в Листинге 5.16. Это использует Sass переменные (как определенные в Bootstrap) так и встроенные названия цветов (т.е., white для #fff). Обратите внимание, в частности, на кардинальное улучшение в правилах для тега footer.

Листинг 5.16. Начальный SCSS сконвертированный для использования вложенностей и переменных.
app/assets/stylesheets/custom.css.scss
@import "bootstrap";

/* mixins, variables, etc. */

$grayMediumLight: #eaeaea;

/* universal */

html {
  overflow-y: scroll;
}

body {
  padding-top: 60px;
}

section {
  overflow: auto;
}

textarea {
  resize: vertical;
}

.center {
  text-align: center;
  h1 {
    margin-bottom: 10px;
  }
}

/* typography */

h1, h2, h3, h4, h5, h6 {
  line-height: 1;
}

h1 {
  font-size: 3em;
  letter-spacing: -2px;
  margin-bottom: 30px;
  text-align: center;
}

h2 {
  font-size: 1.2em;
  letter-spacing: -1px;
  margin-bottom: 30px;
  text-align: center;
  font-weight: normal;
  color: $grayLight;
}

p {
  font-size: 1.1em;
  line-height: 1.7em;
}


/* header */

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: white;
  text-transform: uppercase;
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
  line-height: 1;
  &:hover {
    color: white;
    text-decoration: none;
  }
}

/* footer */

footer {
  margin-top: 45px;
  padding-top: 5px;
  border-top: 1px solid $grayMediumLight;
  color: $grayLight;
  a {
    color: $gray;
    &:hover {
      color: $grayDarker;
    }
  }
  small {
    float: left;
  }
  ul {
    float: right;
    list-style: none;
    li {
      float: left;
      margin-left: 10px;
    }
  }
}

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

5.3 Ссылки в шаблоне

Теперь, когда мы закончили шаблон сайта с достойным стилем, пришло время приступить к заполнению ссылок которые мы заглушили символом ’#’. Конечно, мы могли бы просто захардкодить ссылки:

<a href="/static_pages/about">About</a>

но это не Rails Way. Было бы хорошо, если бы URL для about страницы был /about, а не /pages/about. Причем, Rails конвенционально использует named routes (именованные маршруты), которые включают в себя код, подобный

<%= link_to "About", about_path %>

Таким образом код обретает более прозрачный смысл и, к тому же, это более гибкий подход, поскольку мы можем изменить определение about_path и URL изменятся везде где используется about_path.

Полный список наших запланированных ссылок представлен в Таблице 5.1, вместе с соответствием URL и маршрутов. Со временем, мы реализуем все ссылки, но предпоследнюю мы добавим только в конце этой главы, а последняя будет создана лишь в Главе 8.

СтраницаURLИменованный маршрут
Home/root_path
About/aboutabout_path
Help/helphelp_path
Contact/contactcontact_path
Sign up/signupsignup_path
Sign in/signinsignin_path
Таблица 5.1: Маршруты и соответствующие им URL для ссылок сайта.

Прежде чем двигаться дальше, давайте добавим страницу Contact (было оставлено в качестве упражнения в Главе 3). Тесты представлены в Листинге 5.17; они просто следуют модели из Листинга 3.19.

Листинг 5.17. Тесты для страницы Contact.
spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "Static pages" do
  .
  .
  .
  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("Ruby on Rails Tutorial Sample App | Contact")
    end
  end
end

Для того чтобы убедиться что оба теста в Листинге 5.17 не проходят, нам нужно закомментировать “Contact” ссылку в подвале, как это показано в Листинге 5.18.

Листинг 5.18. Закоментирование ссылки “Contact” в подвале.
app/views/layouts/_footer.html.erb
<footer class="footer">
  <small>
    <a href="https://railstutorial.org/">Rails Tutorial</a>
    by Michael Hartl
  </small>
  <nav>
    <ul>
      <li><%= link_to "About",   '#' %></li>
      <li><%#= link_to "Contact", '#' %></li>
      <li><a href="http://news.railstutorial.org/">News</a></li>
    </ul>
  </nav>
</footer>

В Листинге 5.18 мы видим как выглядят коментирование кода в Embedded Ruby, что подразумевает помещение знака Ruby-комментария # сразу за знаком процента:

<%#= link_to "Contact", '#' %>

В этой точке тест из Листинга 5.17 должен быть провальным:

$ bundle exec rspec spec/requests/static_pages_spec.rb

Код приложения параллелен коду для добавления страницы About в Разделе 3.2.2: вначале мы обновляем маршруты (Листинг 5.19), затем добавляем contact действие в контроллер StaticPages (Листинг 5.20) и, наконец, мы создаем представление Contact (Листинг 5.21).

Листинг 5.19. Добавление маршрута для страницы Contact.
config/routes.rb
SampleApp::Application.routes.draw do
  get "static_pages/home"
  get "static_pages/help"
  get "static_pages/about"
  get "static_pages/contact"
  .
  .
  .
end
Листинг 5.20. Добавление действия для страницы Contact.
app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
  .
  .
  .
  def contact
  end
end
Листинг 5.21. Представление для страницы 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>

Теперь убедитесь что тесты проходят:

$ bundle exec rspec spec/requests/static_pages_spec.rb

В этой точке, пройдя через Красный-Зеленый цикл для того чтобы убедиться что мы тестируем правильные вещи, нам следует раскомментировать ссылку “Contact” в подвале (Листинг 5.22). Это далеко не идеальное решение так как теперь наши тесты не смогут отловить регрессию если мы случайно удалим тег h1 из Листинга 5.21. Исправление этой ошибки, требующее наприсания теста специально для содержимого h1 оставлено в качестве упражнения (Раздел 5.6).

Листинг 5.22. Раскоментирование ссылки “Contact” в подвале.
app/views/layouts/_footer.html.erb
<footer class="footer">
  <small>
    <a href="https://railstutorial.org/">Rails Tutorial</a>
    by Michael Hartl
  </small>
  <nav>
    <ul>
      <li><%= link_to "About",   '#' %></li>
      <li><%= link_to "Contact", '#' %></li>
      <li><a href="http://news.railstutorial.org/">News</a></li>
    </ul>
  </nav>
</footer>

5.3.1 Тестирование маршрутов

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

visit '/static_pages/about'

на

visit about_path

и так далее для остальных страниц. Результат представлен в Листинге 5.23.

Листинг 5.23. Тесты для именованных маршрутов.
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 root_path
      expect(page).to have_content('Sample App')
    end

    it "should have the base title" do
      visit root_path
      expect(page).to have_title("Ruby on Rails Tutorial Sample App")
    end

    it "should not have a custom page title" do
      visit root_path
      expect(page).not_to have_title('| Home')
    end
  end

  describe "Help page" do

    it "should have the h1 'Help'" do
      visit help_path
      expect(page).to have_content('Help')
    end

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

  describe "About page" do

    it "should have the h1 'About Us'" do
      visit about_path
      expect(page).to have_content('About Us')
    end

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

  describe "Contact page" do

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

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

Как обычно, вам следует проверить, что тесты сейчас покраснели:

$ bundle exec rspec spec/requests/static_pages_spec.rb

Кстати, если код в Листинге 5.23 удивляет вас своими повторениями и многословностью, вы не одиноки. Мы отрефакторим эту грязь в красивую драгоценность в Разделе 5.3.4.

5.3.2 Rails маршруты

Теперь, когда у нас есть нужные нам тесты для URL, пришло время заставить их работать. Как отмечалось в Разделе 3.1, Rails использует для маршрутов файл config/routes.rb. Если вы посмотрите на дефолтный, файл routes.rb, вы увидите беспорядок, но это полезный беспорядок — полный закомментированных примеров маршрутов. Я советую почитать его как-нибудь, а также взглянуть на статью о маршрутах в (rus)Rails Guides для более углубленного изучения маршрутов.

Для того чтобы определить именованные маршруты нам необходимо заменить такие маршруты как

get 'static_pages/help'

на

match '/help', to: 'static_pages#help', via: 'get'

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

Применение этого паттерна к остальным статическим страницам приводит нас к Листингу 5.24. Единственным исключением здесь является страница Home, о которой мы позаботимся в Листинге 5.26.

Листинг 5.24. Маршруты для статических страниц.
config/routes.rb
SampleApp::Application.routes.draw do
  match '/help',    to: 'static_pages#help',    via: 'get'
  match '/about',   to: 'static_pages#about',   via: 'get'
  match '/contact', to: 'static_pages#contact', via: 'get'
  .
  .
  .
end

Если вы внимательно почитаете код в Листинге 5.24, вы возможно сможете понять что он делает; например, вы можете увидеть что

match '/about', to: 'static_pages#about',  via: 'get'

устанавливает соответствие между GET запросом к ’/about’ и именованным маршрутом, а также направляет его в about действие контроллера StaticPages. Прежде это было более очевидно: мы использовали

get 'static_pages/about'

для того чтобы попасть в то же место, но /about более краток. В дополнение, как отмечалось выше, код match ’/about’ также автоматически создает именованные маршруты для использования в контроллерах и представлениях:

about_path -> '/about'
about_url  -> '//localhost:3000/about'

Обратите внимание, что about_url это полный URL //localhost:3000/about (localhost:3000 заменится на доменное имя, такое как example.com, для развернутого сайта). Как обсуждалось в Разделе 5.3, для того чтобы получить просто /about, вы используете about_path. (Rails Tutorial использует path форму по соглашению, но разница редко имеет значение на практике (т.е также возможно применение _url формы).)

После определения этих маршрутов тесты для About, Contact, и Help страниц должны пройти:

$ bundle exec rspec spec/requests/static_pages_spec.rb

Это оставляет один провальный тест для страницы Home.

Для маршрута Home страницы мы могли бы использовать код вроде этого:

match '/', to: 'static_pages#home', via: 'get'

Однако делать этого не придется; Rails имеет специальный маршрут для корневого URL / (“слэш”) расположенный внизу файла (Листинг 5.25).

Листинг 5.25. Закомментированная подсказка для определения root маршрута.
config/routes.rb
SampleApp::Application.routes.draw do
  .
  .
  .
  # You can have the root of your site routed with "root"
  # root 'welcome#index'
  .
  .
  .
end

Используя Листинг 5.25 как модель, мы приходим к Листингу 5.26 чтобы направить корневой URL / к Home странице.

Листинг 5.26. Добавление соответствия для root маршрута.
config/routes.rb
SampleApp::Application.routes.draw do
  root  'static_pages#home'
  match '/help',    to: 'static_pages#help',    via: 'get'
  match '/about',   to: 'static_pages#about',   via: 'get'
  match '/contact', to: 'static_pages#contact', via: 'get'
  .
  .
  .
end

Этот код направляет корневой URL / к /static_pages/home, а это означает что //localhost:3000/ наконец то покажет нечто иное нежели дефолтную Rails страницу из Рис. 1.3. Он также дает нам следующие URL хелперы:

root_path -> '/'
root_url  -> '//localhost:3000/'

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

$ bundle exec rspec spec/requests/static_pages_spec.rb

Теперь нам осталось просто заполнить ссылки в макете.

5.3.3 Именованные маршруты

Давайте применим именованные маршруты, созданные в Разделе 5.3.2. Это повлечет за собой заполнение второго аргумента link_to функции правильным именованным маршрутом. Например, мы конвертируем

<%= link_to "About", '#' %>

в

<%= link_to "About", about_path %>

и так далее.

Мы начнем с партиала _header.html.erb (Листинг 5.27), который содержит ссылки на Home и Help страницы. Пока мы здесь, мы также, следуя общей конвенции Сети, свяжем логотип с Home страницей.

Листинг 5.27. Партиал _header со ссылками.
app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="navbar-inner">
    <div class="container">
      <%= link_to "sample app", root_path, id: "logo" %>
      <nav>
        <ul class="nav pull-right">
          <li><%= link_to "Home",    root_path %></li>
          <li><%= link_to "Help",    help_path %></li>
          <li><%= link_to "Sign in", '#' %></li>
        </ul>
      </nav>
    </div>
  </div>
</header>

У нас не будет именованного маршрута для “Sign in” ссылки, до Главы 8, поэтому мы пока оставили ее в виде ’#’.

Другим местом со ссылками является партиал подвала, _footer.html.erb, в котором есть ссылки для About и Contact страниц (Листинг 5.28).

Листинг 5.28. Партиал _footer со ссылками.
app/views/layouts/_footer.html.erb
<footer class="footer">
  <small>
    <a href="https://railstutorial.org/">Rails Tutorial</a>
    by Michael Hartl
  </small>
  <nav>
    <ul>
      <li><%= link_to "About",   about_path %></li>
      <li><%= link_to "Contact", contact_path %></li>
      <li><a href="http://news.railstutorial.org/">News</a></li>
    </ul>
  </nav>
</footer>

Теперь наш макет имеет ссылки на все статические страницы созданные в Главе 3, так, например, /about ведет к About странице (Рис. 5.8).

Кстати, стоит отметить, что мы на самом деле не протестировали на наличие ссылок в шаблоне, наши тесты будут провальными, только если маршруты не определены. Вы можете проверить это, закомментировав один из маршрутов в Листинге 5.24 и запустив ваш набор тестов. Метод тестирования который на самом деле обеспечивает уверенность что ссылки существуют и отправляют в правильные места, см. в Разделе 5.6.

about_page_styled
Рис. 5.8: About страница на /about(полный размер)

5.3.4 Приятный RSpec

Мы отметили в Разделе 5.3.1 что тесты для статических страниц стали немного многословными и наполнились повторениями (Листинг 5.23). В этом разделе мы применим самые последние фишки RSpec для того чтобы сделать наши тесты более компактными и элегантными.

Давайте взглянем на пару примеров чтобы увидеть как они могут быть улучшены:

describe "Home page" do

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

  it "should have the base title" do
    visit root_path
    expect(page).to have_title("Ruby on Rails Tutorial Sample App")
  end

  it "should not have a custom page title" do
    visit root_path
    expect(page).not_to have_title('| Home')
  end
end

Здесь стоит отметить одну вещь - все три примера включают посещение root_path. Мы можем устранить это дублирование с помощью блока before:

describe "Home page" do
  before { visit root_path }

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

  it "should have the base title" do
    expect(page).to have_title("Ruby on Rails Tutorial Sample App")
  end

  it "should not have a custom page title" do
    expect(page).not_to have_title('| Home')
  end
end
Здесь используется строка

before { visit root_path }

для того чтобы посещать root_path перед каждым примером. (Метод before можно также вызвать спомощью синонима before(:each).)

Есть еще один источник повторений в каждом примере; у нас есть

it "should have the content 'Sample App'" do

и

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

которые, по сути, одно и то же. К тому же, оба примера ссылаются на переменную page. Мы можем устранить это дублирование сказав RSpec-y что page это субъект тестирования с помощью

subject { page }

а затем применив вариант метода it для того чтобы собрать код и описание (теста) в одну строку:

it { should have_content('Sample App') }

Благодаря subject { page }, вызов should автоматически использует переменную page предоставленную гемом Capybara (Раздел 3.2.1).

Применение этих изменений дает нам гораздо более компактные тесты для страницы Home:

  subject { page }

  describe "Home page" do
    before { visit root_path }

    it { should have_content('Sample App') }
    it { should have_title("Ruby on Rails Tutorial Sample App") }
    it { should_not have_title('| Home') }
  end

Этот код выглядит лучше, но тест заголовка по-прежнему длинноват. Кроме того, большая часть теста заголовка в Листинге 5.23 содержит длиный текст заголовка вида

"Ruby on Rails Tutorial Sample App | About"

Упражнение в Разделе 3.5 предлагает вам устранить часть этого дублирования посредством определения переменной base_title и интерполяции строк (Листинг 3.31). Мы можем сделать еще лучше, определив full_title, который является параллелью хелпера full_title из Листинга 4.2. Мы сделаем это создав директорию spec/support и файл utilities.rb для RSpec-утилит (Листинг 5.29).

Листинг 5.29. Файл для утилит RSpec с функцией full_title.
spec/support/utilities.rb
def full_title(page_title)
  base_title = "Ruby on Rails Tutorial Sample App"
  if page_title.empty?
    base_title
  else
    "#{base_title} | #{page_title}"
  end
end

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

Файлы в spec/support директории автоматически включаются RSpec, это означает что мы можем написать тесты для Home страницы следующим образом:

  subject { page }

  describe "Home page" do
    before { visit root_path }

    it { should have_content('Sample App') }
    it { should have_title(full_title('')) }
    it { should_not have_title('| Home') }
  end

Теперь мы можем упростить тесты для страниц Help, About и Contact используя теже методы что и для страницы Home. Результаты представлены в Листинге 5.30.

Листинг 5.30. Улучшение тестов для статических страниц.
spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "Static pages" do

  subject { page }

  describe "Home page" do
    before { visit root_path }

    it { should have_content('Sample App') }
    it { should have_title(full_title('')) }
    it { should_not have_title('| Home') }
  end

  describe "Help page" do
    before { visit help_path }

    it { should have_content('Help') }
    it { should have_title(full_title('Help')) }
  end

  describe "About page" do
    before { visit about_path }

    it { should have_content('About') }
    it { should have_title(full_title('About Us')) }
  end

  describe "Contact page" do
    before { visit contact_path }

    it { should have_content('Contact') }
    it { should have_title(full_title('Contact')) }
  end
end

Теперь необходимо убедиться что тесты по-прежнему проходят:

$ bundle exec rspec spec/requests/static_pages_spec.rb

Стиль RSpec из Листинга 5.30 намного более лаконичен нежели стиль в Листинге 5.23—однако, он может быть еще более лаконичным (Раздел 5.6). При дальнейшей разработке примера приложения мы будем использовать этот более компактный стиль везде, где это только будет возможно.

5.4 Регистрация пользователей: первый шаг

В качестве кульминации нашей работы над макетом и маршрутами, в этом разделе мы сделаем маршрут для страницы регистрации (signup), что будет означать создание второго контроллера. Это первый важный шаг к предоставлению пользователям возможности регистрироваться на нашем сайте, мы сделаем следующий шаг, моделирование пользователя, в Главе 6 и мы закончим работу в Главе 7.

5.4.1 Контроллер Users

Мы создали наш первый контроллер - StaticPages - еще в Разделе 3.1. Пришло время создать второй — контроллер Users. Как и прежде, мы будем использовать generate для создания простейшего контроллера, который отвечает нашим текущим потребностям, а именно, с одной страницей-заглушкой для регистрации новых пользователей. Следуя конвенции REST архитектуры предпочитаемой Рельсами, мы назовем действие для новых пользователей, new и передадим его в качестве аргумента в generate controller для создания его (действия) автоматически (Листинг 5.31).

Листинг 5.31. Генерация контроллера Users (с new действием).
$ rails generate controller Users new --no-test-framework
      create  app/controllers/users_controller.rb
       route  get "users/new"
      invoke  erb
      create    app/views/users
      create    app/views/users/new.html.erb
      invoke  helper
      create    app/helpers/users_helper.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/users.js.coffee
      invoke    scss
      create      app/assets/stylesheets/users.css.scss

Это создает контроллер Users с new действием (Листинг 5.32) и заглушку представления (Листинг 5.33).

Листинг 5.32. Начальный контроллер Users с new действием.
app/controllers/users_controller.rb
class UsersController < ApplicationController
  def new
  end

end
Листинг 5.33. Начальное new действие для Users.
app/views/users/new.html.erb
<h1>Users#new</h1>
<p>Find me in app/views/users/new.html.erb</p>

5.4.2 URL для регистрации

С кодом из Раздела 5.4.1 у нас уже есть рабочая страница для создания новых пользователей на /users/new, но вспомним из Таблицы 5.1, что вместо этого нам необходим URL /signup. Как и в Разделе 5.3, мы начнем с написания интеграционных тестов, которые мы сейчас сгенерируем:

$ rails generate integration_test user_pages

Затем, следуя модели спеков для статических страниц в Листинге 5.30, мы заполняем тесты для страниц пользователей кодом тестирующим содержимое h1 и title тегов как это показано в Листинге 5.34.

Листинг 5.34. начальный спек для пользователей с тестом для страницы регистрации.
spec/requests/user_pages_spec.rb
require 'spec_helper'

describe "User pages" do

  subject { page }

  describe "signup page" do
    before { visit signup_path }

    it { should have_content('Sign up') }
    it { should have_title(full_title('Sign up')) }
  end
end

Мы можем запустить эти тесты командой rspec как обычно:

$ bundle exec rspec spec/requests/user_pages_spec.rb

Стоит отметить что мы также можем запустить все интеграционные тесты передав в качестве параметра всю директорию вместо одного файла:

$ bundle exec rspec spec/requests/

Опираясь на этот паттерн вы можете догадаться как запустить все спеки:

$ bundle exec rspec spec/

Обычно мы будем использовать именно этот способ запуска тестов в остальной части учебника. Кстати, стоит отметить, что вы можете также запускать набор тестов с помощью Rake задачи spec:

$ bundle exec rake spec

(Фактически, вы можете просто набрать rake; дефолтное поведение rake это запуск набора тестов.) Единственное 'но' - использование rake для запуска тестов примера приложения в его текущем состоянии вызовет ошибку т.к. этой команде требуется правильно подготовленная тестовая база данных; этот шаг мы отложили до Раздела 6.2.1.

По конструкции, контроллер Users уже имеет new действие, так что все что нам необходимо для прохождения тестов это правильные маршрут и контент представления. Мы будем следовать примерам из Листинга 5.24 и добавим match ’/signup’ правило для URL регистрации (Листинг 5.35).

Листинг 5.35. Маршрут для страницы регистрации.
config/routes.rb
SampleApp::Application.routes.draw do
  get "users/new"

  root  'static_pages#home'
  match '/signup',  to: 'users#new',            via: 'get'
  match '/help',    to: 'static_pages#help',    via: 'get'
  match '/about',   to: 'static_pages#about',   via: 'get'
  match '/contact', to: 'static_pages#contact', via: 'get'
  .
  .
  .
end

Обратите внимание, мы сохранили правило get "users/new", которое было сгенерировано автоматически при создании Users контроллера в Листинге 5.31. В настоящее время это правило необходимо для работы адреса ’users/new’, но оно не соответствует REST конвенции (Таблица 2.2) и мы избавимся от него в Разделе 7.1.2.

Теперь все что нам теперь необходимо для прохождения тестов это представление с заголовком браузера и заголовком первого уровня “Sign up” (Листинг 5.36).

Листинг 5.36. Начальная страница регистрации (заглушка).
app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<p>Find me in app/views/users/new.html.erb</p>

Теперь тест страницы регистрации из Листинга 5.34 должен пройти. Нам осталось лишь добавить соответствующую ссылку к кнопке на Home странице. Как и с прочими маршрутами, match ’/signup’ дает нам именованный маршрут signup_path, который мы применяем в Листинге 5.37.

Листинг 5.37. Добавление ссылки к кнопке Signup.
app/views/static_pages/home.html.erb
<div class="center hero-unit">
  <h1>Welcome to the Sample App</h1>

  <h2>
    This is the home page for the
    <a href="https://railstutorial.org/">Ruby on Rails Tutorial</a>
    sample application.
  </h2>

  <%= link_to "Sign up now!", signup_path, class: "btn btn-large btn-primary" %>
</div>

<%= link_to image_tag("rails.png", alt: "Rails"), 'https://rubyonrails.org/' %>

С этим работу над ссылками и именованными маршрутами можно считать законченной, по крайней мере до тех пор, пока мы не добавим маршрут для входа на сайт (Глава 8). Получившаяся страница для регистрации пользователей (на URL /signup) представлена на Рис. 5.9.

new_signup_page_bootstrap
Рис. 5.9: Страница регистрации (/signup). (полный размер)

В этой точке тесты должны проходить:

$ bundle exec rspec spec/

5.5 Заключение

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

Если вы используете Git, вам следует объединить изменения с мастер-веткой:

$ git add .
$ git commit -m "Finish layout and routes"
$ git checkout master
$ git merge filling-in-layout

Также вы можете отправить изменения на GitHub:

$ git push

Наконец, вы можете развернуть приложение на Heroku:

$ git push heroku

Результатом должно стать работающий пример приложения на продакшен сервере:

$ heroku open

Если столкнулись с проблемами, попробуйте выполнить

$ heroku logs

чтобы узнать об ошибках с помощью логов Heroku.

5.6 Упражнения

  1. Мы видели в Разделе 5.3 что Rspec-матчер have_content иногда работает слишком общО что приводит нас к временному закомментированию строки “Contact” в футере во избежание ошибочно-проходящего теста (Листинг 5.18). Для того чтобы исправить это Листинг 5.38 вводит предоставляемый Capybara метод have_selector который позволяет нам тестировать наличие отдельных HTML тегов. Удаляя и возвращая на место “Contact” h1 в Листинге 5.21, убедитесь что код в Листинге 5.38 выдает провальный тест даже если подвал не закомментирован.
  2. Код для тестирования статических страниц в Листинге 5.30 компактен, но по прежнему имеет повторения. RSpec поддерживает возможность называемую shared examples предназначенную для устранения такого вида дублирования. Следуя примеру в Листинге 5.39, заполните пропущенные тесты для страниц Help, About и Contact. Обратите внимание - команда let кратко обсуждаемая в Листинге 3.31, создает локальную переменную с данным значением по запросу (т.е. при использовании переменной), в отличие от переменной экземпляра, которая создается в момент назначения. (Листинг 5.39 также использует have_selector из Листинга 5.38.)
  3. Возможно вы заметили что наши тесты для ссылок шаблона тестируют маршруты, но на самом деле не проверяют, ведут ли ссылки к правильным страницам. Один из способов реализации этих испытаний заключается в использовании visit и click_link внутри RSpec интеграционного теста. Дополните код из Листинга 5.40 чтобы проверить что все ссылки шаблона правильно определены.
  4. Устраните необходимость в тестовом хелпере full_title в Листинге 5.29 написав тест для оригинального вспомогательного метода, как это показано в Листинге 5.41. (Вам необходимо будет создать директорию spec/helpers и файл application_helper_spec.rb.) Затем include его в тест с помощью кода в Листинге 5.42. Проверьте, запустив тесты, что новый код работает. Примечание: Листинг 5.41 использует регулярные выражения о которых мы узнаем больше в Разделе 6.2.4. (Спасибо Alex Chaffee за совет и код используемый в этом упражнении.)
Листинг 5.38. Более основательный тест для страницы Contact.
spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "Static pages" do
  .
  .
  .
  describe "Contact page" do
    before { visit contact_path }

    it { should have_selector('h1', text: 'Contact') }
    it { should have_title(full_title('Contact')) }
  end
end
Листинг 5.39. Применение RSpec shared example для устранения дублирования в тестах.
spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "Static pages" do

  subject { page }

  shared_examples_for "all static pages" do
    it { should have_selector('h1', text: heading) }
    it { should have_title(full_title(page_title)) }
  end

  describe "Home page" do
    before { visit root_path }
    let(:heading)    { 'Sample App' }
    let(:page_title) { '' }

    it_should_behave_like "all static pages"
    it { should_not have_title('| Home') }
  end

  describe "Help page" do
    .
    .
    .
  end

  describe "About page" do
    .
    .
    .
  end

  describe "Contact page" do
    .
    .
    .
  end
end
Листинг 5.40. Тест для ссылок в шаблоне.
spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "Static pages" do
  .
  .
  .
  it "should have the right links on the layout" do
    visit root_path
    click_link "About"
    expect(page).to have_title(full_title('About Us'))
    click_link "Help"
    expect(page).to # заполнить
    click_link "Contact"
    expect(page).to # заполнить
    click_link "Home"
    click_link "Sign up now!"
    expect(page).to # заполнить
    click_link "sample app"
    expect(page).to # заполнить
  end
end
Листинг 5.41. Тесты для хелпера full_title из application_helper.rb.
spec/helpers/application_helper_spec.rb
require 'spec_helper'

describe ApplicationHelper do

  describe "full_title" do
    it "should include the page title" do
      expect(full_title("foo")).to match(/foo/)
    end

    it "should include the base title" do
      expect(full_title("foo")).to match(/^Ruby on Rails Tutorial Sample App/)
    end

    it "should not include a bar for the home page" do
      expect(full_title("")).not_to match(/\|/)
    end
  end
end
Листинг 5.42. Замена тестового хелпера full_title на простой include.
spec/support/utilities.rb
include ApplicationHelper
  1. Спасибо читателю Colm Tuite за его замечательную работу по переводу примера приложения на CSS фреймворк Bootstrap. 
  2. Каркасы в Ruby on Rails Tutorial сделаны с помощью замечательного онлайн приложения для создания каркасов, называемого Mockingbird
  3. Они абсолютно не связаны с классами Ruby. 
  4. Вы моли заметить, что img tag, вместо сходства с <img>...</img>, более похож на <img ... />. Теги, соответствующие этой форме, известны как самозакрывающиеся
  5. Также возможно использовать LESS с файлопроводом; см. подробности на GitHub странице гема less-rails-bootstrap
  6. Многие Rails разработчики используют shared директорию для партиалов совместно используемых разными представлениями. Я предпочитаю использовать shared папку для "служебных" партиалов которые используются во множестве страниц, помещая партиалы, которые используются буквально на каждой странице (такие как части шаблона сайта) в layouts директорию. (Мы создадим shared директорию в Главе 7.) Это кажется мне более логичным, но помещение всех партиалов в папку shared тоже замечательно работает. 
  7. Вы, возможно, удивлены тем, что мы используем и тег footer и класс .footer. Ответ заключается в том, что тег имеет понятное значение для читателя, а класс необходим для Bootstrap. Тег div вместо footer тоже будет работать. 
  8. Структура этого раздела опирается на замечательную статью The Rails 3 Asset Pipeline in (about) 5 Minutes от Michael Erasmus. Более подробно о файлопроводе см. (rus)Rails Guide on the Asset Pipeline
  9. Более старый .sass формат, также поддерживаемый Sass, определяет новый язык который является менее многословным (и в нем нет фигурных скобок), но менее удобным для существующих проектов и его сложнее изучать тем кто уже знаком с CSS.