Разработка приложений на Ruby on Rails

Заполнение шаблона

Разбить на страницы
Показывать лекцию целиком

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

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

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

При создании веб-приложений, часто бывает полезным получить общий вид пользовательского интерфейса как можно раньше. Таким образом, на протяжении остальной части книги, я буду часто использовать макеты (в контексте Веб часто называемыми каркасами), которые являются грубыми набросками того, как приложение, возможно, будет выглядеть.Каркасы в Ruby on Rails Tutorial сделаны с помощью замечательного онлайн приложения для создания каркасов, называемого http://gomockingbird.com/. Вы можете увидеть конечный результат на рис 5.7. Он отличается в некоторых деталях, но это нормально, так как каркас и не должен быть абсолютно точным.

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

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

$ git checkout -b filling-in-layout

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

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

<!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>

Здесь тег Они абсолютно не связаны с классами Ruby. - 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).

<div class="center hero-unit">
  <h1>Welcome to the Sample App</h1>

  <h2>
    This is the home page for the
    <a href="http://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

Второй Вы могли заметить, что img tag, вместо сходства с <img>...</img>, более похож на <img ... />. Теги, соответствующие этой форме, известны как самозакрывающиеся.

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

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

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

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

(рис 5.2) Страница Home (/static_pages/home) без кастомного CSS.

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 доступными для текущего приложения.Также возможно использовать LESS с файлопроводом; см. подробности на GitHub странице гема less-rails-bootstrap.

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.

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.

@import "bootstrap";

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

(рис 5.3) Пример приложения с Bootstrap CSS.

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

@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;
}
(рис 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.7" type="example">Листинге 5.7. (Не все эти правила применяются к странице Home page, но каждое правило будет использовано в какой-то части примера приложения.) Результат Листинга 5.7 показан на @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; } (рис 5.5) Добавление нескольких стилей для типографики

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

@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;
}

Здесь .

(рис 5.6) Пример приложения с приятно отстиленным логотипом.

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

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

<!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' %>

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

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

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

<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 и класс .footer. Ответ заключается в том, что тег имеет понятное значение для читателя, а класс необходим для Bootstrap. Тег div вместо footer тоже будет работать.

<footer class="footer">
  <small>
    <a href="http://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).

<!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.14" type="example">Листинг 5.14). Результаты представлены на . . . /* 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.7) Home страница (/static_pages/home) с добавленным подвалом.

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

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

Файлопровод

Файлопровод включает в себя множество подкапотных изменений в Rails, но с точки зрения типичного Rails разработчика есть три принципиальных момента в которых нужно разобраться: директории ассетов (активов, файлов), файлы-манифесты и препроцессоры.Структура этого раздела опирается на замечательную статью The Rails 3 Asset Pipeline in (about) 5 Minutes от Michael Erasmus. Более подробно о файлопроводе см. (rus)Rails Guide on the Asset Pipeline. Давайте рассмотрим каждый из них по очереди.

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

В версиях 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).

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

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

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

    Как было вкратце отмечено в Разделе 5.1.2, Sass поддерживает формат называемый SCSS (обозначается расширением файла Более старый .sass формат, также поддерживаемый Sass, определяет новый язык который является менее многословным (и в нем нет фигурных скобок), но менее удобным для существующих проектов и его сложнее изучать тем кто уже знаком с CSS. Это означает, что любой валидный 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.

    @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.

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

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

    <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 для ссылок сайта.
    Страница URL Именованный маршрут
    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.17; они просто следуют модели из Листинга 3.19.

    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.

    <footer class="footer">
      <small>
        <a href="http://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).

    SampleApp::Application.routes.draw do
      get "static_pages/home"
      get "static_pages/help"
      get "static_pages/about"
      get "static_pages/contact"
      .
      .
      .
    end
    class StaticPagesController < ApplicationController
      .
      .
      .
      def contact
      end
    end
    <% provide(:title, 'Contact') %>
    <h1>Contact</h1>
    <p>
      Contact Ruby on Rails Tutorial about the sample app at the
      <a href="http://railstutorial.org/contact">contact page</a>.
    </p>

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

    $ bundle exec rspec spec/requests/static_pages_spec.rb

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

    <footer class="footer">
      <small>
        <a href="http://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.1. Другими словами, мы меняем

    visit '/static_pages/about'

    на

    visit about_path

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

    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.

    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.

    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  -> 'http://localhost:3000/about'

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

    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 странице.

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

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

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

    $ bundle exec rspec spec/requests/static_pages_spec.rb

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

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

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

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

    в

    <%= link_to "About", about_path %>

    и так далее.

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

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

    <footer class="footer">
      <small>
        <a href="http://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, так, например, ).

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

    (рис 5.8) About страница на /about.

    Приятный 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).

    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.

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

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

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

    Контроллер Users

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

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

    class UsersController < ApplicationController
      def new
      end
    
    end
    <h1>Users#new</h1>
    <p>Find me in app/views/users/new.html.erb</p>

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

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

    $ rails generate integration_test user_pages

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

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

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

    <% 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.

    <div class="center hero-unit">
      <h1>Welcome to the Sample App</h1>
    
      <h2>
        This is the home page for the
        <a href="http://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). Получившаяся страница для регистрации пользователей (на URL /signup) представлена на рис 5.9.

    (рис 5.9) Страница регистрации (/signup).

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

    $ bundle exec rspec spec/

    Заключение

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

    Если вы используете 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.3 что Rspec-матчер have_content иногда работает слишком общО что приводит нас к временному закомментированию строки "Contact" в футере во избежание ошибочно-проходящего теста (Листинг 5.18). Для того чтобы исправить это Листинг 5.38 вводит предоставляемый Capybara метод have_selector который позволяет нам тестировать наличие отдельных HTML тегов. Удаляя и возвращая на место "Contact" h1 в Листинге 5.21, убедитесь что код в Листинге 5.38 выдает провальный тест даже если подвал не закомментирован.
  • Код для тестирования статических страниц в Листинге 5.30 компактен, но по прежнему имеет повторения. RSpec поддерживает возможность называемую shared examples предназначенную для устранения такого вида дублирования. Следуя примеру в Листинге 5.39, заполните пропущенные тесты для страниц Help, About и Contact. Обратите внимание - команда let кратко обсуждаемая в Листинге 3.31, создает локальную переменную с данным значением по запросу (т.е. при использовании переменной), в отличие от переменной экземпляра, которая создается в момент назначения. (Листинг 5.39 также использует have_selector из Листинга 5.38.)
  • Возможно вы заметили что наши тесты для ссылок шаблона тестируют маршруты, но на самом деле не проверяют, ведут ли ссылки к правильным страницам. Один из способов реализации этих испытаний заключается в использовании visit и click_link внутри RSpec интеграционного теста. Дополните код из Листинга 5.40 чтобы проверить что все ссылки шаблона правильно определены.
  • Устраните необходимость в тестовом хелпере full_title в Листинге 5.29 написав тест для оригинального вспомогательного метода, как это показано в Листинге 5.41. (Вам необходимо будет создать директорию spec/helpers и файл application_helper_spec.rb.) Затем include его в тест с помощью кода в Листинге 5.42. Проверьте, запустив тесты, что новый код работает. Примечание: Листинг 5.41 использует регулярные выражения о которых мы узнаем больше в Разделе 6.2.4. (Спасибо Alex Chaffee за совет и код используемый в этом упражнении.)
  • 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
    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
    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
    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
    include ApplicationHelper
    Страницы:

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

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

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

    При создании веб-приложений, часто бывает полезным получить общий вид пользовательского интерфейса как можно раньше. Таким образом, на протяжении остальной части книги, я буду часто использовать макеты (в контексте Веб часто называемыми каркасами), которые являются грубыми набросками того, как приложение, возможно, будет выглядеть.Каркасы в Ruby on Rails Tutorial сделаны с помощью замечательного онлайн приложения для создания каркасов, называемого http://gomockingbird.com/. Вы можете увидеть конечный результат на рис 5.7. Он отличается в некоторых деталях, но это нормально, так как каркас и не должен быть абсолютно точным.

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

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

    $ git checkout -b filling-in-layout

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

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

    <!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>

    Здесь тег Они абсолютно не связаны с классами Ruby. - 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).

    <div class="center hero-unit">
      <h1>Welcome to the Sample App</h1>
    
      <h2>
        This is the home page for the
        <a href="http://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

    Второй Вы могли заметить, что img tag, вместо сходства с <img>...</img>, более похож на <img ... />. Теги, соответствующие этой форме, известны как самозакрывающиеся.

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

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

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

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

    (рис 5.2) Страница Home (/static_pages/home) без кастомного CSS.

    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 доступными для текущего приложения.Также возможно использовать LESS с файлопроводом; см. подробности на GitHub странице гема less-rails-bootstrap.

    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.

    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.

    @import "bootstrap";

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

    (рис 5.3) Пример приложения с Bootstrap CSS.

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

    @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;
    }
    
    (рис 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.7" type="example">Листинге 5.7. (Не все эти правила применяются к странице Home page, но каждое правило будет использовано в какой-то части примера приложения.) Результат Листинга 5.7 показан на @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; } (рис 5.5) Добавление нескольких стилей для типографики

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

    @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;
    }

    Здесь .

    (рис 5.6) Пример приложения с приятно отстиленным логотипом.

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

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

    <!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' %>

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

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

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

    <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 и класс .footer. Ответ заключается в том, что тег имеет понятное значение для читателя, а класс необходим для Bootstrap. Тег div вместо footer тоже будет работать.

    <footer class="footer">
      <small>
        <a href="http://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).

    <!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.14" type="example">Листинг 5.14). Результаты представлены на . . . /* 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.7) Home страница (/static_pages/home) с добавленным подвалом.

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

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

    Файлопровод

    Файлопровод включает в себя множество подкапотных изменений в Rails, но с точки зрения типичного Rails разработчика есть три принципиальных момента в которых нужно разобраться: директории ассетов (активов, файлов), файлы-манифесты и препроцессоры.Структура этого раздела опирается на замечательную статью The Rails 3 Asset Pipeline in (about) 5 Minutes от Michael Erasmus. Более подробно о файлопроводе см. (rus)Rails Guide on the Asset Pipeline. Давайте рассмотрим каждый из них по очереди.

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

    В версиях 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).

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

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

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

    Как было вкратце отмечено в Разделе 5.1.2, Sass поддерживает формат называемый SCSS (обозначается расширением файла Более старый .sass формат, также поддерживаемый Sass, определяет новый язык который является менее многословным (и в нем нет фигурных скобок), но менее удобным для существующих проектов и его сложнее изучать тем кто уже знаком с CSS. Это означает, что любой валидный 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.

    @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.

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

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

    <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 для ссылок сайта.
    Страница URL Именованный маршрут
    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.17; они просто следуют модели из Листинга 3.19.

    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.

    <footer class="footer">
      <small>
        <a href="http://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).

    SampleApp::Application.routes.draw do
      get "static_pages/home"
      get "static_pages/help"
      get "static_pages/about"
      get "static_pages/contact"
      .
      .
      .
    end
    class StaticPagesController < ApplicationController
      .
      .
      .
      def contact
      end
    end
    <% provide(:title, 'Contact') %>
    <h1>Contact</h1>
    <p>
      Contact Ruby on Rails Tutorial about the sample app at the
      <a href="http://railstutorial.org/contact">contact page</a>.
    </p>

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

    $ bundle exec rspec spec/requests/static_pages_spec.rb

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

    <footer class="footer">
      <small>
        <a href="http://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.1. Другими словами, мы меняем

    visit '/static_pages/about'

    на

    visit about_path

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

    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.

    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.

    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  -> 'http://localhost:3000/about'

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

    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 странице.

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

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

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

    $ bundle exec rspec spec/requests/static_pages_spec.rb

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

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

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

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

    в

    <%= link_to "About", about_path %>

    и так далее.

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

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

    <footer class="footer">
      <small>
        <a href="http://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, так, например, ).

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

    (рис 5.8) About страница на /about.

    Приятный 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).

    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.

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

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

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

    Контроллер Users

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

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

    class UsersController < ApplicationController
      def new
      end
    
    end
    <h1>Users#new</h1>
    <p>Find me in app/views/users/new.html.erb</p>

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

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

    $ rails generate integration_test user_pages

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

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

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

    <% 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.

    <div class="center hero-unit">
      <h1>Welcome to the Sample App</h1>
    
      <h2>
        This is the home page for the
        <a href="http://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). Получившаяся страница для регистрации пользователей (на URL /signup) представлена на рис 5.9.

    (рис 5.9) Страница регистрации (/signup).

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

    $ bundle exec rspec spec/

    Заключение

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

    Если вы используете 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.3 что Rspec-матчер have_content иногда работает слишком общО что приводит нас к временному закомментированию строки "Contact" в футере во избежание ошибочно-проходящего теста (Листинг 5.18). Для того чтобы исправить это Листинг 5.38 вводит предоставляемый Capybara метод have_selector который позволяет нам тестировать наличие отдельных HTML тегов. Удаляя и возвращая на место "Contact" h1 в Листинге 5.21, убедитесь что код в Листинге 5.38 выдает провальный тест даже если подвал не закомментирован.
  • Код для тестирования статических страниц в Листинге 5.30 компактен, но по прежнему имеет повторения. RSpec поддерживает возможность называемую shared examples предназначенную для устранения такого вида дублирования. Следуя примеру в Листинге 5.39, заполните пропущенные тесты для страниц Help, About и Contact. Обратите внимание - команда let кратко обсуждаемая в Листинге 3.31, создает локальную переменную с данным значением по запросу (т.е. при использовании переменной), в отличие от переменной экземпляра, которая создается в момент назначения. (Листинг 5.39 также использует have_selector из Листинга 5.38.)
  • Возможно вы заметили что наши тесты для ссылок шаблона тестируют маршруты, но на самом деле не проверяют, ведут ли ссылки к правильным страницам. Один из способов реализации этих испытаний заключается в использовании visit и click_link внутри RSpec интеграционного теста. Дополните код из Листинга 5.40 чтобы проверить что все ссылки шаблона правильно определены.
  • Устраните необходимость в тестовом хелпере full_title в Листинге 5.29 написав тест для оригинального вспомогательного метода, как это показано в Листинге 5.41. (Вам необходимо будет создать директорию spec/helpers и файл application_helper_spec.rb.) Затем include его в тест с помощью кода в Листинге 5.42. Проверьте, запустив тесты, что новый код работает. Примечание: Листинг 5.41 использует регулярные выражения о которых мы узнаем больше в Разделе 6.2.4. (Спасибо Alex Chaffee за совет и код используемый в этом упражнении.)
  • 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
    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
    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
    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
    include ApplicationHelper
    Вернуться к учебному плану