Ruby on Rails チュートリアル

プロダクト開発の0→1を学ぼう

第2版 目次

第5章レイアウトを作成する

この章では、アプリケーションにBootstrapフレームワークを組み込み、そして、カスタムスタイルを追加します1。また5.1までに、作成するページへのリンクをレイアウトに追加します。その途中で、パーシャル、Railsのルーティング、アセットパイプラインについて学び、さらにSassについても紹介します(5.2)。 また、第3章 で作成したテストを、最新のRSpecテクニックを用いてリファクタリングします。章の最後に、ユーザをサイトにログインさせるための重要な一歩を踏み出します。

5.1構造を追加する

RailsチュートリアルはWeb開発のための本であり、Webデザインの本ではありませんが、だからといって何のスタイルもない寒々しい外観のアプリケーションでいつまでも作業を続けていると憂鬱になってしまいます。そこで、この章ではレイアウトにいくつかの構造とCSSを与えて最小限のスタイルを追加します。カスタムCSSルールの他に、Twitter社によるオープンソースのWebデザインフレームワークであるBootstrapを利用します。また、コードそのものにもスタイルを与えます。つまり、散らかりはじめたコードレイアウトをパーシャルを使用して整えるということです。

Webアプリケーションを作成するときに、ユーザーインターフェイスの概要をできるだけ早いうちに把握しておくことがしばしば有用です。本書ではこれより、モックアップ (Webの文脈ではよく ワイヤーフレームと呼ばれます) という、最終的なアプリケーションの外観を示す一種のラフスケッチを使用することにします2。この章では、 主に3.1で紹介したサイトロゴ、ナビゲーションヘッダー、サイトフッタを含む静的ページを開発します。これらのページの中で最も重要な、Homeページのモックアップを図5.1に示します。モックアップに基いて作成した最終結果は図5.7で確認することができます。両者を見比べると、細部が若干異なることに気が付くでしょう (たとえば、実際には最後にRailsのロゴをページに追加します)。しかしモックアップは正確である必要はありませんので、これで十分です。

home_page_mockup_bootstrap
図5.1サンプルアプリケーションのHomeページのモックアップ。(拡大)

Gitでバージョン管理をしているのであれば、これまでと同様、この時点で新しいブランチを作成するのがよいでしょう。

$ git checkout -b filling-in-layout

5.1.1ナビゲーション

第一段階として、サンプルアプリケーションにリンクとスタイルを追加するために、サイトのレイアウトファイルapplication.html.erb (リスト4.3で登場) にHTML構造を追加し、レイアウトファイルを更新します。この更新には、領域 (divタグ) の追加、CSSクラスの追加、サイトナビゲーションの起点となる領域の追加も含まれます。完全なファイルはリスト5.1に示します。続いて、これを構成している多くの部品について解説します。表示結果を今すぐ確認したいのであれば、図5.2で確認できます (注:この時点ではわざわざ見に行くほどの仕上がりではありませんが)。

リスト5.1 構造を追加したWebサイトのレイアウト。
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= stylesheet_link_tag    "application", media: "all" %>
    <%= javascript_include_tag "application" %>
    <%= csrf_meta_tags %>
    <!--[if lt IE 9]>
    <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]-->
  </head>
  <body>
    <header class="navbar navbar-fixed-top navbar-inverse">
      <div class="navbar-inner">
        <div class="container">
          <%= link_to "sample app", '#', id: "logo" %>
          <nav>
            <ul class="nav pull-right">
              <li><%= link_to "Home",    '#' %></li>
              <li><%= link_to "Help",    '#' %></li>
              <li><%= link_to "Sign in", '#' %></li>
            </ul>
          </nav>
        </div>
      </div>
    </header>
    <div class="container">
      <%= yield %>
    </div>
  </body>
</html>

上のコードで最初に注目すべきことの1つは、Ruby 1.8スタイルのハッシュがRuby 1.9スタイルに変更されていることです (4.3.3)。以下のコードが、

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

以下の記述に置き換えられています。

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

旧式のハッシュ構文は、特に古いアプリケーションでは未だに一般的なので、両方を理解できることが重要です。

それでは、リスト5.1のその他の新しい要素を上から順に見ていきましょう。3.1で簡単に説明したように、Rails 3はデフォルトで HTML5を使用します (doctype <!DOCTYPE html>で示されています)。HTML5標準は比較的新しいため、ブラウザ (特に、古いバージョンのIE) によってはサポートが不十分なことがあります。この問題を回避するため、(“HTML5 shim”として知られる) JavaScriptコードをインクルードしてあります。

<!--[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 で特別にサポートされています。これにより、Firefox、Chrome、Safariなどの他のブラウザに影響を与えずに、IEのバージョンが9未満の場合にのみHTML5 shimをインクルードすることができるため、非常に好都合です。

それに続くセクションには、サイトのロゴを表示するheader、(divタグによる) いくつかの領域、ナビゲーションリンクのリストがあります。

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

headerタグは、ページのトップに来るべき要素を表します。このheaderタグには、navbarnavbar-fixed-topnavbar-inverseという3つのCSSクラス3がスペース区切りで与えられています。

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

すべてのHTML要素には、クラスとidの両方を指定することができます。これらは単なるラベルで、CSSでスタイルを指定するときに便利です(5.1.2)。クラスとIDの主な違いは、クラスはページの中で何度でも使用できるのに対し、IDは一度しか使用することができない点です。今回の場合、すべてのnavbarクラスには、5.1.2でインストールするBootstrapフレームワークによって特別な意味を与えられます。

headerタグの内側には2つのdivタグがあります。

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

divタグは一般的な表示領域を表し、ドキュメントを別々のパーツに分ける以外のことはしません。古いスタイルのHTMLでは、divタグはサイトのほぼすべての領域に使用されますが、HTML5では多くのアプリケーションに共通の領域で使用するheader要素、nav要素、section要素を追加します。この場合、それぞれのdivにはCSSクラスが与えられています。headerタグのクラスと同様に、これらのクラスもBootstrapにおいて特別な意味を持っています。

divに続いて、埋め込み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>

ここでは、リンク (3.3.2で、アンカータグaを使用して作成) を生成するために、Railsヘルパーのlink_toを使用しています。link_toの第1引数はリンクテキスト、第2引数はURIです。このURIは5.3.3名前付きルートのURIに変更しますが、今はWebデザインで一般に使用されるスタブURI’#’にしておきます。第3引数はオプションハッシュで、この場合はサンプルアプリのリンクでCSSのid logoを指定しています (他の3つのリンクにはオプションハッシュが指定されていませんが、必須ではないので構いません)。Railsヘルパーは、このようにオプションのハッシュを取ることがよくあり、これによりRailsのコードから離れることなく任意のHTMLオプションを柔軟に追加することができます。

divの内側の2番目の要素は、リストアイテムタグli順不同リストタグulによって作られた、ナビゲーションリンクのリストです。

<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タグはその内側がナビゲーションリンクであるという意図を伝える役割があります。ulタグのnavpull-rightクラスは、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メソッドはWebサイトのレイアウトにページごとの内容を挿入します。

5.1.3で追加するサイトフッターを除いて、これでレイアウトは完成しました。Homeページへアクセスして表示結果を確認することができます。今後登場するスタイル要素を利用できるようにするために、home.html.erbビューに特別な要素をいくつか追加します(リスト5.2)。

リスト5.2 ログインページへのリンクがあるHomeページ。
app/views/static_pages/home.html.erb
<div class="center hero-unit">
  <h1>Welcome to the Sample App</h1>

  <h2>
    This is the home page for the
    <a href="http://railstutorial.jp/">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は、signupボタンのbtnクラス、btn-largeクラス、btn-primaryクラスと同様、Bootstrapにおいて特別な意味を持ちます。

2番目のlink_toでは、引数として画像ファイルのパスと任意のオプションハッシュをとるimage_tagヘルパーの能力が示されています。シンボルを使用して、この場合はalt属性を設定しています。わかりやすくするために、このタグによって生成されるHTMLを以下に示しました4

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

alt属性は、画像がない場合に代わりに表示される文字列です。また、視覚障がいのあるユーザーが使用するスクリーンリーダーでは、この属性が読み上げられてそこに画像があることが示されます。HTML標準では実際に要求されているにも関わらず、画像にalt属性を付けていない手抜きのWebサイトをときどき見かけます。Railsでは幸いにも、この属性を指定せずにimage_tagを呼び出した場合は、画像ファイル名 (拡張子を除く) をデフォルトのalt属性として自動的に付加してくれます。なお、今回は (先頭が大文字の) "Rails"とするためにaltテキストを明示的に設定しています。

いよいよ、ここまでの労働の成果を確認する準備ができました (図5.2)。思っていたよりもみすぼらしいでしょうか? 確かにそうですね。しかし、HTML要素に実用的なクラスを与えるという良い仕事ができたのも確かです。さらに、クラスを与えたこの段階で、CSSを使用してサイトにスタイルを与えることができたのは、タイミングとして非常に適切であると思います。

ところで、rails.png画像が実際に表示されていることに気付いて驚いたかもしれません。この画像はどこから来たのでしょうか。実は、この画像は新しいRailsアプリケーションには必ず無償で含まれており、app/assets/images/rails.pngに置かれています。image_tagヘルパーを使用したことにより、Railsはアセットパイプライン (5.2) を使用してこの画像を自動的に見つけます。

layout_no_logo_or_custom_css_bootstrap
図5.2カスタムCSSを使用していないHomeページ (/static_pages/home)。(拡大)

5.1.2BootstrapとカスタムCSS

5.1.1では、多くのHTML要素にCSSクラスを関連付けました。こうしておくことで、CSSベースでレイアウトを構成する際に高い柔軟性を与えてくれます。5.1.1で述べたように、これらのクラスの多くは、Twitterが作成したフレームワークであるBootstrap特有のものです。Bootstrapを使用すると、洗練されたWebデザインとユーザーインターフェイス要素を簡単にHTML5アプリケーションに追加することができます。この節では、サンプルアプリケーションにスタイルを追加するために、カスタムCSSルールとBootstrapを組み合わせて使用します。

最初に、リスト5.3で示しているようにBootstrapを追加しましょう。これは、bootstrap-sass gemを使用してRailsアプリケーションに導入できます。Bootstrapフレームワークでは、動的なスタイルシートを生成するためにLESS CSS言語を使用していますが、Railsのアセットパイプラインはデフォルトでは (LESSと非常によく似た) Sass言語をサポートします (5.2)。そのため、bootstrap-sassは、LESSをSassへ変換し、必要なBootstrapファイルが現在のアプリケーションですべて利用できるようにします5

リスト5.3 Gemfilebootstrap-sassを追加する。
source 'https://rubygems.org'

gem 'rails', '3.2.14'
gem 'bootstrap-sass', '2.1'
.
.
.

いつものようにbundle installを実行して、Bootstrapをインストールします。

$ bundle install

次に、開発中のアプリケーションに変更を反映するために、Webサーバーを再起動します (ほとんどのシステムでは、最初にCtrl-Cを押下してサーバーを停止し、次にrails serverコマンドを実行することでサーバーを再起動できます)。

アプリケーションにカスタムCSSを追加するための第一段階として、カスタムCSSを格納するための以下のファイルを作成します。

app/assets/stylesheets/custom.css.scss

(テキストエディタかIDEで新しいファイルを作成してください)。 このディレクトリ名とファイル名は、どちらも重要です。以下のディレクトリは、

app/assets/stylesheets

アセットパイプライン(5.2)の一部であり、このディレクトリに置かれたスタイルシートはapplication.cssの一部として自動的にサイトレイアウトにインクルードされます。さらに、ファイル名のcustom.css.scssには.cssという拡張子も含まれているので、このファイルはCSSファイルであることが示されています。また、.scssという拡張子も含まれているので、 このファイルはSassを記述できるCSSファイル (Sassy CSS: Scss) であることも示されており、アセットパイプラインはこれを見てSassを処理できるようにします (Sassは5.2.2まで登場しませんが、bootstrap-sass gemが動作するためのおまじないとして必要です)。

カスタムCSS用のファイルを作成したら、リスト5.4のように@importを使用してBootstrapをインクルードします。

リスト5.4 Bootstrap CSSを追加する。
app/assets/stylesheets/custom.css.scss
@import "bootstrap";

図5.3の結果で示されるように、このひとつの行にBootstrapフレームワーク全体が含まれます。(場合によっては、Ctrl-Cを使用してローカルのWebサーバーを再起動する必要があるかもしれません。また、スクリーンショットではBootstrap 2.0を使用していますが、このチュートリアルではBootstrap 2.1を使用しているために、おそらく見た目に多少の違いが生じている可能性があることをご了承ください。これらについて心配する必要はありません。) さて、テキストの配置は今ひとつで、ロゴにはスタイルもありませんが、色使いとsignupボタンはなかなかよい感じになってきました。

sample_app_only_bootstrap
図5.3Bootstrap CSSとサンプルアプリケーション。(拡大)

次に、リスト5.5に示したように、Webサイト全体にわたってレイアウトと個別のページにスタイルを与えるためのCSSを追加します。リスト5.5には多数の記述ルールがあります。CSSの記述ルールを把握するためには、関心のある箇所をコメントアウトして表示を確認することをお勧めします。CSSでは、/* … */でコメントアウトできるので、調べてみたいコードをこれで囲い、表示がどのように変わるかを確認してみてください。リスト5.5が反映された結果を図5.4に示します。

リスト5.5 すべてのページに適用される共通のスタイルをCSSに追加する。
app/assets/stylesheets/custom.css.scss
@import "bootstrap";

/* universal */

html {
  overflow-y: scroll;
}

body {
  padding-top: 60px;
}

section {
  overflow: auto;
}

textarea {
  resize: vertical;
}

.center {
  text-align: center;
}

.center h1 {
  margin-bottom: 10px;
}
sample_app_universal
図5.4スペースや共通スタイルを追加した結果。(拡大)

リスト5.5のCSSの形式は一貫しています。CSSルールでは一般に、クラス、id、HTMLタグ、またはそれらの組み合わせ、のいずれかを指定します。そしてその後ろにスタイリングコマンドのリストを記述します。たとえば、以下のコードでは、

body {
  padding-top: 60px;
}

ページ上部に60ピクセルの余白を追加します。headerタグにnavbar-fixed-topクラスが与えられているので、これに従ってBootstrapはナビゲーションバーをページ上部に固定し、ナビゲーションバーの下に余白を置いて主要部分から分離します (デフォルトのnavbarの色は、Bootstrap 2.0から2.1に変わったときに変更されたため、現在の淡色の代わりにダークな色調にしたい場合はnavbar-inverseクラスを使用する必要があります)。また、このルールにある以下のCSSは、

.center {
  text-align: center;
}

centerクラスにtext-align: centerプロパティを関連付けています。言い換えると、.center冒頭のドット.は、このルールがクラスに対してスタイルを適用することを示しています。(冒頭がポンド記号#の場合は、リスト5.7に示したように、そのルールがCSS idに対してスタイルを適用することを示します。この場合、centerクラスに属している (divなどの) タグの内側にある要素は、すべてページ中でセンタリングされることを意味しています (このクラスの例はリスト5.2で参照できます)。

Bootstrapには洗練されたタイポグラフィーを利用できるCSSルールがありますが、ここではさらに、リスト5.6に示したようにサイトのテキストの外観を変えるカスタムCSSルールを追加しましょう。(これらのルールはHomeページですべて適用されるとは限りませんが、サンプルアプリケーションの他の場所でも使用されるものもあります)。リスト5.6を反映した結果を図5.5で確認することができます。

リスト5.6 洗練されたタイポグラフィーを利用するためのCSSを追加する。
app/assets/stylesheets/custom.css.scss
@import "bootstrap";
.
.
.

/* typography */

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

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

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

p {
  font-size: 1.1em;
  line-height: 1.7em;
}
sample_app_typography
図5.5タイポグラフィースタイルを追加する。(拡大)

最後に、いくつかのルールをサイトロゴに追加します。このサイトロゴは「sample app」だけが表示されているシンプルなものです。リスト5.7のCSSは、テキストを大文字に変換し、サイズ、色、配置を変更します (サイトロゴがページで一度しか使用されないことを前提としてCSS idを使用していますが、代わりにクラスを使用することもできます)。

リスト5.7 サイトロゴにCSSを追加する。
app/assets/stylesheets/custom.css.scss
@import "bootstrap";
.
.
.

/* header */

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

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

上のコードのcolor: #fffは、ロゴの色を白に変更します。HTMLの色は、16進数 (基数が16) の3つの数値の組み合わせで表現され、赤、緑、青の三原色に (この順序で) コード化することができます。このコード#ffffffは、3色すべてが最大に使用されており、純白になります。なお、#fffは、完全な#ffffffの短縮形です。CSS標準には、共通HTMLカラーの別名も多数定義されています。たとえば、#fffwhiteと書くこともできます。リスト5.7のCSSの結果は図5.6で確認できます。

sample_app_logo
図5.6デザインされたロゴとサンプルアプリ。(拡大)

5.1.3パーシャル (partial)

リスト5.1のレイアウトはその目的を果たしていますが、少々散らかっています。HTML shimは、それだけで3行も占有し、風変わりなIE特有の文法を使用しているので、これをうまく隠すことができたらどんなによいでしょう。また、HTMLヘッダーは論理的な単位を形成するため、一箇所にまとめる必要もあります。Railsでは、パーシャル (partial) と呼ばれる機能を使用してこれを実現することができます。最初に、パーシャルを定義するとレイアウトがどのように変わるかを見てみましょう (リスト5.8)。

リスト5.8 Webサイトのレイアウトにスタイルシート用とヘッダー用のパーシャルをそれぞれ追加する。
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= stylesheet_link_tag    "application", media: "all" %>
    <%= javascript_include_tag "application" %>
    <%= csrf_meta_tags %>
    <%= render 'layouts/shim' %>    
  </head>
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
    </div>
  </body>
</html>

リスト5.8では、以下のようにrenderと呼ばれるRailsヘルパー呼び出しだけを使って、HTML shimのスタイルシート行を置換しています。

<%= render 'layouts/shim' %>

この行では、app/views/layouts/_shim.html.erbというファイルを探してその内容を評価し、結果をビューのその場所に挿入しています6 (<%= ... %>は、テンプレート内でRubyの式を評価するための埋め込みRuby記法であることを思い出してください。評価した結果がテンプレートに挿入されます)。ファイル名_shim.html.erbの前のアンダースコアに注目してください。このアンダースコアは、パーシャルで使用する普遍的な命名規約であり、また、一目見ただけでディレクトリ中のすべてのパーシャルを識別することが可能になります。

もちろん、パーシャルが動作するためには、その中にコンテンツを記述しなければなりません。このshimパーシャルの場合は、リスト5.1のわずか3行のshimコードだけです。追加した結果をリスト5.9に示します。

リスト5.9 HTML shim用のパーシャル。
app/views/layouts/_shim.html.erb
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->

同様に、他のヘッダーの情報もリスト5.10のパーシャルに移動し、renderを呼び出してレイアウトに挿入することができます。

リスト5.10 サイトヘッダー用のパーシャル。
app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="navbar-inner">
    <div class="container">
      <%= link_to "sample app", '#', id: "logo" %>
      <nav>
        <ul class="nav pull-right">
          <li><%= link_to "Home",    '#' %></li>
          <li><%= link_to "Help",    '#' %></li>
          <li><%= link_to "Sign in", '#' %></li>
        </ul>
      </nav>
    </div>
  </div>
</header>

これでパーシャルの作成方法がわかりましたので、今度はヘッダーに対応するフッタを同じ方法で追加しましょう。ここまでくれば、ファイル名は_footer.html.erbで、layoutsディレクトリ (リスト5.11) に置くということが皆様にもわかると思います7

リスト5.11 サイトフッタ用のパーシャル。
app/views/layouts/_footer.html.erb
<footer class="footer">
  <small>
    <a href="http://railstutorial.jp/">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.jp/">News</a></li>
    </ul>
  </nav>
</footer>

ヘッダーの場合と同様に、フッタの中でもlink_toメソッドを使用して、AboutページとContactページへの内部リンクを追加してあります。ひとまず、リンク先のURIは’#’としておきます (headerタグと同様、footerタグもHTML5で新たに追加された要素です)。

フッタパーシャルは、スタイルシートやヘッダーパーシャルのときと同じ方法でレイアウト中に追加できます (リスト5.12)。

リスト5.12 サイトのレイアウトにフッタパーシャルを追加する。
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= stylesheet_link_tag    "application", media: "all" %>
    <%= javascript_include_tag "application" %>
    <%= csrf_meta_tags %>
    <%= render 'layouts/shim' %>    
  </head>
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
      <%= render 'layouts/footer' %>
    </div>
  </body>
</html>

そのまま実際にフッタを表示してみるとどうにも見苦しいので、リスト5.13でスタイルを若干追加しましょう。スタイルを追加した結果を図5.7に示します。

リスト5.13 サイトのフッタ用CSSを追加する。
app/assets/stylesheets/custom.css.scss
.
.
.

/* footer */

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

footer a {
  color: #555;
}  

footer a:hover { 
  color: #222;
}

footer small {
  float: left;
}

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

footer ul li {
  float: left;
  margin-left: 10px;
}
site_with_footer_bootstrap
図5.7Homeページ (/static_pages/home) にフッタを追加する。(拡大)

5.2Sass と アセットパイプライン

Rails 3.0と、その後のバージョンの最も大きな違いの1つは、CSS、JavaScript、画像などの静的コンテンツの生産と管理を大幅に強化する「アセットパイプライン」です。この節では、アセットパイプラインの高度な概要と、アセットパイプラインの一部としてデフォルトで含まれている、Sassと呼ばれる素晴らしいCSS生成ツールの使い方について説明します。

5.2.1 アセットパイプライン

アセットパイプラインは、Railsの流儀を守りながら多大な変化をもたらしますが、一般的なRails開発者の視点からは、アセットディレクトリ、マニフェストファイル、プリプロセッサエンジンという、3つの主要な機能が理解の対象となります8。では、それぞれを順に見ていきましょう。

アセットディレクトリ

Rails 3.0以前のバージョンでは、静的ファイルはpublic/以下の次のディレクトリに置かれていました。

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

これらのディレクトリ中のファイルは、http://example.com/stylesheetsのようなリクエストによって自動的に配信されます。これは3.0以降も同様です。

Rails 3.1からは、静的ファイルを目的別に分類する、標準的な3つのディレクトリが使用されるようになりました。

  • app/assets: 現在のアプリケーション固有のアセット
  • lib/assets: あなたの開発チームによって作成されたライブラリ用のアセット
  • vendor/assets: サードパーティのアセット

これらのディレクトリには、それぞれのアセットクラス用のサブディレクトリがあります。たとえば、app/assetsには次のようなサブディレクトリがあります。

$ ls app/assets/
images      javascripts stylesheets

上記の説明から、5.1.2で取り上げたcustom.css.scssが配置された場所と、その理由について理解することができると思います。custom.css.scssは、サンプルアプリケーション固有のアセットなので、app/assets/stylesheetsに配置されているのです。

マニフェストファイル

アセットを上記の論理的な場所へ配置すれば、マニフェストファイルを使用して、それらをどのように1つのファイルにまとめるのかをRailsに指示することができます。なお、実際にまとめるのはSprockets gemが行います。(マニフェストファイルはCSSとJavaScriptには適用されますが、画像ファイルには適用されません) 。1つの例として、アプリケーションスタイルシート用のマニフェストファイルを見てみましょう (リスト5.14)。

リスト5.14. アプリケーション固有のCSS用マニフェストファイル。
app/assets/stylesheets/application.css
/*
 * This is a manifest file that'll automatically include all the stylesheets
 * available in this directory and any sub-directories. You're free to add
 * application-wide styles to this file and they'll appear at the top of the
 * compiled file, but it's generally better to create a new file per style 
 * scope.
 *= require_self
 *= require_tree . 
*/

上の行で重要な部分は実はCSSコメントの中にあります。以下の行は、適切なファイルをインクルードするためにSprocketsによって使用されます。

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

以下の行は、

*= require_tree .

app/assets/stylesheetsディレクトリ (サブディレクトリを含む) のすべてのCSSファイルをアプリケーションCSSにインクルードするようにします (このドットも必要です)。以下の行は、

*= require_self

application.css自身をもインクルードすることを指示します。

Railsには実用的なデフォルトのマニフェストファイルが付属しているので、Railsチュートリアルでは変更を加える必要がありませんが、もし必要な場合は、Railsガイドの「アセットパイプライン」で詳細な情報を参照できます。

プリプロセッサエンジン

必要なアセットをディレクトリに配置してまとめた後、Railsはさまざまなプリプロセッサエンジンを介してそれらを実行し、ブラウザに配信できるようにそれらをマニフェストファイルを用いて結合し、サイトテンプレート用に準備します。Railsは、どのプリプロセッサを使用するかを、ファイル名の拡張子を使用して判断します。最も一般的な拡張子は、Sass用の.scss、CoffeeScript用の.coffee、埋め込みRuby (ERb) 用の.erbです。3.3.3では最初にERbを、5.2.2ではSassをそれぞれ扱いました。 なお本書では扱いませんが、CoffeeScriptはエレガントで簡潔な言語で、JavaScriptにコンパイルして実行します (興味のある方は、RailsCastの「CoffeeScriptの基礎 (英語)」から始めると良いでしょう)。

プリプロセッサエンジンはつなげて実行する (chain) ことができます。

foobar.js.coffee

上の拡張子の場合、CoffeeScriptプロセッサ経由で実行されます。

foobar.js.erb.coffee

上の拡張子の場合は、CoffeeScriptとERbの両方で実行されます (コードは右から左へと実行されますので、この例ではCoffeeScriptが最初に実行されます)。

本番環境での効率性

アセットパイプラインの最大のメリットの1つは、本番のアプリケーションで効率的になるように最適化されたアセットも自動的に生成されることです。従来は、CSSとJavaScriptを整理するために、機能を個別のファイルに分割し、(インデントを多用して) 読みやすいフォーマットに整えていました。これは、プログラマにとっては便利な方法ですが、本番環境にとっては非効率です。それというのも、拡大のCSSやJavaScriptファイルを多数インクルードすると、ページの読み込み時間が著しく遅くなるからです (読み込み時間は、ユーザ体験の質に影響を与える重要な指標の1つです)。一方、アセットパイプラインでは、本番環境に最適化するために、すべてのスタイルシートを1つのCSSファイル (application.css) にまとめ、すべてのJavaScriptファイルを1つのJSファイル (javascripts.js) にまとめてくれます。さらに、それらのファイルすべてに対して (lib/assetsvendor/assetsのファイルも含め) 不要な空白を取り除く処理を行い、ファイルサイズを最小化してくれます。これにより、アセットパイプラインは、2つの異なった状況に対してそれぞれ最高の環境を提供してくれます。つまり、プログラマーに対しては見やすく分割されたフォーマットのファイルを提供し、本番環境に対しては最適化された1つのファイルを提供してくれます。

5.2.2素晴らしい構文を備えたスタイルシート

Sass は、スタイルシートを記述するための言語であり、CSSに比べて多くの点が強化されています。この節では、Sassが提供する2つの重要な機能であるネスト変数について説明します (3つ目の重要な機能であるミックスインについては、7.1.1で紹介します)。

5.1.2でも簡単に説明しましたが、SassはSCSSというフォーマットに対応しています (.scssという拡張子はSCSSであることを表します)。SCSSは、CSSの機能をすべて包含する、厳密なスーパーセットです。つまり、SCSSはCSSに新しい機能を追加しただけで、まったく新しい構文を定義したものではありません9。このため、有効なCSSファイルは、すべてSCSSファイルとしても扱うことができ、既存の記法ルールを使用しているプロジェクトにとっても互換性のある便利なフォーマットになっています。本書の例では、Bootstrapの恩恵を得るために、最初からSCSSを使用してきました。Railsのアセットパイプラインは、.scssという拡張子を持つファイルをSassを使って自動的に処理してくれます。このため、custom.css.scssファイルはSassプリプロセッサによって前処理され、その後ブラウザでの配信に備えてパッケージ化されます。

ネスト

スタイルシート内に共通のパターンがある場合は、要素をネストさせることができます。たとえば、リスト 5.5では、以下のように.center.center h1の両方に対してルールがあります。

.center {
  text-align: center;
}

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

上のルールは、Sassを使用して以下のように書き換えることができます。

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

上の例では、ネストの内側にあるh1というルールは、.centerのルールを継承しています。

今度は、もう少し異なるルールに対してネスト機能を使う例を見てみましょう。リスト 5.7には以下のコードがあります。

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

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

上のコードには#logoというidが2回使用されています。1回目はロゴ自身を定義するために、2回目はhover属性を定義するために使用されています (なおhover属性は、該当する要素の上にマウスポインタをかざしたときの表示を定義します)。2つ目のルールをネストするためには、親属性である#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は、SCSSをCSSに変換する際に、&:hover#logo:hoverに置換します。

これらのネスト機能は、フッターのCSSでも使用できます。リスト5.13のコードは、SCSSを使用して以下のように書き換えることができます。

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

リスト 5.13を手作業で変換してみることは、良い演習になります。変換後にもCSSが適切に動作していることを確認してみましょう。

変数

Sassでは、冗長なコードを削除し、より自由な表現を可能にするために、変数が定義できるようになっています。たとえば、リスト 5.6リスト 5.13を見てみると、同じ色を繰り返し参照している箇所があります。

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

上のコードの#999は薄い灰色を指しています。Sassでは、このような値を変数として定義し、以下のように変数名を与えることができます。

$lightGray: #999;

この機能を使用して、SCSSを以下のように書き直すことができます。

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

$lightGrayのような変数名は、#999 のような値よりもわかりやすいので、たとえその変数が繰り返し使われないとしても、変数名を与えることは多くの場合有用です。実際、Bootstrapフレームワークでは、多くの色に対して変数名を定義しています。定義されている変数は「Bootstrap page of LESS variables」で参照することができます。このWebサイトでは、SassではなくLESSを使って変数が定義されていますが、bootstrap-sassというgemを使用すれば、Sassでも同様の変数が使えるようになります。LESSとSass の違いを想像するのはそれほど難しくありません。たとえば、LESSではアットマーク@を使用しているのに対して、Sassはドルマーク$を使っていることはすぐにわかります。話を戻して、Bootstrapの変数の一覧表を見ると、薄い灰色に対して以下の変数名が与えられることに気が付きます。

 @grayLight: #999;

これは、bootstrap-sassというgemを使えば、SCSSでも同様に$grayLightという変数が使えることを意味しています。先ほど定義した$lightGrayというカスタム変数に代わりに、用意された変数を使ってみましょう。

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

今回取り上げたSassのネスト機能や変数機能を使ってSCSSファイルを全面的に書き直すと、リスト 5.15のようになります。このリストでは、Sassの変数 (詳しくはBootstrap LESSの変数一覧を参考にしてください) や、組み込みの色変数 (たとえば#fffにはwhite という変数) を使っています。footerタグのルールが、劇的に向上していることを確認してみてください。

リスト5.15 ネストや変数を使って初期のSCSSファイルを書き直した結果。
app/assets/stylesheets/custom.css.scss
@import "bootstrap";

/* mixins, variables, etc. */

$grayMediumLight: #eaeaea;

/* universal */

html {
  overflow-y: scroll;
}

body {
  padding-top: 60px;
}

section {
  overflow: auto;
}

textarea {
  resize: vertical;
}

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

/* typography */

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

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

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

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


/* header */

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

/* footer */

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

Sassを使ってスタイルシートをより簡単にする方法は他にもありますが、今回はその中でも最も重要な機能を使ってリスト 5.15を書き直しました。Sassを使うことによって、素晴らしいスタートを切ることができました。Sassの詳細については、Sass の公式サイト (英語) を参照してください。

5.3レイアウトのリンク

サイトのレイアウトが美しく仕上がりましたので、今度は’#’で代用していたリンクを書き換えてみましょう。もちろん、以下のようにリンクを直接記述することもできます。

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

しかし、上の記法はRails流ではありません。まず、aboutページへのURIは/static_pages/aboutではなく/aboutの方がよいでしょう。さらに、Railsでは以下のようなコードでは名前付きルートを使用するのが慣例となっています。

<%= link_to "About", about_path %>

上のようにすることでコードの意味がわかりやすくなり、about_pathの定義を変えればabout_pathが使用されているすべてのURIを変更できるため、柔軟性が高まります。

今後使用する計画のあるすべてのリンクのリストを、URIとルート (route) のマッピングと共に表5.1に示します。この章の終わりまでに、最後のリンクを除き全て実装します。(最後のリンクは第8章で作成します。)

ページURI名前付きルート
Home/root_path
About/aboutabout_path
Help/helphelp_path
Contact/contactcontact_path
Sign up/signupsignup_path
Sign in/signinsignin_path
表 5.1サイトリンクのルート (routing) とURIのマッピング。

先に進む前にContactページを追加しましょう (これは第3章の演習の積み残しです)。Contactページのテストをリスト5.16に示します。これは単にリスト3.18で使用されているテストのパターンに従ったものです。リスト5.16のアプリケーションコードでは、Ruby 1.9スタイルのハッシュに変更していることに注意してください。

リスト5.16 Contactページのテスト。
spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "Static pages" do
  .
  .
  .
  describe "Contact page" do

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

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

以下のテストは失敗するはずです。

$ bundle exec rspec spec/requests/static_pages_spec.rb

アプリケーションコードは、3.2.2のAboutページへの追加と良く似ています。最初にルート (リスト5.17) を更新します。次にcontactアクションをStaticPagesコントローラ (リスト5.18) に追加します。最後にContactビュー (リスト5.19) を作成します。

リスト5.17 Contactページ用のルートを追加する。
config/routes.rb
SampleApp::Application.routes.draw do
  get "static_pages/home"
  get "static_pages/help"
  get "static_pages/about"
  get "static_pages/contact"
  .
  .
  .
end
リスト5.18 Contactページ用のアクションを追加する。
app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
  .
  .
  .
  def contact
  end
end
リスト5.19 Contactページのビュー。
app/views/static_pages/contact.html.erb
<% provide(:title, 'Contact') %>
<h1>Contact</h1>
<p>
  Contact Ruby on Rails Tutorial about the sample app at the
  <a href="http://railstutorial.jp/contact">contact page</a>.
</p>

今度はテストが成功することを確認してください。

$ bundle exec rspec spec/requests/static_pages_spec.rb

5.3.1 ルートのテスト

静的ページの結合テストを書いておいたので、ルートに対するテストはシンプルです。コードに直接書かれているアドレスを表5.1にある名前付きルートにそれぞれ置き換えるだけです。つまり、以下のコードの場合、

visit '/static_pages/about'

上を以下のように変更します。

visit about_path

他のページについても同様に変更します。変更の結果をリスト5.20に示します。

リスト5.20 名前付きルートのテスト。
spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "Static pages" do

  describe "Home page" do

    it "should have the h1 'Sample App'" do
      visit root_path
      page.should have_selector('h1', text: 'Sample App')
    end

    it "should have the base title" do
      visit root_path
      page.should have_selector('title',
                        text: "Ruby on Rails Tutorial Sample App")
    end

    it "should not have a custom page title" do
      visit root_path
      page.should_not have_selector('title', text: '| Home')
    end
  end

  describe "Help page" do

    it "should have the h1 'Help'" do
      visit help_path
      page.should have_selector('h1', text: 'Help')
    end

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

  describe "About page" do

    it "should have the h1 'About'" do
      visit about_path
      page.should have_selector('h1', text: 'About Us')
    end

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

  describe "Contact page" do

    it "should have the h1 'Contact'" do
      visit contact_path
      page.should have_selector('h1', text: 'Contact')
    end

    it "should have the title 'Contact'" do
      visit contact_path
      page.should have_selector('title',
                    text: "Ruby on Rails Tutorial Sample App | Contact")
    end
  end
end

いつもと同様に、今度のテストは赤色 (失敗) になるはずです。

$ bundle exec rspec spec/requests/static_pages_spec.rb

ところで、もしリスト5.20が繰り返しが多く冗長だと感じたなら、それはあなただけはありません。この乱雑な状態を5.3.4で美しい宝石へとリファクタリングする予定です。

5.3.2 Railsのルート

URIに対するテストができあがったので、それらを実際に利用できるようにしましょう。3.1.2で説明したとおり、RailsがURIマッピングに使用するファイルはconfig/routes.rbです。デフォルトのルートファイルの内容を見てみると、かなり乱雑になっています。しかし、それらはすべてコメントアウトされたルートマッピングの例であり、必要な乱雑さです。時間のあるときにこのルートマッピングを読んでみることをお勧めします。また、より詳細なルーティングの扱いについては、Railsガイドの「Rails ルーティング」を参照することをお勧めします。

名前付きルートを定義するため、以下のようなルールを置き換えます。

get 'static_pages/help'

上のルーティングを下記のように置き換えます。

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

このルーティングは、/helpで有効なページと、そのページへのパスを返すhelp_pathという名前の名前付きルートの両方を準備します (実際には、matchの箇所にgetを使用しても同じ名前付きルートになりますが、matchを利用する方がよりRailsの慣例に従っています)。

このパターンを他の静的ページに適用すると、リスト5.21になります。唯一の例外は、リスト5.23で取り扱うHomeページです。

リスト5.21 静的ページのルート。
config/routes.rb
SampleApp::Application.routes.draw do
  match '/help',    to: 'static_pages#help'
  match '/about',   to: 'static_pages#about'
  match '/contact', to: 'static_pages#contact'
  .
  .
  .
end

リスト5.21コードを注意深く読むことで、このコードの動作を理解できるでしょう。たとえば以下のコードの場合について説明します。

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

上のコードは’/about’に一致し、それによってStaticPagesコントローラのaboutアクションにルーティングします。変更前の以下のコードは、より明示的でした。

get 'static_pages/about'

このコードも同じページへたどり着きますが、/aboutの方が簡潔です。さらに前述したように、match ’/about’というコードは自動的にコントローラとビューで使用する名前付きルートを生成します。

about_path => '/about'
about_url  => 'http://localhost:3000/about'

about_urlフルURI http://localhost:3000/about であることに注目してください (なお、localhost:3000は本番環境でexample.comのようなドメイン名へ置き換えられます)。5.3で説明したように、/about のみを取得する場合は about_pathを使います。なお、Railsチュートリアルでは、path書式を使用する一般的な規約に従い、リダイレクトの場合のみurl書式を使用します。これは、HTTP標準では技術的にリダイレクト後にフルURIが要求されるためです。ただし、ほとんどのブラウザではどちらの方法でも動作します。

ルーティングが定義されたので、Help、About、Contactページのテストはパスするはずです。

$ bundle exec rspec spec/requests/static_pages_spec.rb

これでHomeページのテストが、失敗する最後のテストになります。

Homeページへのルートマッピングを作成する際に、以下のようなコードを使用することも一応可能です

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

しかし、実際にはルート (root) については上のように書く必要はありません。Railsでは、routesファイル (リスト5.22) の下の方のコメント内に、ルート(root) URI / (“スラッシュ”) 用の特別な記法が書かれています。

リスト5.22 ルート (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.22のコメントを参考に、リスト5.23のようにルート (root) URI / をHomeページへルーティングします。

リスト5.23 ルート (root) へのルーティングのためのマッピングを追加する。
config/routes.rb
SampleApp::Application.routes.draw do
  root to: 'static_pages#home'

  match '/help',    to: 'static_pages#help'
  match '/about',   to: 'static_pages#about'
  match '/contact', to: 'static_pages#contact'  
  .
  .
  .
end

上のコードは、ルート (root) URLである / を /static_pages/home に割り当て、URIヘルパーに以下の設定を与えます。

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

リスト5.22の他のコメントの内容にも注意してください。/にアクセスする際にRailsがデフォルトのページ (図1.3) を表示しないように、public/index.htmlを削除しておきます。もちろん、単にこのファイルを削除しても構いませんが、バージョン管理にGitを使用している場合は、git rmを使用することでファイル削除と同時にGitに削除を通知することもできます。

$ git rm public/index.html

これで静的ページへのルートがすべて動作し、テストもすべてパスするはずです。

$ bundle exec rspec spec/requests/static_pages_spec.rb

後は、レイアウトのリンクをこれらの名前付きルートで埋めればよいのです。

5.3.3名前付きルート

レイアウトのリンクを有効にするために、5.3.2で作成した名前付きルートを記入しましょう。そのためには、link_toメソッドの2番目の引数に適切な名前付きルートを指定する必要があります。たとえば以下のコードの場合、

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

上を以下のように変更します。

<%= link_to "About", about_path %>

最初に、HomeページとHelpページへのリンクを持つヘッダーパーシャル_header.html.erb (リスト5.24) から取りかかります。ヘッダーパーシャルでは、Web共通の慣習に従って、ロゴにもHomeページへのリンクを追加します。

リスト5.24 ヘッダーパーシャルにリンクを追加する。
app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="navbar-inner">
    <div class="container">
      <%= link_to "sample app", root_path, id: "logo" %>
      <nav>
        <ul class="nav pull-right">
          <li><%= link_to "Home",    root_path %></li>
          <li><%= link_to "Help",    help_path %></li>
          <li><%= link_to "Sign in", '#' %></li>
        </ul>
      </nav>
    </div>
  </div>
</header>

[Sign in] リンクの名前付きルートは第8章で作成するため、今の段階では’#’のままにしておきます。

フッターパーシャル_footer.html.erbにもリンクがあります。これらはAboutページとContactページへのリンクです (リスト5.25)。

リスト5.25 フッターパーシャルにリンクを追加する。
app/views/layouts/_footer.html.erb
<footer class="footer">
  <small>
    <a href="http://railstutorial.jp/">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.jp/">News</a></li>
    </ul>
  </nav>
</footer>

これで、レイアウトに第3章で作成したすべての静的ページへのリンクができました。たとえば/aboutの場合はAboutページ (図5.8) に移動します。

ところで、実際にレイアウト上にリンクが存在するかどうかをまだテストしていませんが、テスト方法のヒントとして、「名前付きルートが定義されていなければテストは失敗する」ということに注目してください。このことは、リスト5.21のルーティング定義をコメントアウトし、テストスイートを実行することで確認できます。リンクが実際に正しい場所を指していることを確認するテストの手法については、5.6で説明します。

about_page_styled
図5.8/aboutで表示されるAboutページ。(拡大)

5.3.4RSpecを洗練させる

5.3.1で、静的ページのテストが冗長で繰り返しが多くなってきたことを指摘しました (リスト5.20)。この節では、RSpecの最新の機能を使い、テストをより簡潔で洗練されたものにします。

テストの改善方法について、いくつかの例を見てみましょう。

describe "Home page" do

  it "should have the h1 'Sample App'" do
    visit root_path
    page.should have_selector('h1', text: 'Sample App')
  end

  it "should have the base title" do
    visit root_path
    page.should have_selector('title',
                      text: "Ruby on Rails Tutorial Sample App")
  end

  it "should not have a custom page title" do
    visit root_path
    page.should_not have_selector('title', text: '| Home')
  end
end

まず、上の3つの例はいずれもルートへのアクセスを含んでいることに気付きます。beforeブロックを使用することでこの冗長箇所を除くことができます。

describe "Home page" do
  before { visit root_path } 

  it "should have the h1 'Sample App'" do
    page.should have_selector('h1', text: 'Sample App')
  end

  it "should have the base title" do
    page.should have_selector('title',
                      text: "Ruby on Rails Tutorial Sample App")
  end

  it "should not have a custom page title" do
    page.should_not have_selector('title', text: '| Home')
  end
end

上のコードでは以下を使用しました。

before { visit root_path }

これにより、それぞれの例の前にルートパスへのアクセスを実行します (beforeメソッドは、別名でもあるbefore(:each)で呼ぶこともできます)。

冗長性の原因は他にもあります。

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

上と以下はどちらも本質的には同じ内容です。

page.should have_selector('h1', text: 'Sample App')

さらに、どちらの例もpage変数を参照しています。以下のように、pageはテストの主題 (subject) であることをRSpecに伝えることにより、冗長の原因を排除できます。

subject { page }

次に、以下のようにitメソッドの変種を使用することにより、コードと記述を1行に収めます。

it { should have_selector('h1', text: 'Sample App') }

subject { page }と記述したことにより、shouldの呼び出しは自動的にCapybara (3.2.1)により提供されるpage変数を使用します。

これらの変更を加えることでHomeページのテストはより簡潔になります。

  subject { page }

  describe "Home page" do
    before { visit root_path } 

    it { should have_selector('h1', text: 'Sample App') }
    it { should have_selector 'title',
                        text: "Ruby on Rails Tutorial Sample App" }
    it { should_not have_selector 'title', text: '| Home' }
  end

上のコードは以前より良くなりましたが、まだタイトルのテストが少し長すぎます。確かに、リスト5.20にあるほとんどのタイトルのテストで以下の長いテキストが使用されています。

"Ruby on Rails Tutorial Sample App | About"

3.5の演習では、base_title変数を定義し、文字列の補完を使用することでこの冗長性を排除することを提案しました (リスト3.30)。リスト4.2full_titleヘルパーと同様のfull_titleを定義することで、さらに改良することができます。これを行うには、RSpecユーティリティで使用するspec/supportディレクトリーとutilities.rbファイルを作成します (リスト5.26)。

リスト5.26 full_titleメソッドを持つRSpecユーティリティー用ファイル。
spec/support/utilities.rb
def full_title(page_title)
  base_title = "Ruby on Rails Tutorial Sample App"
  if page_title.empty?
    base_title
  else
    "#{base_title} | #{page_title}"
  end
end

もちろんこれは、基本的にリスト4.2のヘルパーの複製です。しかし、2つの独立したメソッドがあることで基本タイトルのタイプミスを検出することができます。これはやや疑問の残る設計ですが、それでも、演習で登場する (5.6) オリジナルのfull_titleヘルパーを直接テストしたときよりも良い (少しだけ高度な) アプローチです。

spec/supportディレクトリはRSpecによって自動的に読み込まれるため、Homeテストは以下のように書くことができます。

  subject { page }

  describe "Home page" do
    before { visit root_path } 

    it { should have_selector('h1',    text: 'Sample App') }
    it { should have_selector('title', text: full_title('')) }
    it { should_not have_selector('title', text: '| Home') }
  end

Homeページで使用したのと同じ方法で、Help、About、Contactページのテストを単純化することができます。変更の結果をリスト5.27に示します。

リスト5.27 静的ページの端正になったテスト。
spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "Static pages" do

  subject { page }

  describe "Home page" do
    before { visit root_path }

    it { should have_selector('h1',    text: 'Sample App') }
    it { should have_selector('title', text: full_title('')) }
    it { should_not have_selector 'title', text: '| Home' }
  end

  describe "Help page" do
    before { visit help_path }

    it { should have_selector('h1',    text: 'Help') }
    it { should have_selector('title', text: full_title('Help')) }
  end

  describe "About page" do
    before { visit about_path }

    it { should have_selector('h1',    text: 'About') }
    it { should have_selector('title', text: full_title('About Us')) }
  end

  describe "Contact page" do
    before { visit contact_path }

    it { should have_selector('h1',    text: 'Contact') }
    it { should have_selector('title', text: full_title('Contact')) }
  end
end

テストは今度もパスするはずです。

$ bundle exec rspec spec/requests/static_pages_spec.rb

リスト5.27のRspecスタイルは、リスト5.20のスタイルよりもずっと簡潔でわかりやすくなっています。実は、さらに簡潔でわかりやすくすることが可能です (5.6)。サンプルアプリケーションの今後の開発では、そのさらに簡潔なスタイルを可能な限り使用することにします。

5.4ユーザーのサインアップ:最初のステップ

この節では、レイアウトとルーティングの取り組みにおける頂点として、サインアップページへのルートを作成します。そのために2番目のコントローラを作成することになります。これは、Webサイトでユーザー登録を行えるようにするための最初の重要な一歩となります。次の一歩であるユーザのモデリングは第6章で行い、第7章でユーザー登録が完成します。

5.4.1ユーザーコントローラ

3.1.2で最初のコントローラであるStaticPagesコントローラを作成してからしばらく経ちました。今度は2番目のコントローラであるUsersコントローラを作成しましょう。1番目のときと同様、generateを実行して、現時点での要求である新規ユーザー用のサインアップページ (スタブ) を持つ、最も簡単なコントローラを作成します。Railsで好まれているRESTアーキテクチャの規約に従い、新規ユーザー用のアクションをnewにすることに決め、generate controllerの引数として渡して自動的にアクションを作成します (リスト5.28)。

リスト5.28 Usersコントローラの生成 (newアクションを追加)。
$ rails generate controller Users new --no-test-framework
      create  app/controllers/users_controller.rb
       route  get "users/new"
      invoke  erb
      create    app/views/users
      create    app/views/users/new.html.erb
      invoke  helper
      create    app/helpers/users_helper.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/users.js.coffee
      invoke    scss
      create      app/assets/stylesheets/users.css.scss

上のコマンドにより、newアクションを持つUsersコントローラ(リスト5.29)と、スタブのユーザービューを作成します(リスト5.30)。

リスト5.29 newアクションを持つ最初のUsersコントローラ。
app/controllers/users_controller.rb
class UsersController < ApplicationController
  def new
  end

end
リスト5.30 Users用の最初のnewアクション。
app/views/users/new.html.erb
<h1>Users#new</h1>
<p>Find me in app/views/users/new.html.erb</p>

5.4.2サインアップURI

5.4.1のコードにより、新規ユーザー用の動作するページが/users/new にできました。ここで表5.1を思い出していただきたいのですが、URIは/users/newではなく表のとおりに/signupにしたいと思います。5.3のときと同様、最初にいくつかの結合テストを作成しましょう。以下を実行してテスト用ファイルを生成します。

$ rails generate integration_test user_pages

次に、リスト5.27の静的ページspecのひな形に従い、リスト5.31に示すように、userページのh1titleタグの内容をテストするコードを記入します。

5.31 サインアップページへのテストを含む最初のusers用spec。
spec/requests/user_pages_spec.rb
require 'spec_helper'

describe "User pages" do

  subject { page }

  describe "signup page" do
    before { visit signup_path }

    it { should have_selector('h1',    text: 'Sign up') }
    it { should have_selector('title', text: full_title('Sign up')) }
  end
end

いつもと同様、これらのテストを以下のようにrspecコマンドで実行できます。

$ bundle exec rspec spec/requests/user_pages_spec.rb

上のように特定のファイルを1つ渡す代わりに、以下のようにrequestsディレクトリ全体を渡すと、すべてのリクエストspecのテストを実行できることを覚えておくと良いでしょう。

$ bundle exec rspec spec/requests/

上のパターンから、それ以外のディレクトリを含むすべてのspecを実行する以下の方法も容易に想像がつくと思います。

$ bundle exec rspec spec/

完全を期して、今後はチュートリアルの最後まで基本的に上の方法を使用してテストをフル実行します。なお、Rakeタスクでspecのテストスイートを実行できることも覚えておくとよいでしょう (他の開発者が使っているのを見たことがあるかもしれません)。

$ bundle exec rake spec

(実際にはrakeとタイプするだけで済みます。rakeのデフォルトの動作はテストスイートの実行です。)

Usersコントローラは、作成時に既にnewアクションを持っているため、後はテストをパスさせるために正しいルートとビューの中身を作成すればよいのです。リスト5.21の例に従い、ユーザ登録URL用にmatch ’/signup’のルールを追加します。(リスト5.32)。

リスト5.32 サインアップページへのルート。
config/routes.rb
SampleApp::Application.routes.draw do
  get "users/new"

  root to: 'static_pages#home'

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

  match '/help',    to: 'static_pages#help'
  match '/about',   to: 'static_pages#about'
  match '/contact', to: 'static_pages#contact'
  .
  .
  .
end

リスト5.28では、Usersコントローラを生成したときに自動的に生成されたget "users/new"のルールはそのままにしてあることに注目してください。現時点では、このルールは’users/new’のルーティングが動作するために必要ですが、適切なREST規約 (表2.2) に従っていません。これは7.1.2で取り除きます。

テストをパスさせるために今必要なのは、“Sign up” のタイトルとヘッダーがあるビューです (リスト5.33)。

リスト5.33 最初の (スタブ) サインアップページ。
app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<p>Find me in app/views/users/new.html.erb</p>

この時点で、リスト5.31のテストはパスするはずです。残っている作業は、Homeページのボタンに適切なリンクを追加することです。他のルートと同様、match ’/signup’と記述したことでsignup_pathという名前付きルートができ、それをリスト5.34で使用します。

リスト5.34 ボタンをサインアップページにリンクする。
app/views/static_pages/home.html.erb
<div class="center hero-unit">
  <h1>Welcome to the Sample App</h1>

  <h2>
    This is the home page for the
    <a href="http://railstutorial.jp/">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)。結果を図5.9の新規ユーザーのページ (URI /signup) に示します。

new_signup_page_bootstrap
図5.9/signupで表示される新しいサインアップページ 。(拡大)

この時点で、テストはパスするはずです。

$ bundle exec rspec spec/

5.5最後に

この章では、アプリケーションのレイアウトを形にし、ルーティングを洗練させました。本書では、以後サンプルアプリケーションを肉付けすることに専念します。最初に、サインアップ、サインイン、サインアウトできるユーザーを追加します。次に、マイクロポストを追加します。最後に、他のユーザーをフォローできるようにします。

Gitを使っている方は、この時点でmasterブランチに変更をマージしてください。

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

続いてGitHubにプッシュしても構いません。

$ git push

最後にHerokuへデプロイすることもできます。

$ git push heroku

これで、本番環境のサーバーでサンプルアプリケーションが動作しているはずです。

$ heroku open

不具合が発生した場合は、以下のコマンドを試してみてください。

$ heroku logs

上のコマンドを使用してHerokuのログファイルを参照し、エラーをデバッグしてください。

5.6演習

  1. リスト5.27の静的ページのテストコードは簡潔ですが、まだ繰り返しが残っています。RSpecには、冗長性を排除するためのShared Examplesと呼ばれる機能があります。リスト5.35の例に従い、Help、AboutとContactページで不足しているテストを補ってください。リスト3.30で簡単に紹介したletコマンドは、要求 (変数が使用されるときなど) に応じて与えられた値を持つ「ローカル変数」を作成することに注意してください。対照的にインスタンス変数は、要素代入 (assignment) されたときに作成されます。
  2. 既にお気付きの方もいると思いますが、これまで行なってきた、レイアウト上のリンクのルーティングテストは、そのリンクが実際に正しいページへのリンクになっているかどうかをチェックしていません。このようなテストを実装する方法のひとつは、RSpecの結合テスト内でvisitclick_linkを使用することです。レイアウトのリンクが正しく定義されていることを確認するコードを追加して、リスト5.36を補ってください。
  3. リスト5.37に示すように、元のヘルパーメソッドに対するテストを書き、それによってリスト5.26full_titleテストヘルパーへの依存を解消してください (spec/helpersディレクトリとapplication_helper_spec.rbファイルの両方を作成する必要があるでしょう)。次に、リスト5.38のコードを使用して、それをテストへincludeしてください。テストスイートを実行して、新しいコードに問題がないことを確認してください。注意: リスト5.37では、6.2.4で学ぶ正規表現を使用しています (この演習を提案し、コードを提供してくれたAlex Chaffeeに感謝します)。
リスト5.35 RSpecのShared Exampleを使用してテストの冗長性を排除する。
spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "Static pages" do

  subject { page }

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

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

    it_should_behave_like "all static pages"
    it { should_not have_selector 'title', text: '| Home' }
  end

  describe "Help page" do
    .
    .
    .
  end

  describe "About page" do
    .
    .
    .
  end

  describe "Contact page" do
    .
    .
    .
  end
end
リスト5.36 レイアウトのリンクをテストする。
spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "Static pages" do
  .
  .
  .
  it "should have the right links on the layout" do
    visit root_path
    click_link "About"
    page.should have_selector 'title', text: full_title('About Us')
    click_link "Help"
    page.should # fill in
    click_link "Contact"
    page.should # fill in
    click_link "Home"
    click_link "Sign up now!"
    page.should # fill in
    click_link "sample app"
    page.should # fill in
  end
end
リスト5.37 full_titleヘルパーのテスト。
spec/helpers/application_helper_spec.rb
require 'spec_helper'

describe ApplicationHelper do

  describe "full_title" do
    it "should include the page title" do
      full_title("foo").should =~ /foo/
    end

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

    it "should not include a bar for the home page" do
      full_title("").should_not =~ /\|/
    end
  end
end
リスト5.38 full_titleテストヘルパーを単純に includeで置換する。
spec/support/utilities.rb
include ApplicationHelper
  1. サンプルアプリケーションでBootstrapを使用するための変換作業で、目覚ましい働きで助けてくれた読者のColm Tuiteに感謝します。
  2. Ruby on Railsチュートリアルのモックアップは、Mockingbirdというオンラインモックアップアプリケーションで作成しています。
  3. CSSクラスは、Rubyのクラスとはまったく関係がありません。
  4. 既にお気付きだと思いますが、imgタグは<img>...</img>ではなく<img ... />と書きます。この書式に従うタグは 単独タグとして知られています。 
  5. アセットパイプラインでLESSを使うこともできます。詳細はless-rails-bootstrap gemを参照してください。
  6. 多くのRails開発者は、異なるビューの間で共通に使用するパーシャルを保存するディレクトリとして、sharedディレクトリを使用します。著者は、複数のビューで共有するユーティリティパーシャルについてはsharedフォルダに保存し、文字どおり全ページ (サイトレイアウトの一部として) 共通のパーシャルについてはlayoutsディレクトリへ保存することを好んでいます (sharedディレクトリは第7章で作成します)。著者はこのように分割保存するのが論理的であると考えますが、sharedフォルダにすべて保存しても問題なく動作します。
  7. footerタグと.footerクラスを両方使用していることについて疑問に思う方がいるかもしれません。その理由は、footerタグとする方が読み手にとって意味が明確であるのと、footerクラスはBootstrapで使用するためです。footerdivに置き換えても動作は変わりません。
  8. このチュートリアル構成は、Michael Erasmusによる素晴らしいブログ記事「5分でわかるRails 3のアセットパイプライン (英語)」をもとにしています。詳細については、Railsガイドの「アセットパイプライン」の項を参照してください。
  9. Sassでもサポートされている古い.sassというフォーマットでは、冗長性の少ない (括弧の少ない) 新しい言語を定義しますが、既存のプロジェクトには若干不便であり、既にCSSに慣れ親しんだ人にとっては学習が面倒でもあります。
前の章
第5章 レイアウトを作成する
次の章