четверг, 11 октября 2012 г.

Ruby on Rails Tutorial 5

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

В процессе краткого обзора Ruby в Главе 4, мы добавили базовые таблицы каскадных стилей в шаблон нашего сайта (Раздел 4.1.2). В этой главе мы добавим собственную таблицу стилей, добавим в шаблон ссылки на страницы (например, Home и About), которые мы создали ранее. В процессе мы узнаем о частичных шаблонах (partials), Rails маршрутах, а также об интеграционных тестах. Мы закончим, сделав первый важный шаг на пути создания опции регистрации пользователей на нашем сайте.

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

Rails Учебник это книга по веб-разработке, а не по веб-дизайну, но работа над приложением, которое выглядит как полное дерьмо - удручает, поэтому, в этом разделе мы добавим некоторые структуры в шаблон и придадим ему минимальный дизайн с CSS. Мы также придадим нашему коду немного стиля, так сказать, используя частичные шаблоны почистим шаблон, поскольку он немного загроможден.
При создании веб-приложений, часто бывает полезным получить общий вид пользовательского интерфейса как можно раньше. Таким образом, на протяжении остальной части книги, я буду часто использовать макеты (в контексте Веб часто называемыми каркасами), которые являются грубыми набросками того, как приложение, возможно, будет выглядеть.1 В этой главе мы будем главным образом разрабатывать статические страницы введеные в Разделе 3.1, включая логотип сайта, заголовок с навигацией по сайту, и подвал сайта. Каркас для наиболее важной из страниц — Home страницы, представлен на Рис. 5.1. (Вы можете увидеть конечный результат на Рис. 5.8. Он отличается в некоторых деталях, например, подвал имеет четыре ссылки вместо трех, но это нормально, так как каркас и не должен быть абсолютно точным.)
home_page_mockup
Рис. 5.1: Каркас Home страницы примера приложения. (полный размер)
Как обычно, если вы используете Git для управления версиями, то сейчас самое время создать новую ветку:
$ git checkout -b filling-in-layout
У вас мог еще сохраниться example_user.rb файл из Главы 4 в каталоге вашего проекта, если это так, то вам следует просто удалить его.

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

Когда мы последний раз видели файл шаблона сайта application.html.erb в Листинге 4.3, мы только что добавили к нему таблицы стилей Blueprint, используя Rails помощник stylesheet_link_tag. Пришло время добавить еще пару таблиц стилей, одну специально для интернет-браузеров Explorer и одну для нашего (скоро-будет-добавлен) пользовательского CSS. Мы также добавим несколько дополнительных (div-ов), несколько id и class-ов, и запустим навигацию по нашему сайту. Законченный файл — в Листинге 5.1; объяснения различных частей последуют сразу за ним. Если вы не хотите откладывать удовольствие, вы можете посмотреть на результаты представленные на Рис. 5.2. (Примечание: это (пока) не очень приятно.)
Листинг 5.1. Шаблон сайта с добавленными структурами.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <%= csrf_meta_tag %>
    <!--[if lt IE 9]>
    <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]-->
    <%= stylesheet_link_tag 'blueprint/screen', :media => 'screen' %>
    <%= stylesheet_link_tag 'blueprint/print',  :media => 'print' %>
    <!--[if lt IE 8]><%= stylesheet_link_tag 'blueprint/ie' %><![endif]-->
    <%= stylesheet_link_tag 'custom', :media => 'screen' %>
  </head>
  <body>
    <div class="container">
      <header>
        <%= image_tag("logo.png", :alt => "Sample App", :class => "round") %>
        <nav class="round">
          <ul>
            <li><%= link_to "Home", '#' %></li>
            <li><%= link_to "Help", '#' %></li>
            <li><%= link_to "Sign in", '#' %></li>
          </ul>
        </nav>
      </header>
      <section class="round">
        <%= yield %>
      </section>
    </div>
  </body>
</html>
Давайте просмотрим новые элементы, сверху донизу. Как вкратце отмечалось в Разделе 3.1, Rails 3 по умолчанию использует HTML5 (о чем говорит doctype <!DOCTYPE html>); т.к. HTML5 — новый стандарт, и некоторые браузеры (особенно Internet Explorer) пока не полностью его поддерживают, так что мы включим JavaScript код (известный как “HTML5 shiv”) чтобы обойти это затруднение:
<!--[if lt IE 9]>
<script src="http://html5shiv.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 и Safari.
После строки, включающей стили Blueprint (впервые представлена в Листинге 4.4), идет другая Internet Explorer–специфичная строка, в этот раз это стиль, который включается только если версия Internet Explorer ниже чем 8 (if lt IE 8):
<!--[if lt IE 8]><%= stylesheet_link_tag 'blueprint/ie' %><![endif]-->
IE имеет большое количество “индивидуальных особенностей” (особенно до версии 8), и с Blueprint поставляется специальный ie.css файл, исправляющий значительную часть из них.
За IE стилем идет ссылка на файл, который еще не существует, custom.css, в котором мы разместим наши пользовательские CSS:
<%= stylesheet_link_tag 'custom', :media => 'screen' %>
CSS простит нам даже отсутствие файла — без него наша страница все равно будет работать нормально. (Мы создадим custom.css в Разделе 5.1.2.)
В следующем разделе расположился контейнер div окружающий навигацию по сайту и контент, контейнер определяется div тегом с классом container. Этот контейнер DIV необходим Blueprint-у (см. подробности в Blueprint tutorial Далее идут header и section элементы; header содержит логотип "sample app" (будет добавлен позже) и навигацию по сайту (nav). И последний элемент - section, содержащий основной контент сайта:
<div class="container">
  <header>
    <%= image_tag("logo.png", :alt => "Sample App", :class => "round") %>
    <nav class="round">
      <ul>
        <li><%= link_to "Home", '#' %></li>
        <li><%= link_to "Help", '#' %></li>
        <li><%= link_to "Sign in", '#' %></li>
      </ul>
    </nav>
  </header>
  <section class="round">
    <%= yield %>
  </section>
</div>
div тег в HTML это generic division (# "родовое отделение" — если верить автопереводчику :) ); он не делает ничего, кроме разделения документа на отдельные части. В старом стиле HTML, div теги использовали почти для всех разделов сайта, но HTML5 добавил header, nav, и section элементы для разделов, общих для многих приложений. Всем элементам HTML, включая дивы и новые HTML5 элементы, могут быть присвоены классы2 и id (идентификаторы); это всего лишь ярлыки, и они полезны для создания стиля с CSS (Раздел 5.1.2). Основное различие между классами и идентификаторами, в том, что классы могут быть использованы несколько раз на странице, а идентификаторы могут быть использованы лишь один раз.
Внутри header находится Rails помощник, называемый image_tag:
<%= image_tag("logo.png", :alt => "Sample App", :class => "round") %>
Отметим, что, как и со stylesheet_link_tag (Раздел 4.3.4), мы передаем хэш опций, в данном случае для назначения alt и class атрибутов image тега, используются символы :alt и :class. Чтобы прояснить это, давайте посмотрим на HTML который этот тег производит:3
<img alt="Sample App" class="round" src="/images/logo.png" />
alt атрибут, это то, что будет отображаться, если нет изображения,4 и class будет использоваться для создания стиля в Разделе 5.1.2. (Rails хелперы часто принимают хэши опций именно таким образом, что позволяет нам гибко добавлять произвольные варианты HTML, не покидая Rails.) Вы можете увидеть результаты на Рис. 5.2; логотип мы добавим в конце этого раздела.
Второй элемент внутри header это список навигационных ссылок, сделанный с использованием тега ненумерованного списка — ul, и тега элемента списка li:
<nav class="round">
  <ul>
    <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); первый аргумент это текст ссылки, а второй это URL. Мы заполним URL-адреса именованными маршрутами в Разделе 5.2.3, но сейчас мы используем заглушки URL ’#’ обычно применяемые в веб-дизайне. После Rails обработки этого шаблона и оценки Embedded Ruby, список выглядит следующим образом:
<nav class="round">
  <ul>
    <li><a href="#">Home</a></li>
    <li><a href="#">Help</a></li>
    <li><a href="#">Sign in</a></li>
  </ul>
</nav>
Наш шаблон теперь завершен, и мы можем посмотреть на результаты, посетив, например, Home страницу. В ожидании добавления пользователей на наш сайт в Главе 8, давайте добавим signup (регистрация) ссылку в home.html.erb представление (Листинг 5.2).
Листинг 5.2. Home страница со ссылкой на страницу регистрации.
app/views/pages/home.html.erb
<h1>Sample App</h1>

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

<%= link_to "Sign up now!", '#', :class => "signup_button round" %>
Как и в предыдущем использовании link_to, это только создает ссылку-заглушку вида
<a href="#" class="signup_button round">Sign up now!</a>
Отметим еще раз, повторяющуюся тему опций хэшей, которые в данном случае используются для добавления пары CSS классов в анкорный(якорный, привязки) тег. Вы могли заметить, что a тег здесь имеет два класса, разделенные пробелом:
<a href="#" class="signup_button round">
Это удобно для общего случая элемента с двумя различными типами стилей.
Теперь мы, наконец, готовы увидеть плоды наших трудов (Рис. 5.2).5 Говорите "Не впечатляет"? Может быть. К счастью, однако, мы проделали хорошую работу, прописав для наших HTML элементов внятные классы и идентификаторы, которые позволят нам добавить стиль на сайт с помощью CSS.
layout_no_logo_or_custom_css
Рисунок 5.2: Home страница (/pages/home) без логотипа и пользовательского CSS. (полный размер)
Прежде чем мы перейдем к CSS стайлингу, давайте заменим альтернативный текст логотипа на изображение логотипа, который вы можете загрузить на

http://railstutorial.org/images/sample_app/logo.png
Поместите логотип в public/images чтобы Rails смог его найти. Результат — в Рис. 5.3.
layout_logo_no_css
Рисунок 5.3: Home страница (/pages/home) с логотипом, но без пользовательского CSS. (полный размер)

5.1.2 Пользовательские CSS

В Разделе 5.1.1, вы могли заметить, что элементы CSS семантичны, то есть, обозначающие их слова, имеют значение в английском языке за пределами структуры страницы. Например, вместо того чтобы писать, что навигационное меню было “right-top” мы использовали “nav”. Это дает нам значительную гибкость при построении шаблона на основе CSS.
Давайте начнем с заполнения файла custom.css кодом из Листинга 5.3. (В Листинге 5.3. содержится немало правил. Чтобы получить представление о том, что именно делает каждое конкретное правило CSS, часто бывает полезным, закомментировать его с помощью CSS комментария, т. е., поместить его в /* … */, и посмотреть, что изменилось.
Листинг 5.3. CSS для container, body, и ссылок.
public/stylesheets/custom.css
.container {
  width: 710px;
}

body {
  background: #cff;
}

header {
  padding-top: 20px;
}

header img {
  padding: 1em;
  background: #fff;
}

section {
  margin-top: 1em;
  font-size: 120%;
  padding: 20px;
  background: #fff;
}

section h1 {
  font-size: 200%;
}

/* Links */

a {
  color: #09c;
  text-decoration: none;
}

a:hover {
  color: #069;
  text-decoration: underline;
}

a:visited {
  color: #069;
}
Вы можете видеть результат работы этого CSS в Рис. 5.4. Здесь очень много CSS, но он имеет последовательную форму. Каждое правило относится либо к классу, ID, тегу HTML, или какой-либо их комбинации и сопровождается списком "стильных" команд. Например,
body {
  background: #cff;
}
изменяет цвета фона body тега на голубой, а
header img {
  padding: 1em;
  background: #fff;
}
устанавливает ширину внутреннего отступа (padding) в один (одну) em (ширина строчной (маленькой) буквы m) вокруг изображения (img) внутри header тега. Это правило также делает цвет фона белым (#fff).6 Аналогичным образом,
.container {
  width: 710px;
}
стилизует элемент с классом container, в данном случае, придавая ему ширину в 710 пикселов (что соответствует 18 столбцам Blueprint).7 Точка . в .container обозначает что это правило для класса называемого “container”. (как мы увидим в Разделе 8.2.3, знак фунт # указывает (также как и точка в случае CSS класса), что это правило для id.)
layout_with_colors
Рисунок 5.4: Home страница (/pages/home) с пользовательскими цветами. (полный размер)
Изменение цвета это хорошо, но навигационные ссылки по-прежнему висят внизу по левой стороне страницы. Давайте переместим их в другое место и дадим им лучшую внешность с навигационными правилами из Листинга 5.4. Результаты представлены в Рис. 5.5. (В некоторых примерах кода, включая Листинг 5.4, я использую три вертикальные точки для обозначения пропущенного кода. Вашему приложению эти точки не нужны.)
Листинг 5.4. CSS для навигации по сайту.
public/stylesheets/custom.css
.
.
.
/* Navigation */

nav {
  float: right;
}

nav {
  background-color: white;
  padding: 0 0.7em;
  white-space: nowrap;
}

nav ul {
  margin: 0;
  padding: 0;
}

nav ul li {
  list-style-type: none;
  display: inline-block;
  padding: 0.2em 0;
}

nav ul li a {
  padding: 0 5px;
  font-weight: bold;
}

nav ul li a:visited {
  color: #09c;
}

nav ul li a:hover {
  text-decoration: underline;
}
Здесь nav ul стилизует ul тег внутри nav тега, nav ul li стилизует li тег внутри ul тега внутри nav тега, и так далее.
layout_no_rounded_corners
Рисунок 5.5: Home страница (/pages/home) с отстиленной навигацией по сайту. (полный размер)
В качестве предпоследнего шага, мы сделаем ссылку на страницу регистрации на нашем сайте немного более очевидной. (Хотя на примере приложения нас это мало тревожит, на каком-либо реальном сайте это, естественно, очень важно — сделать регистрационную ссылку как можно более заметной.) Листинг 5.5 показывает CSS, который делает регистрационную ссылку большой, зеленой и кликабельной (клик в любом месте внутри бокса отправит по ссылке).
Листинг 5.5. CSS делающий регистрационную кнопку большой, зеленой и кликабельной.
public/stylesheets/custom.css
.
.
.

/* Sign up button */

a.signup_button {
  margin-left: auto;
  margin-right: auto;
  display: block;
  text-align: center;
  width: 190px;
  color: #fff;
  background: #006400;
  font-size: 150%;
  font-weight: bold;
  padding: 20px;
}
Здесь целая куча правил; как обычно, закомментируйте любую строку и перезагрузите страницу, если вы хотите увидеть, что она делает. Конечным результатом является регистрационная ссылка, которую трудно не заметить (Рис. 5.6).
signup_button
Рисунок 5.6: Home страница (/pages/home) с signup кнопкой. (полный размер)
В качестве последнего штриха, мы задействуем round класс, который мы присвоили многим элементам нашего сайта. Хотя в нынешних острых углах блоков нет ничего страшного, смягчение углов придаст интерфейсу бОльшую дружелюбность. Мы можем сделать это, используя CSS код из Листинга 5.6, результаты показаны на Рис. 5.7.
Листинг 5.6. Правила таблицы стилей для скругления углов.
public/stylesheets/custom.css
.
.
.
/* Round corners */

.round {
  -moz-border-radius:    10px;
  -webkit-border-radius: 10px;
  border-radius:         10px;
}
Стоит отметить, что этот трюк работает в Firefox, Safari, Opera, и во многих других браузерах, но это не работает в Internet Explorer. Есть способы получения скругленных углов, которые работают во всех браузерах, но нет никакой другой техники, которая хотя бы приблизилась к той легкости, с которой мы это только что проделали, так что мы просто рискнем оставить наших пользователей IE с несколькими крошечными порезами (от острых углов).
layout_rounded_corners
Рисунок 5.7: Home страница (/pages/home) со скругленными углами. (полный размер)

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

Хотя шаблон в Листинге 5.1 все еще служит своей цели, он становится немного суматошным: есть четыре строки, включающие CSS и восемь строк header-а, что логически, несет в себе всего две идеи (включить CSS и причесать header). Мы можем убрать эти разделы, используя удобный объект Rails называемый partials (частичные шаблоны, партиалы). Давайте сначала посмотрим, как шаблон выглядит после определения партиалов (Листинг 5.7).
Листинг 5.7. Шаблон сайта с партиалами для ссылок на таблицы стилей и header-а.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <%= csrf_meta_tag %>
    <%= render 'layouts/stylesheets' %>
  </head>
  <body>
    <div class="container">
      <%= render 'layouts/header' %>
      <section class="round">
        <%= yield %>
      </section>
    </div>
  </body>
</html>
В Листинге 5.7, мы заменили строки ссылок на таблицы стилей одним вызовом Rails помощника называемого render:
<%= render 'layouts/stylesheets' %>
Результатом этой строки является поиск файла, называемого app/views/layouts/_stylesheets.html.erb, оценка его содержания, и вставка результата оценки в представление.8 (Напомним, что <%= ... %> это Embedded Ruby синтаксис, необходимый для оценки выражения Ruby и последующей вставки результата в шаблон. Обратите внимание на символ подчеркивания в имени файла _stylesheets.html.erb; это подчеркивание - универсальное соглашение для именования частичных шаблонов, которое, среди прочего, позволяет идентифицировать все партиалы в каталоге с первого взгляда.
Конечно, чтобы заполучить частичные шаблоны на работу, мы должны заполнить их некоторым содержанием; в случае частичного шаблона для таблиц стилей, это всего лишь четыре ссылки на таблицы стилей, взятые из Листинга 5.1; результат представлен в Листинге 5.8. (Технически, HTML5 shiv включает JavaScript, а не CSS. C другой стороны, его назначение - дать возможность Internet Explorer-у понимать CSS с HTML5, так что, логически, он по-прежнему принадлежит к _stylesheets партиалу.)
Листинг 5.8. Частичный шаблон включающий таблицы стилей.
app/views/layouts/_stylesheets.html.erb
<!--[if lt IE 9]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<%= stylesheet_link_tag 'blueprint/screen', :media => 'screen' %>
<%= stylesheet_link_tag 'blueprint/print',  :media => 'print' %>
<!--[if lt IE 8]><%= stylesheet_link_tag 'blueprint/ie' %><![endif]-->
<%= stylesheet_link_tag 'custom', :media => 'screen' %>
Аналогичным образом, мы можем переместить в частичный шаблон и материал header-а Листинг 5.9 и вставить его в шаблон другим вызовом render:
<%= render 'layouts/header' %>
Листинг 5.9. Партиал для header-а сайта.
app/views/layouts/_header.html.erb
<header>
  <%= image_tag("logo.png", :alt => "Sample App", :class => "round") %>
  <nav class="round">
    <ul>
      <li><%= link_to "Home", '#' %></li>
      <li><%= link_to "Help", '#' %></li>
      <li><%= link_to "Sign in", '#' %></li>
    </ul>
  </nav>
</header>
Теперь, когда мы знаем, как сделать частичные шаблоны, давайте добавим подвал (footer) для каждой страницы сайта, чтобы он шел вместе с шапкой (header). К этому моменту вы, вероятно, догадались, что мы будем называть его _footer.html.erb и поместим его в layouts каталог (Листинг 5.10).
Листинг 5.10. Частичный шаблон для подвала сайта.
app/views/layouts/_footer.html.erb
<footer>
  <nav class="round">
    <ul>
      <li><%= link_to "About", '#' %></li>
      <li><%= link_to "Contact", '#' %></li>
      <li><a href="http://news.railstutorial.org/">News</a></li>
      <li><a href="http://www.railstutorial.org/">Rails Tutorial</a></li>
    </ul>
  </nav>
</footer>
Как и в шапке (header), в подвале (footer) мы использовали link_to для внутренних ссылок на About и Contact страницы и заглушили URL-адреса символом ’#’ до лучших времен. (Как и header, footer - новый, HTML5, тег.)
Мы можем вставить партиал подвала в шаблон тем же образом, что и stylesheets и header партиалы (Листинг 5.11).
Листинг 5.11. Шаблон сайта с партиалом подвала.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <%= csrf_meta_tag %>
    <%= render 'layouts/stylesheets' %>
  </head>
  <body>
    <div class="container">
      <%= render 'layouts/header' %>
      <section class="round">
        <%= yield %>
      </section>
      <%= render 'layouts/footer' %>
    </div>
  </body>
</html>
Конечно, подвал будет уродливым без какого-либо стиля (Листинг 5.12). Результаты представлены на Рис. 5.8.
Листинг 5.12. Добавлениеe CSS для частичного шаблона подвала.
public/stylesheets/custom.css
.
.
.
footer {
  text-align: center;
  margin-top: 10px;
  width: 710px;
  margin-left: auto;
  margin-right: auto;
}

footer nav {
  float: none;
}
Отметим здесь правило
footer nav {
  float: none;
}
которое перекрывает предыдущее правило
nav {
  float: right;
}
и смещает подвал в нижнюю часть страницы, а не отталкивает вправо, как навигацию в заголовке. Это конвенция наличия правопреемства правил, позволяющая последующим правилам переопределять предыдущие — то, что делает каскадные таблицы стилей именно каскадными.
site_with_footer
Рисунок 5.8: Home страница (/pages/home) с добавленным подвалом. (полный размер)

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

Теперь, когда мы закончили шаблон сайта с достойным стилем, пришло время приступить к заполнению ссылок которые мы заглушили символом ’#’. Конечно, мы могли бы жестко захардкодить ссылки:
<a href="/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, вместе с соответствием URI и маршрутов. Со временем, мы реализуем все ссылки, но предпоследнюю мы добавим только в конце этой главы, а последняя будет создана лишь в Главе 9.
СтраницаURLИменованный маршрут
Home/root_path
About/aboutabout_path
Contact/contactcontact_path
Help/helphelp_path
Sign up/signupsignup_path
Sign in/signinsignin_path
Таблица 5.1: Маршруты и соответствующие URL для ссылок сайта.

5.2.1 Интеграционные тесты

Перед написанием маршрутов для нашего приложения, мы продолжим с нашим TDD, написав несколько тестов для них. Есть несколько способов проверить маршруты, и я собираюсь воспользоваться этой возможностью, чтобы представить интеграционные (объединенные) тесты, которые дают нам возможность симулировать доступ браузера к нашему приложению и таким образом протестировать его от начала и до конца. Как мы увидим в Разделе 8.4, тестирование маршрутов это только начало.
Мы начнем с генерации интеграционного теста для ссылок в шаблоне примера приложения:
$ rails generate integration_test layout_links
      invoke  rspec
      create    spec/requests/layout_links_spec.rb
Обратите внимание, что генератор автоматически добавляет _spec.rb к имени нашего тестового файла, сохраняя его как spec/requests/layout_links_spec.rb. (В RSpec, интеграционные тесты также называют request specs (спецификация запросов); происхождение этой терминологии мне неясно.)
Наши интеграционные тесты будут использовать ту же get функцию, что мы использовали в Разделе 3.2 в Pages контроллер spec, с кодом вроде этого:
describe "GET 'home'" do
  it "should be successful" do
    get 'home'
    response.should be_success
  end
end
В этом разделе мы хотим проверить такие URL-адреса, как / и /about, но вы не можете get эти URL-адреса внутри контроллера теста — контроллер тестов знает только об URL-адресах, определенных для этого конкретного контроллера. В отличие от интеграционных (объединенных) тестов, не связанных такими ограничениями, так как они выполнены в виде интегрированных (комплексных, объединенных) тестов для всего приложения и, следовательно, могут get любую страницу, какую захотят.
Следуя модели Pages контроллера spec, мы можем написать интеграционную спецификацию (spec) для каждой из страниц, указанных в Таблице 5.1 (за исключением тех, которые мы еще не создали); а именно, Home, About, Contact, и Help. Чтобы убедиться в правильности воспроизводимой в каждом случае страницы (т.е. представления), мы проверим правильность заголовка используя have_selector. Тесты представлены в Листинге 5.13.
Листинг 5.13. Интеграционные тесты для маршрутов.
spec/requests/layout_links_spec.rb
require 'spec_helper'

describe "LayoutLinks" do

  it "should have a Home page at '/'" do
    get '/'
    response.should have_selector('title', :content => "Home")
  end

  it "should have a Contact page at '/contact'" do
    get '/contact'
    response.should have_selector('title', :content => "Contact")
  end

  it "should have an About page at '/about'" do
    get '/about'
    response.should have_selector('title', :content => "About")
  end

  it "should have a Help page at '/help'" do
    get '/help'
    response.should have_selector('title', :content => "Help")
  end
end
Конечно, в этой точке они должны быть провальными (Красными), мы сделаем их Зелеными в Разделе 5.2.2.
Кстати, если у вас нет Help страницы в этой точке, то сейчас хорошее время, чтобы добавить ее. (Если вы решили упражнения Главы 3 в Разделе 3.5, она у вас уже есть.) Во-первых, добавьте help действие в контроллер Pages (Листинг 5.14). Затем создайте соответствующее представление (Листинг 5.15).
Листинг 5.14. Добавление help действия в контроллер Pages.
app/controllers/pages_controller.rb
class PagesController < ApplicationController
  .
  .
  .
  def help
    @title = "Help"
  end
end
Листинг 5.15. Добавление представления для Help страницы.
app/views/pages/help.html.erb
<h1>Help</h1>
<p>
  Get help on Ruby on Rails Tutorial at the
  <a href="http://railstutorial.org/help">Rails Tutorial help page</a>.
  To get help on this sample app, see the
  <a href="http://railstutorial.org/book">Rails Tutorial book</a>.
</p>
Есть одна финальная деталь, с которой нужно разобраться, прежде чем двигаться дальше: если вы запустили Autotest, вы могли заметить, что он не работает с интеграционным тестом. Видимо, это является особенностью данного продукта, так как интеграционный тест может быть медленным, и, следовательно, может сорвать красный-зеленый-реорганизовать (refactor) цикл. Тем не менее, я все же предпочел включить интеграционный тест в Автотест. Чтобы заставить его работать, вы просто должны сказать Автотесту выполнять тесты в spec/requests каталоге (Листинг 5.16 или Листинг 5.17).
Листинг 5.16. Дополнения к .autotest, необходимые для запуска интеграционного (объединенного) теста с Autotest на OS X.
Autotest.add_hook :initialize do |autotest|
  autotest.add_mapping(/^spec\/requests\/.*_spec\.rb$/) do
    autotest.files_matching(/^spec\/requests\/.*_spec\.rb$/)
  end
end
Листинг 5.17. Дополнения к .autotest, необходимые для запуска интеграционного теста с Autotest на Ubuntu Linux.
Autotest.add_hook :initialize do |autotest|
  autotest.add_mapping(%r%^spec/(requests)/.*rb$%) do|filename, _|
    filename
  end
end
Не беспокойтесь о том, откуда этот код появился; Я также не знаю Autotest API. В какой-то момент я Гуглил в поиске таких терминов, как "RSpec autotest integration" и нашел его, и когда я бросил его в мой .autotest файл, он работал.)

5.2.2 Rails маршруты

Теперь, когда у нас есть тесты для URL, такие как мы хотим, пришло время заставить их работать. Как отмечалось в Разделе 3.1.2, Rails использует для маршрутов файл config/routes.rb. Если вы посмотрите на дефолтный (по умолчанию) файл routes.rb, вы увидите беспорядок, но это полезный беспорядок — полный закомментированных примеров маршрутов. Я советую почитать его как-нибудь, а также взглянуть на Rails Guides article “Rails Routing from the outside in” (# перевод этой статьи, а также многих других из данного руководства можно найти на сайте http://www.rusrails.ru/) для более углубленного изучения маршрутов. Сейчас, однако, мы будем придерживаться примеров в Листинге 5.18.9
Листинг 5.18. Маршруты для статических страниц.
config/routes.rb
SampleApp::Application.routes.draw do
  match '/contact', :to => 'pages#contact'
  match '/about',   :to => 'pages#about'
  match '/help',    :to => 'pages#help'
  .
  .
  .
end
Листинг 5.18 содержит пользовательские маршруты для contact, about, и help страниц; маршрутом для home страницы мы озаботимся в Листинге 5.20. (Раз уж мы будем пользоваться маршрутами из Листинга 5.18 исключительно с этого момента, мы воспользуемся этой возможностью чтобы удалить маршруты контроллера Pages (get "pages/home", и т.д.) последний раз мы их видели в Листинге 3.17.)
Если вы вчитаетесь в код Листинга 5.18 повнимательнее, вы, вероятно, сможете понять что он делает; например, вы можете видеть, что
match '/about', :to => 'pages#about'
отождествляет ’/about’ и направляет его к about действию контроллера Pages. До этого было более четко: мы использовали get ’pages/about’ чтобы попасть в то же место, но /about более кратко. Не очевидным является то, что match ’/about’ также автоматически создает именованные маршруты для использования в контроллерах и представлениях:
about_path => '/about'
about_url  => 'http://localhost:3000/about'
Обратите внимание, что about_url это полный URL http://localhost:3000/about (localhost:3000 заменится на доменное имя, например example.com, при полном развертывании сайта). Как уже говорилось в Разделе 5.2, чтобы получить только /about, вы используете about_path. (Rails Tutorial использует path форму по соглашению, но разница редко имеет значение на практике (# т.е также возможно применение _url формы).)
С помощью этих маршрутов, теперь определенных, тесты для About, Contact, и Help страниц должны пройти (Как обычно, используйте Autotest или rspec spec/ для проверки.) Кроме теста для Home страницы.
Чтобы установить соответствие маршрута для домашней страницы, мы могли бы использовать код подобный этому:
match '/', :to => 'pages#home'
Однако делать этого не придется; Rails имеет специальный маршрут для корневого URL / (“слэш”) расположенный внизу файла (Листинг 5.19).
Листинг 5.19. Закомментированная подсказка для определения root маршрута.
config/routes.rb
SampleApp::Application.routes.draw do
  .
  .
  .
  # You can have the root of your site routed with "root"
  # just remember to delete public/index.html.
  # root :to => "welcome#index"
  .
  .
  .
end
Используя Листинг 5.19 как модель, мы приходим к Листингу 5.20 чтобы направить root URL / к Home странице.
Листинг 5.20. Добавление соответствия для root маршрута.
config/routes.rb
SampleApp::Application.routes.draw do
  match '/contact', :to => 'pages#contact'
  match '/about',   :to => 'pages#about'
  match '/help',    :to => 'pages#help'

  root :to => 'pages#home'
  .
  .
  .
end
Этот код направляет корневой URL / к /pages/home, и также дает URL помощников следующим образом:
root_path => '/'
root_url  => 'http://localhost:3000/'
Мы должны также прислушаться к коментарию в Листинге 5.19 и удалить public/index.html чтобы предотвратить отображение дефолтной страницы (Рис. 1.3) при посещении /. Конечно, вы можете просто удалить файл выполненив Move to trash в текстовом редакторе, но если вы используете Git для контроля версий, есть способ удалить файл и в тоже время сообщить Git об удалени — с помощью git rm:
$ git rm public/index.html
$ git commit -am "Removed default Rails page"
Вы можете вспомнить из Раздела 1.3.5 что мы использовали Git команду git commit -a -m "Message", с флагами для “все изменения” (-a) и сообщения (-m). Как показано выше, Git также позволяет скрутить два флага в один git commit -am "Message".
При этом, все маршруты для статических страниц работают, и тесты должны пройти. Теперь мы просто должны заполнить ссылки в макете.

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

Давайте применим именованные маршруты, созданные в Разделе 5.2.2. Это повлечет за собой заполнение второго аргумента link_to функции правильным именованным маршрутом. Например, мы конвертируем
<%= link_to "About", '#' %>
в
<%= link_to "About", about_path %>
и так далее.
Мы начнем с партиала шапки, _header.html.erb (Листинг 5.21), который содержит ссылки на Home и Help страницы. Пока мы здесь, мы также, следуя общей конвенции Сети, свяжем логотип с Home страницей.
Листинг 5.21. Партиал шапки со ссылками.
app/views/layouts/_header.html.erb
<header>
  <% logo = image_tag("logo.png", :alt => "Sample App", :class => "round") %>
  <%= link_to logo, root_path %>
  <nav class="round">
    <ul>
      <li><%= link_to "Home", root_path %></li>
      <li><%= link_to "Help", help_path %></li>
      <li><%= link_to "Sign in", '#' %></li>
    </ul>
  </nav>
</header>
У нас не будет именованного маршрута для “Sign in” ссылки, до Главы 9, поэтому мы пока оставили ее в виде ’#’. Обратите внимание, что этот код определяет локальную переменную logo для тега логотипа, а затем делает ссылку на него в следующей строке:
  <% logo = image_tag("logo.png", :alt => "Sample App", :class => "round") %>
  <%= link_to logo, root_path %>
Это немного чище, нежели упихивать все в одну строку. Особенно важно отметить, что ERb для присваивания не имеет знака равенства, это просто <% ... %>, Потому что мы не хотим, чтобы эта строка вставлялась в шаблон. (Использование локальной переменной таким образом - это только один из способов сделать это. Более чистым способом будет определение logo помощника; см. Раздел 5.5.)
Другим местом со ссылками является подвал, _footer.html.erb, в котором есть ссылки для About и Contact страниц (Листинг 5.22).
Листинг 5.22. Частичный шаблон подвала со ссылками.
app/views/layouts/_footer.html.erb
<footer>
  <nav class="round">
    <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>
      <li><a href="http://www.railstutorial.org/">Rails Tutorial</a></li>
    </ul>
  </nav>
</footer>
При этом, наш макет имеет ссылки на все статические страницы, созданные в Главе 3, так, например, /about ведет к About странице (Рис. 5.9).
Кстати, стоит отметить, что мы на самом деле не протестировали на наличие ссылок в шаблоне, наши тесты будут провальными, только если маршруты не определены. Вы можете проверить это, закомментировав один из маршрутов в Листинге 5.18 и выполнив тесты. Метод тестирования, который на самом деле обеспечивает уверенность что ссылки существуют и отправляют в правильные места, см. Разделе 5.5.
about_page
Рисунок 5.9: About страница на /about (полный размер)

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

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

5.3.1 Users контроллер

Мы создали наш первый контроллер Pages, еще в Разделе 3.1.2. Пришло время создать второй — контроллер Users. Как и прежде, мы будем использовать generate для создания простейшего контроллер, который отвечает нашим текущим потребностям, а именно, с одной страницей-заглушкой для регистрации новых пользователей. Следуя конвенции REST архитектуры предпочитаемой Рельсами, мы назовем действие для новых пользователей, new и передадим его в качестве аргумента в generate controller для создания его (действия) автоматически (Листинг 5.23).
Листинг 5.23. Генерация контроллера Users (с new действием).
$ rails generate controller Users new
      create  app/controllers/users_controller.rb
       route  get "users/new"
      invoke  erb
      create    app/views/users
      create    app/views/users/new.html.erb
      invoke  rspec
      create    spec/controllers/users_controller_spec.rb
      create    spec/views/users
      create    spec/views/users/new.html.erb_spec.rb
      invoke  helper
      create    app/helpers/users_helper.rb
      invoke    rspec
      create    spec/helpers/users_helper_spec.rb
Как и с контроллером Pages, это генерирует specs (спеки) для представлений и хелперов которые нам не понадобится, так что удалите их:
$ rm -rf spec/views
$ rm -rf spec/helpers
Генератор контроллера создает как Users контроллер, так и полезный дефолтный тест, который проверяет, что new действие реагирует должным образом на запрос GET (Блок 3.1); код представлен в Листинг 5.24. Этот код должен выглядеть знакомым; он следует точно такой же форме, что и Pages контроллер spec в последний раз виденный в Разделе 3.3.1 (Листинг 3.20).
Листинг 5.24. Тестирование signup страницы.
spec/controllers/users_controller_spec.rb
require 'spec_helper'

describe UsersController do

  describe "GET 'new'" do
    it "should be successful" do
      get 'new'
      response.should be_success
    end
  end
end
По конструкции, контроллер Users уже имеет надлежащее new действие и new.html.erb представление, чтобы пройти этот тест (Листинг 5.25). (Для просмотра страницы /users/new, вам может потребоваться перезагрузка сервера.)
Листинг 5.25. Действие для страницы "новый пользователь" (signup).
app/controllers/users_controller.rb
class UsersController < ApplicationController

  def new
  end

end
Чтобы вернуться к духу Test-Driven Development, давайте добавим собственный (провальный) тест проверяющий заголовок (тайтл) на содержание строки "Sign up" (Листинг 5.26). Не забудьте добавить render_views как мы это делали в Pages контроллере spec (Листинг 3.20); в противном случае, тест не пройдет, даже после того как мы добавим правильный заголовок (тайтл).
Листинг 5.26. Тест для заголовка signup страницы.
spec/controllers/users_controller_spec.rb
require 'spec_helper'

describe UsersController do
  render_views

  describe "GET 'new'" do
    it "should be successful" do
      get 'new'
      response.should be_success
    end

    it "should have the right title" do
      get 'new'
      response.should have_selector("title", :content => "Sign up")
    end
  end
end
Этот тест использует have_selector метод (который мы видели ранее (Раздел 3.3.1); отмечу, что как и в Разделе 3.3.1, have_selector нуждается в render_views строке чтобы он мог тестировать представления вместе с действиями.
Конечно, по задумке этот тест сейчас провальный (Красный). Чтобы создать пользовательский заголовок, нам нужно сделать @title переменную экземпляра как в Разделе 3.3.3.Таким образом, мы можем получить Зеленый с кодом в Листинге 5.27.
Листинг 5.27. Назначение пользовательского заголовка для new user страницы.
app/controllers/users_controller.rb
class UsersController < ApplicationController

  def new
    @title = "Sign up"
  end
end

5.3.2 Signup URL

С кодом из Раздела 5.3.1, мы уже имеем работающую страницу для нового пользователя на /users/new, но вспомним из Таблицы 5.1 что мы хотим, чтобы вместо него был URL /signup. Как и в Разделе 5.2, мы вначале напишем тест (Листинг 5.28).
Листинг 5.28. Простой интеграционный тест для user signup ссылки.
spec/requests/layout_links_spec.rb
require 'spec_helper'

describe "LayoutLinks" do
  .
  .
  .
  it "should have a signup page at '/signup'" do
    get '/signup'
    response.should have_selector('title', :content => "Sign up")
  end
end
Отметим, что это тот же файл, который используется для других ссылок шаблона, хотя Signup страница в другом контроллере. Возможность тестировать страницы из нескольких контроллеров является одним из преимуществ использования интеграционных (объединенных) тестов.
Последний шаг это создание именованного маршрута для регистрации. Мы будем следовать примерам из Листинга 5.18 и добавим match ’/signup’ правило для signup URL (Листинг 5.29).
Листинг 5.29. Маршрут для страницы регистрации.
config/routes.rb
SampleApp::Application.routes.draw do
  get "users/new"

  match '/signup',  :to => 'users#new'

  match '/contact', :to => 'pages#contact'
  match '/about',   :to => 'pages#about'
  match '/help',    :to => 'pages#help'

  root :to => 'pages#home'
  .
  .
  .
end
Обратите внимание, мы сохранили правило get "users/new", которое было сгенерированно автоматически при создании Users контроллера в Листинге 5.23. В настоящее время это правило необходимо для работы роутинга /users/new правильному, но не соответствующему REST конвенции (Таблица 2.2), и мы ликвидируем его в Разделе 6.3.3.
В этой точке тест для регистрации из Листинга 5.28 должен пройти. Все, что осталось, это добавить правильную ссылку к кнопке на главной странице. Как и с другими маршрутами, match ’/signup’ дает нам именованный маршрут signup_path, который мы поместим для использования в Листинге 5.30.
Листинг 5.30. Добавление ссылки к кнопке Signup страницы.
app/views/pages/home.html.erb
<h1>Sample App</h1>

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

<%= link_to "Sign up now!", signup_path, :class => "signup_button round" %>
К сделанной ссылке и именованному маршруту, мы в последствиии добавим маршрут для входа (Глава 9). Результирующая new user страница (на URL /signup) представлена на Рис. 5.10.
blank_signup_page
Рисунок 5.10: Новая signup страница на /signup(полный размер)

5.4 Заключение

В этой главе мы привели шаблон нашего приложение в чувство и отполировали маршруты. Остальная часть книги посвящена конкретизации примера приложения: во-первых, путем добавления пользователей, которые могут зарегистрироваться, войти в систему и выйти; затем добавлением пользовательских микросообщений и, наконец, добавлением пользовательских взаимоотношений.
Если вы используете Git, убедитесь, что зафиксировали и объединили изменения (и, просто чтобы быть параноиком, запустите тесты в первую очередь):
$ bundle exec rspec spec/
$ git add .
$ git commit -m "Finished layout and routes"
$ git checkout master
$ git merge filling-in-layout
Вы, возможно, также захотите отправить в GitHub, или развернуть на Heroku:
$ git push
$ git push heroku

5.5 Упражнения

  1. Заменить локальную переменную logo в Листинге 5.21 на вспомогательный метод с тем же названием, так чтобы новый частичный шаблон выглядел как Листинг 5.31. Используйте код из Листинга 5.32 чтобы начать работу.
  2. Возможно, вы заметили, что наши тесты для ссылок шаблона тестируют маршруты, но на самом деле не проверяют, ведут ли ссылки к правильным страницам. Один из способов реализации этих испытаний заключается в использовании visit и click_link внутри RSpec интеграционного теста. Дополните код из Листинга 5.33 чтобы проверить что все ссылки шаблона правильно определены.
Листинг 5.31. Партиал шапки с помощником logo из Листинга 5.32.
app/views/layouts/_header.html.erb
<header>
  <%= link_to logo, root_path %>
  <nav class="round">
    <ul>
      <li><%= link_to "Home", root_path %></li>
      <li><%= link_to "Help", help_path %></li>
      <li><%= link_to "Sign in", '#' %></li>
    </ul>
  </nav>
</header>
Листинг 5.32. Образец для помощника logo
app/helpers/application_helper.rb
module ApplicationHelper

  def logo
    # Fill in.
  end

  # Return a title on a per-page basis.
  def title
    .
    .
    .
  end
end
Листинг 5.33. Тесты для ссылок в шаблоне.
spec/requests/layout_links_spec.rb
require 'spec_helper'

describe "LayoutLinks" do
  .
  .
  .
  it "should have the right links on the layout" do
    visit root_path
    click_link "About"
    response.should have_selector('title', :content => "About")
    click_link "Help"
    response.should # fill in
    click_link "Contact"
    response.should # fill in
    click_link "Home"
    response.should # fill in
    click_link "Sign up now!"
    response.should # fill in
  end
end
  1. Макеты в Ruby on Rails Tutorial сделаны с помощью замечательного онлайн приложения для создания макетов, называемого Mockingbird
  2. Они абсолютно не связаны с классами Ruby. 
  3. Вы моли заметить, что img tag, вместо сходства с <img>...</img>, более похож на <img ... />. Теги, соответствующие этой форме, известны как самозакрывающиеся
  4. alt текст также, будет выведен на экран средствами чтения с экрана для людей со слабым зрением. Хотя люди иногда неаккуратны при включении alt атрибута для изображений, фактически, это требуется HTML стандартом. К счастью, Rails включает дефолтный alt атрибут; если вы не укажете значение атрибута, в названии image_tag, Rails просто будет использовать имя файла изображения (за вычетом расширения). В этом случае, тем не менее, Sample App является более дескриптивным чем logo, таким образом я предпочел указать alt текст явно. 
  5. Отметьте, что пользователи Chrome и Safari будут видеть индикатор поврежденного изображения, вместо альтернативного текста “Sample App”. 
  6. Цвета HTML могут быть кодированы тремя основными 16 (шестнадцатеричными) числами, каждое из которых обозначает один из трех основных цветов, Красный, Зеленый, и Синий. #fff это максимум всех трех цветов, что приводит к чистому белому. За подробностями обращайтесь на сайт HTML colors
  7. Blueprint CSS испльзует сетку из столбцов шириной 40 пикселей, 30 пикселей для самого столбца и 10 пикселей отступа. Самый правый столбец в отступе не нуждается, таким образом, 18 столбцов это 710 пикселей: 18 * 40 – 10 = 710. 
  8. Многие Rails разработчики используют shared директорию для партиалов совместно используемых разными представлениями. Я предпочитаю использовать shared папку для "служебных" партиалов которые используются во множестве страниц, помещая партиалы, которые используются буквально на каждой странице (такие как части шаблона сайта) в layouts директорию. (Мы создадим shared директорию в Главе 8.) Это кажется мне более логичным, но помещение всех партиалов в папку shared тоже замечательно работает. 
  9. В строке SampleApp::Application.routes.draw do вы могли распознать, что draw метод берет block, конструкцию, которую мы видели в Разделе 4.3.2

Комментариев нет:

Отправить комментарий