Ruby on Rails Tutorial
Изучение Веб Разработки на Rails
Michael Hartl
Содержание
- Предисловие к русскому изданию
- Глава 1 От нуля к развертыванию
- Глава 2 demo app
- Глава 3 В основном статические страницы
- Глава 4 Rails-приправленный Ruby
- Глава 5 Заполнение шаблона
- Глава 6 Моделирование пользователей
- Глава 7 Регистрация
- Глава 8 Войти, выйти
- Глава 9 Обновление, демонстрация и удаление пользователей
- Глава 10 Микросообщения пользователей
- Глава 11 Слежение за сообщениями пользователей
Предисловие
Моя компания (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. Он отличается в некоторых деталях, но это нормально, так как каркас и не должен быть абсолютно точным.
Как обычно, если вы используете Git для управления версиями, то сейчас самое время создать новую ветку:
$ git checkout -b filling-in-layout
5.1.1 Навигация по сайту
В качестве первого шага к добавлению ссылок и стилей к примеру приложения, мы добавим в файл шаблона сайта application.html.erb
(последний раз мы его видели в Листинге 4.3) дополнительные HTML структуры. Это подразумевает добавление нескольких дополнительных разделителей, немного CSS классов и запуск навигации по сайту. Полный файл представлен в Листинге 5.1; объяснения различных частей последуют сразу за ним. Если вы не хотите откладывать удовольствие, вы можете посмотреть на результаты представленные на Рис. 5.2. (Примечание: это (пока) не очень приятно.)
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<%= stylesheet_link_tag "application", media: "all" %>
<%= javascript_include_tag "application" %>
<%= 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>
Первое что стоит отметить это переход от старого Ruby 1.8–стиля хэшей к новому, Ruby 1.9 - стилю (Раздел 4.3.3). Здесь,
<%= stylesheet_link_tag "application", :media => "all" %>
было заменено на
<%= stylesheet_link_tag "application", media: "all" %>
Важно понимать что старый синтаксис хэшей имеет глубокие корни и вы должны уметь распознавать оба варианта.
Давайте просмотрим другие новые элементы Листинга 5.1. Как вкратце отмечалось в Разделе 3.1, Rails 3 по умолчанию использует 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
это текст ссылки, а второй это URI. Мы заменим URI на именованные маршруты в Разделе 5.3.3, но сейчас мы используем заглушки URI ’#’
обычно применяемые в веб-дизайне. Третий аргумент это хэш опций, в данном случае добавление 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).
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"), 'http://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”.
Теперь мы, наконец, готовы увидеть плоды наших трудов (Рис. 5.2). Говорите "Не впечатляет"? Может быть. К счастью, однако, мы проделали хорошую работу, прописав для наших HTML элементов внятные классы и идентификаторы, которые позволят нам отстилить наш сайт с помощью CSS.
Кстати, вы возможно были удивлены, обнаружив что изображение rails.png
уже существует. Откуда оно взялось? Оно бесплатно включено в каждое новое Rails приложение и вы найдете его в app/assets/images/rails.png
. Поскольку мы используем хелпер image_tag
, Rails находит его автоматически с помощью файлопровода (Раздел 5.2).
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
Gemfile
.
source 'https://rubygems.org'
gem 'rails', '3.2.13'
gem 'bootstrap-sass', '2.1'
.
.
.
Для установки Bootstrap мы, как обычно запускаем bundle install
:
$ bundle install
Затем рестартуем веб-сервер для того чтобы изменения вступили в силу. (На большинстве систем рестарт сервера подразумевает нажатие Ctrl-C и последующий запуск rails server
.)
Первый шаг в добавлении кастомных CSS к нашему приложению это создание файла для их хранения:
app/assets/stylesheets/custom.css.scss
(Используйте ваш текстовый редактор или IDE для создания нового файла.) В данном случае важными являются и название директории и название файла. Директория
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.4.
app/assets/stylesheets/custom.css.scss
@import "bootstrap";
Эта единственная строка включает весь Bootstrap CSS фреймворк, результат показан на Рис. 5.3. (Вам, возможно, придется рестартовать веб сервер. Стоит также отметить что скриншоты используют Bootstrap 2.0, в то время как учебник использует Bootstrap 2.1, так что могут быть небольшие отличия во внешнем виде, но это не повод для беспокойства.) Текст расположен плохо и логотип оказался неотстиленым, но цвета и кнопка регистрации выглядят многообещающе.
Затем мы добавим немного CSS который будет использован для стилизации шаблона и каждой отдельной страницы, как это показано в Листинге 5.5. В Листинге 5.5 довольно много правил; для того чтобы понять что делает CSS правило, часто бывает полезным закомментировать его с помощью CSS комментариев, т.e., поместив его внутрь /* … */
и посмотреть что изменилось.
Результат CSS из Листинга 5.5 показан на Рис. 5.4.
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;
}
Обратите внимание что CSS в Листинге 5.5 имеют последовательный вид. В целом, 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.7, знак фунта #
указывает на правило для стилизации CSS id.) Это означает что этот элемент внутри любого тега (такого как div
) с классом center
будет отцентрован на странице. (Мы увидим пример этого класса в Листинге 5.2.)
Хотя Bootstrap поставляется с CSS правилами для приятной типографики, мы также добавим немного кастомных правил для представления текста на нашем сайте, как это показано в Листинге 5.6. (Не все эти правила применяются к странице Home page, но каждое правило будет использовано в какой-то части примера приложения.) Результат Листинга 5.6 показан на Рис. 5.5.
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.7em;
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”. CSS в Листинге 5.7 конвертирует текст в верхний регистр и изменяет его размер, цвет и положение. (Мы использовали CSS id потому что предполагаем что логотип сайта будет на странице в единственном экземпляре, но вы можете использовать класс вместо него.)
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.7 показан на Рис. 5.6.
5.1.3 Частичные шаблоны (partials)
Хотя шаблон в Листинге 5.1 и выполняет свою работу, но он становится немного суматошным. HTML shim занимает три строки и использует странный IE-специфичный синтаксис, и было бы хорошо вынести его в какое нибудь отдельное место. К тому же заголовок формирует отдельную логическую единицу и он весь должен быть упакован в одном месте. Мы можем достигнуть этого, используя удобное Rails-приспособление называемое partials (# партиалы, частичные шаблоны). Давайте сначала посмотрим, как шаблон выглядит после определения партиалов (Листинг 5.8).
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<%= stylesheet_link_tag "application", media: "all" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
<%= render 'layouts/shim' %>
</head>
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
</div>
</body>
</html>
В Листинге 5.8 мы заменили 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.9.
app/views/layouts/_shim.html.erb
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
Аналогичным образом, мы можем переместить материал header-а в частичный шаблон показанный в Листинге 5.10 и вставить его в шаблон другим вызовом render
.
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.11).7
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 страницы и заглушили URI-адреса символом ’#’
до лучших времен. (Как и header
, footer
- новый, HTML5, тег.)
Мы можем вставить партиал подвала в шаблон тем же образом, что и shim и header партиалы (Листинг 5.12).
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<%= stylesheet_link_tag "application", media: "all" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
<%= render 'layouts/shim' %>
</head>
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
<%= render 'layouts/footer' %>
</div>
</body>
</html>
Конечно, подвал будет уродливым, если его немного не отстилить (Листинг 5.13). Результаты представлены на Рис. 5.7.
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;
}
5.2 Sass и файлопровод (asset pipeline)
Одним из наиболее заметных различий между Rails 3.0 и более поздними версиями является файлопровод (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, существуют три каноничные директории для статических ассетов, каждая со своим собственным назначением:
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.14).
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.5 у нас есть правило применяемое и для .center
и для .center h1
:
.center {
text-align: center;
}
.center h1 {
margin-bottom: 10px;
}
В Sass мы можем заменить это на
.center {
text-align: center;
h1 {
margin-bottom: 10px;
}
}
Здесь вложенное правило h1
наследует контекст правила .center
.
Здесь есть второй кандидат на вложение, который требует несколько иного синтаксиса. В Листинге 5.7, мы имеем код
#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.13, который может быть трансформирован в следующее:
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.13 вручную это хорошее упражнение и вы должны убедиться что CSS по прежнему работает как надо после преобразования.
Переменные
Sass позволяет нам определять переменные для устранения дублирования и написания более выразительного кода. Для примера, взгляните на Листинг 5.6 и Листинг 5.13, мы видим, что здесь есть повторяющийся вызов одного и того же цвета:
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.15. Это использует Sass переменные (как определенные в Bootstrap) так и встроенные названия цветов (т.е., white
для #fff
). Обратите внимание, в частности, на кардинальное улучшение в правилах для тега footer
.
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.7em;
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.15 использует наиболее важные из них и дает нам хороший старт. Более подробно об этом см. на сайте Sass.
5.3 Ссылки в шаблоне
Теперь, когда мы закончили шаблон сайта с достойным стилем, пришло время приступить к заполнению ссылок которые мы заглушили символом ’#’
. Конечно, мы могли бы просто захардкодить ссылки:
<a href="/static_pages/about">About</a>
но это не Rails Way. Было бы хорошо, если бы URI для about страницы был /about, а не /pages/about; причем, Rails конвенционально использует named routes (именованные маршруты), которые включают в себя код, подобный
<%= link_to "About", about_path %>
Таким образом код обретает более прозрачный смысл и, к тому же, это более гибкий подход, поскольку мы можем изменить определение about_path
и URI изменятся везде где используется about_path
.
Полный список наших запланированных ссылок представлен в Таблице 5.1, вместе с соответствием URI и маршрутов. Со временем, мы реализуем все ссылки, но предпоследнюю мы добавим только в конце этой главы, а последняя будет создана лишь в Главе 8.
Page | URI | Named route |
---|---|---|
Home | / | root_path |
About | /about | about_path |
Help | /help | help_path |
Contact | /contact | contact_path |
Sign up | /signup | signup_path |
Sign in | /signin | signin_path |
Прежде чем двигаться дальше, давайте добавим страницу Contact (было оставлено в качестве упражнения в Главе 3). Тесты представлены в Листинге 5.16; они просто следуют модели из Листинга 3.18. Обратите внимание на то что в коде приложения в Листинге 5.16 мы переключились на Ruby 1.9 – стиль хэшей.
spec/requests/static_pages_spec.rb
require 'spec_helper'
describe "Static pages" do
.
.
.
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: "Ruby on Rails Tutorial Sample App | Contact")
end
end
end
Вам следует проверить что эти тесты в красном:
$ bundle exec rspec spec/requests/static_pages_spec.rb
Код приложения параллелен коду для добавления страницы About в Разделе 3.2.2: вначале мы обновляем маршруты (Листинг 5.17), затем добавляем contact
действие в контроллер StaticPages (Листинг 5.18) и, наконец, мы создаем представление Contact (Листинг 5.19).
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
app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
.
.
.
def contact
end
end
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
5.3.1 Тестирование маршрутов
С работой которую мы проделали при написании интеграционных тестов для статических страниц, написание тестов для маршрутов довольно простая задача: мы просто заменяем каждое упоминание жестко прописанных адресов на соответствующие именованные маршруты из Таблицы 5.1. Другими словами, мы меняем
visit '/static_pages/about'
на
visit about_path
и так далее для остальных страниц. Результат представлен в Листинге 5.20.
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
page.should have_selector('h1', text: 'Sample App')
end
it "should have the base title" do
visit root_path
page.should have_selector('title',
text: "Ruby on Rails Tutorial Sample App")
end
it "should not have a custom page title" do
visit root_path
page.should_not have_selector('title', text: '| Home')
end
end
describe "Help page" do
it "should have the h1 'Help'" do
visit help_path
page.should have_selector('h1', text: 'Help')
end
it "should have the title 'Help'" do
visit help_path
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'" do
visit about_path
page.should have_selector('h1', text: 'About Us')
end
it "should have the title 'About Us'" do
visit about_path
page.should have_selector('title',
text: "Ruby on Rails Tutorial Sample App | About Us")
end
end
describe "Contact page" do
it "should have the h1 'Contact'" do
visit contact_path
page.should have_selector('h1', text: 'Contact')
end
it "should have the title 'Contact'" do
visit contact_path
page.should have_selector('title',
text: "Ruby on Rails Tutorial Sample App | Contact")
end
end
end
Как обычно, вам следует проверить, что тесты сейчас покраснели:
$ bundle exec rspec spec/requests/static_pages_spec.rb
Кстати, если код в Листинге 5.20 удивляет вас своими повторениями и многословностью, вы не одиноки. Мы отрефакторим эту грязь в красивую драгоценность в Разделе 5.3.4.
5.3.2 Rails маршруты
Теперь, когда у нас есть нужные нам тесты для URI, пришло время заставить их работать. Как отмечалось в Разделе 3.1.2, Rails использует для маршрутов файл config/routes.rb
. Если вы посмотрите на дефолтный, файл routes.rb, вы увидите беспорядок, но это полезный беспорядок — полный закомментированных примеров маршрутов. Я советую почитать его как-нибудь, а также взглянуть на статью о маршрутах в (rus)Rails Guides для более углубленного изучения маршрутов.
Для того чтобы определить именованные маршруты нам необходимо заменить такие маршруты как
get 'static_pages/help'
на
match '/help', to: 'static_pages#help'
Это дает нам два варианта возможных варианта написания пути к странице help: /help
и именованный маршрут help_path
который возвращает путь к этой странице. (На самом деле использование get
вместо match
даст тот же именованнный маршрут, но использование match
более принято.)
Применение этого паттерна к остальным статическим страницам приводит нас к Листингу 5.21. Единственным исключением здесь является страница Home, о которой мы позаботимся в Листинге 5.23.
config/routes.rb
SampleApp::Application.routes.draw do
match '/help', to: 'static_pages#help'
match '/about', to: 'static_pages#about'
match '/contact', to: 'static_pages#contact'
.
.
.
end
Если вы внимательно почитаете код в Листинге 5.21, вы возможно сможете понять что он делает; например, вы можете увидеть что
match '/about', to: 'static_pages#about'
устанавливает соответствие между ’/about’
и именованным маршрутом, а также направляет его в about
действие контроллера StaticPages. Прежде это было более очевидно: мы использовали
get 'static_pages/about'
для того чтобы попасть в то же место, но /about
более краток. В дополнение, как отмечалось выше, код match ’/about’
также автоматически создает именованные маршруты для использования в контроллерах и представлениях:
about_path => '/about'
about_url => '//localhost:3000/about'
Обратите внимание, что about_url
это полный URI //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'
Однако делать этого не придется; Rails имеет специальный маршрут для корневого URI / (“слэш”) расположенный внизу файла (Листинг 5.22).
config/routes.rb
SampleApp::Application.routes.draw do
.
.
.
# You can have the root of your site routed with "root"
# just remember to delete public/index.html.
# root :to => "welcome#index"
.
.
.
end
Используя Листинг 5.22 как модель, мы приходим к Листингу 5.23 чтобы направить корневой URI / к Home странице.
config/routes.rb
SampleApp::Application.routes.draw do
root to: 'static_pages#home'
match '/help', to: 'static_pages#help'
match '/about', to: 'static_pages#about'
match '/contact', to: 'static_pages#contact'
.
.
.
end
Этот код направляет корневой URI / к /static_pages/home и дает нам URI хелперы:
root_path => '/'
root_url => '//localhost:3000/'
Мы должны также прислушаться к коментарию в Листинге 5.22 и удалить public/index.html
чтобы предотвратить отображение дефолтной страницы (Рис. 1.3) при посещении /. Вы можете, конечно, просто удалить файл поместив его в корзину, но если вы используете Git для контроля версий, есть способ удалить файл и в тоже время сообщить Git об удалении — с помощью git rm
:
$ git rm public/index.html
При этом, все маршруты для статических страниц работают, и тесты должны пройти:
$ 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.24), который содержит ссылки на Home и Help страницы. Пока мы здесь, мы также, следуя общей конвенции Сети, свяжем логотип с Home страницей.
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.25).
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.21 и выполнив тесты. Метод тестирования, который на самом деле обеспечивает уверенность что ссылки существуют и отправляют в правильные места, см. в Разделе 5.6.
5.3.4 Приятный RSpec
Мы отметили в Разделе 5.3.1 что тесты для статических страниц стали немного многословными и наполнились повторениями (Листинг 5.20). В этом разделе мы применим самые последние фишки RSpec для того чтобы сделать наши тесты более компактными и элегантными.
Давайте взглянем на пару примеров чтобы увидеть как они могут быть улучшены:
describe "Home page" do
it "should have the h1 'Sample App'" do
visit root_path
page.should have_selector('h1', text: 'Sample App')
end
it "should have the base title" do
visit root_path
page.should have_selector('title',
text: "Ruby on Rails Tutorial Sample App")
end
it "should not have a custom page title" do
visit root_path
page.should_not have_selector('title', text: '| Home')
end
end
Здесь стоит отметить одну вещь - все три примера включают посещение root_path. Мы можем устранить это дублирование с помощью блока before
:
describe "Home page" do
before { visit root_path }
it "should have the h1 'Sample App'" do
page.should have_selector('h1', text: 'Sample App')
end
it "should have the base title" do
page.should have_selector('title',
text: "Ruby on Rails Tutorial Sample App")
end
it "should not have a custom page title" do
page.should_not have_selector('title', text: '| Home')
end
end
Здесь используется строка
before { visit root_path }
для того чтобы посещать root_path перед каждым примером. (Метод before
можно также вызвать спомощью синонима before(:each)
.)
Есть еще один источник повторений в каждом примере; у нас есть
it "should have the h1 'Sample App'" do
и
page.should have_selector('h1', text: 'Sample App')
которые, по сути, одно и то же. К тому же, оба примера ссылаются на переменную page
. Мы можем устранить это дублирование сказав RSpec-y что page
это субъект тестирования с помощью
subject { page }
а затем применив вариант метода it
для того чтобы собрать код и описание (теста) в одну строку:
it { should have_selector('h1', text: 'Sample App') }
Благодаря subject { page }
, вызов should
автоматически использует переменную page
предоставленную гемом Capybara (Раздел 3.2.1).
Применение этих изменений дает нам гораздо более компактные тесты для страницы Home:
subject { page }
describe "Home page" do
before { visit root_path }
it { should have_selector('h1', text: 'Sample App') }
it { should have_selector 'title',
text: "Ruby on Rails Tutorial Sample App" }
it { should_not have_selector 'title', text: '| Home' }
end
Этот код выглядит лучше, но тест заголовка по-прежнему длинноват. Кроме того, большая часть теста заголовка в Листинге 5.20 содержит длиный текст заголовка вида
"Ruby on Rails Tutorial Sample App | About"
Упражнение в Разделе 3.5 предлагает вам устранить часть этого дублирования посредством определения переменной base_title
и интерполяции строк (Листинг 3.30). Мы можем сделать еще лучше, определив full_title
, который является параллелью хелпера full_title
из Листинга 4.2. Мы сделаем это создав директорию spec/support
и файл utilities.rb
для RSpec-утилит (Листинг 5.26).
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_selector('h1', text: 'Sample App') }
it { should have_selector('title', text: full_title('')) }
end
Теперь мы можем упростить тесты для страниц Help, About и Contact используя теже методы что и для страницы Home. Результаты представлены в Листинге 5.27.
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_selector('h1', text: 'Sample App') }
it { should have_selector('title', text: full_title('')) }
it { should_not have_selector 'title', text: '| Home' }
end
describe "Help page" do
before { visit help_path }
it { should have_selector('h1', text: 'Help') }
it { should have_selector('title', text: full_title('Help')) }
end
describe "About page" do
before { visit about_path }
it { should have_selector('h1', text: 'About') }
it { should have_selector('title', text: full_title('About Us')) }
end
describe "Contact page" do
before { visit contact_path }
it { should have_selector('h1', text: 'Contact') }
it { should have_selector('title', text: full_title('Contact')) }
end
end
Теперь необходимо убедиться что тесты по-прежнему проходят:
$ bundle exec rspec spec/requests/static_pages_spec.rb
Стиль RSpec из Листинга 5.27 намного более лаконичен нежели стиль в Листинге 5.20—однако, он может быть еще более лаконичным (Раздел 5.6). При дальнейшей разработке примера приложения мы будем использовать этот более компактный стиль везде, где это только будет возможно.
5.4 Регистрация пользователей: первый шаг
В качестве кульминации нашей работы над макетом и маршрутами, в этом разделе мы сделаем маршрут для страницы регистрации (signup), что будет означать создание второго контроллера. Это первый важный шаг к предоставлению пользователям возможности регистрироваться на нашем сайте, мы сделаем следующий шаг, моделирование пользователя, в Главе 6 и мы закончим работу в Главе 7.
5.4.1 Контроллер Users
Мы создали наш первый контроллер - StaticPages - еще в Разделе 3.1.2. Пришло время создать второй — контроллер Users. Как и прежде, мы будем использовать generate
для создания простейшего контроллера, который отвечает нашим текущим потребностям, а именно, с одной страницей-заглушкой для регистрации новых пользователей. Следуя конвенции REST архитектуры предпочитаемой Рельсами, мы назовем действие для новых пользователей, new
и передадим его в качестве аргумента в generate controller
для создания его (действия) автоматически (Листинг 5.28).
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.29) и заглушку представления (Листинг 5.30).
new
действием. app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
end
end
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 URI для регистрации
С кодом из Раздела 5.4.1 у нас уже есть рабочая страница для создания новых пользователей на /users/new, но вспомним из Таблицы 5.1, что вместо этого нам необходим URI /signup. Как и в Разделе 5.3, мы начнем с написания интеграционных тестов, которые мы сейчас сгенерируем:
$ rails generate integration_test user_pages
Затем, следуя модели спеков для статических страниц в Листинге 5.27, мы заполняем тесты для страниц пользователей кодом тестирующим содержимое h1
и title
тегов как это показано в Листинге 5.31.
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_selector('h1', text: 'Sign up') }
it { should have_selector('title', text: 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
это запуск набора тестов.)
По конструкции, контроллер Users уже имеет new
действие, так что все что нам необходимо для прохождения тестов это правильные маршрут и контент представления. Мы будем следовать примерам из Листинга 5.21 и добавим match ’/signup’
правило для URI регистрации (Листинг 5.32).
config/routes.rb
SampleApp::Application.routes.draw do
get "users/new"
root to: 'static_pages#home'
match '/signup', to: 'users#new'
match '/help', to: 'static_pages#help'
match '/about', to: 'static_pages#about'
match '/contact', to: 'static_pages#contact'
.
.
.
end
Обратите внимание, мы сохранили правило get "users/new"
, которое было сгенерировано автоматически при создании Users контроллера в Листинге 5.28. В настоящее время это правило необходимо для работы адреса ’users/new’
, но оно не соответствует REST конвенции (Таблица 2.2) и мы избавимся от него в Разделе 7.1.2.
Теперь все что нам теперь необходимо для прохождения тестов это представление с заголовком браузера и заголовком первого уровня “Sign up” (Листинг 5.33).
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.31 должен пройти. Нам осталось лишь добавить соответствующую ссылку к кнопке на Home странице. Как и с прочими маршрутами, match ’/signup’
дает нам именованный маршрут signup_path
, который мы применяем в Листинге 5.34.
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"), 'http://rubyonrails.org/' %>
С этим работу над ссылками и именованными маршрутами можно считать законченной, по крайней мере до тех пор, пока мы не добавим маршрут для входа на сайт (Глава 8). Получившаяся страница для регистрации пользователей (на URI /signup) представлена на Рис. 5.9.
В этой точке тесты должны проходить:
$ 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 Упражнения
- Код для тестирования статических страниц в Листинге 5.27 компактен, но по прежнему имеет повторения. RSpec поддерживает возможность называемую shared examples предназначенную для устранения такого вида дублирования. Следуя примеру в Листинге 5.35, заполните пропущенные тесты для страниц Help, About и Contact. Обратите внимание, что команда
let
кратко обсуждаемая в Листинге 3.30, создает локальную переменную с данным значением по запросу (т.е. при использовании переменной), в отличие от переменной экземпляра, которая создается в момент назначения. - Возможно вы заметили что наши тесты для ссылок шаблона тестируют маршруты, но на самом деле не проверяют, ведут ли ссылки к правильным страницам. Один из способов реализации этих испытаний заключается в использовании
visit
иclick_link
внутри RSpec интеграционного теста. Дополните код из Листинга 5.36 чтобы проверить что все ссылки шаблона правильно определены. - Устраните необходимость в тестовом хелпере
full_title
в Листинге 5.26 написав тест для оригинального вспомогательного метода, как это показано в Листинге 5.37. (Вам необходимо будет создать директориюspec/helpers
и файлapplication_helper_spec.rb
.) Затемinclude
его в тест с помощью кода в Листинге 5.38. Проверьте запустив тесты, что новый код работает. Примечание: Листинг 5.37 использует регулярные выражения, о которых мы узнаем больше в Разделе 6.2.4. (Спасибо Alex Chaffee за совет и код используемый в этом упражнении.)
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_selector('title', text: 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_selector 'title', text: '| Home' }
end
describe "Help page" do
.
.
.
end
describe "About page" do
.
.
.
end
describe "Contact page" do
.
.
.
end
end
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"
page.should have_selector 'title', text: full_title('About Us')
click_link "Help"
page.should # fill in
click_link "Contact"
page.should # fill in
click_link "Home"
click_link "Sign up now!"
page.should # fill in
click_link "sample app"
page.should # fill in
end
end
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
full_title("foo").should =~ /foo/
end
it "should include the base title" do
full_title("foo").should =~ /^Ruby on Rails Tutorial Sample App/
end
it "should not include a bar for the home page" do
full_title("").should_not =~ /\|/
end
end
end
full_title
на простой include
. spec/support/utilities.rb
include ApplicationHelper
- Спасибо читателю Colm Tuite за его замечательную работу по переводу примера приложения на CSS фреймворк Bootstrap. ↑
- Каркасы в Ruby on Rails Tutorial сделаны с помощью замечательного онлайн приложения для создания каркасов, называемого Mockingbird. ↑
- Они абсолютно не связаны с классами Ruby. ↑
- Вы могли заметить, что
img
tag, вместо сходства с <img>...</img>, более похож на <img ... />. Теги, соответствующие этой форме, известны как самозакрывающиеся. ↑ - Также возможно использовать LESS с файлопроводом; см. подробности на GitHub странице гема less-rails-bootstrap. ↑
- Многие Rails разработчики используют
shared
директорию для партиалов совместно используемых разными представлениями. Я предпочитаю использоватьshared
папку для "служебных" партиалов которые используются во множестве страниц, помещая партиалы, которые используются буквально на каждой странице (такие как части шаблона сайта) вlayouts
директорию. (Мы создадимshared
директорию в Главе 7.) Это кажется мне более логичным, но помещение всех партиалов в папкуshared
тоже замечательно работает. ↑ - Вы, возможно, удивлены тем, что мы используем и тег
footer
и класс.footer
. Ответ заключается в том, что тег имеет понятное значение для читателя, а класс необходим для Bootstrap. Тегdiv
вместоfooter
тоже будет работать. ↑ - Структура этого раздела опирается на замечательную статью The Rails 3 Asset Pipeline in (about) 5 Minutes от Michael Erasmus. Более подробно о файлопроводе см. (rus)Rails Guide on the Asset Pipeline. ↑
- Более старый
.sass
формат, также поддерживаемый Sass, определяет новый язык который является менее многословным (и в нем нет фигурных скобок), но менее удобным для существующих проектов и его сложнее изучать тем кто уже знаком с CSS. ↑