Ruby on Rails チュートリアル
-
第2版 目次
- 第1章ゼロからデプロイまで
- 第2章デモアプリケーション
- 第3章ほぼ静的なページの作成
- 第4章Rails風味のRuby
- 第5章レイアウトを作成する
- 第6章ユーザーのモデルを作成する
- 第7章ユーザー登録
- 第8章サインイン、サインアウト
- 第9章ユーザーの更新・表示・削除
- 第10章ユーザーのマイクロポスト
- 第11章ユーザーをフォローする
- 第12章Rails 4.0へのアップグレード
|
||
第2版 目次
|
Ruby on Rails チュートリアル
プロダクト開発の0→1を学ぼう
下記フォームからメールアドレスを入力していただくと、招待リンクが記載されたメールが届きます。リンクをクリックし、アカウントを有効化した時点から『30分間』解説動画のお試し視聴ができます。
メール内のリンクから視聴を開始できます。
第2版 目次
- 第1章ゼロからデプロイまで
- 第2章デモアプリケーション
- 第3章ほぼ静的なページの作成
- 第4章Rails風味のRuby
- 第5章レイアウトを作成する
- 第6章ユーザーのモデルを作成する
- 第7章ユーザー登録
- 第8章サインイン、サインアウト
- 第9章ユーザーの更新・表示・削除
- 第10章ユーザーのマイクロポスト
- 第11章ユーザーをフォローする
- 第12章Rails 4.0へのアップグレード
第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のロゴをページに追加します)。しかしモックアップは正確である必要はありませんので、これで十分です。
Gitでバージョン管理をしているのであれば、これまでと同様、この時点で新しいブランチを作成するのがよいでしょう。
$ git checkout -b filling-in-layout
5.1.1ナビゲーション
第一段階として、サンプルアプリケーションにリンクとスタイルを追加するために、サイトのレイアウトファイルapplication.html.erb
(リスト4.3で登場) にHTML構造を追加し、レイアウトファイルを更新します。この更新には、領域 (divタグ) の追加、CSSクラスの追加、サイトナビゲーションの起点となる領域の追加も含まれます。完全なファイルはリスト5.1に示します。続いて、これを構成している多くの部品について解説します。表示結果を今すぐ確認したいのであれば、図5.2で確認できます (注:この時点ではわざわざ見に行くほどの仕上がりではありませんが)。
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<%= stylesheet_link_tag "application", media: "all" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
<header class="navbar navbar-fixed-top navbar-inverse">
<div class="navbar-inner">
<div class="container">
<%= link_to "sample app", '#', id: "logo" %>
<nav>
<ul class="nav pull-right">
<li><%= link_to "Home", '#' %></li>
<li><%= link_to "Help", '#' %></li>
<li><%= link_to "Sign in", '#' %></li>
</ul>
</nav>
</div>
</div>
</header>
<div class="container">
<%= yield %>
</div>
</body>
</html>
上のコードで最初に注目すべきことの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
タグには、navbar
、navbar-fixed-top
、navbar-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
タグのnav
とpull-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)。
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) を使用してこの画像を自動的に見つけます。
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。
Gemfile
へbootstrap-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をインクルードします。
app/assets/stylesheets/custom.css.scss
@import "bootstrap";
図5.3の結果で示されるように、このひとつの行にBootstrapフレームワーク全体が含まれます。(場合によっては、Ctrl-Cを使用してローカルのWebサーバーを再起動する必要があるかもしれません。また、スクリーンショットではBootstrap 2.0を使用していますが、このチュートリアルではBootstrap 2.1を使用しているために、おそらく見た目に多少の違いが生じている可能性があることをご了承ください。これらについて心配する必要はありません。) さて、テキストの配置は今ひとつで、ロゴにはスタイルもありませんが、色使いとsignupボタンはなかなかよい感じになってきました。
次に、リスト5.5に示したように、Webサイト全体にわたってレイアウトと個別のページにスタイルを与えるためのCSSを追加します。リスト5.5には多数の記述ルールがあります。CSSの記述ルールを把握するためには、関心のある箇所をコメントアウトして表示を確認することをお勧めします。CSSでは、/* … */
でコメントアウトできるので、調べてみたいコードをこれで囲い、表示がどのように変わるかを確認してみてください。リスト5.5が反映された結果を図5.4に示します。
app/assets/stylesheets/custom.css.scss
@import "bootstrap";
/* universal */
html {
overflow-y: scroll;
}
body {
padding-top: 60px;
}
section {
overflow: auto;
}
textarea {
resize: vertical;
}
.center {
text-align: center;
}
.center h1 {
margin-bottom: 10px;
}
リスト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で確認することができます。
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」だけが表示されているシンプルなものです。リスト5.7のCSSは、テキストを大文字に変換し、サイズ、色、配置を変更します (サイトロゴがページで一度しか使用されないことを前提としてCSS idを使用していますが、代わりにクラスを使用することもできます)。
app/assets/stylesheets/custom.css.scss
@import "bootstrap";
.
.
.
/* header */
#logo {
float: left;
margin-right: 10px;
font-size: 1.7em;
color: #fff;
text-transform: uppercase;
letter-spacing: -1px;
padding-top: 9px;
font-weight: bold;
line-height: 1;
}
#logo:hover {
color: #fff;
text-decoration: none;
}
上のコードのcolor: #fff
は、ロゴの色を白に変更します。HTMLの色は、16進数 (基数が16) の3つの数値の組み合わせで表現され、赤、緑、青の三原色に (この順序で) コード化することができます。このコード#ffffff
は、3色すべてが最大に使用されており、純白になります。なお、#fff
は、完全な#ffffff
の短縮形です。CSS標準には、共通HTMLカラーの別名も多数定義されています。たとえば、#fff
をwhite
と書くこともできます。リスト5.7のCSSの結果は図5.6で確認できます。
5.1.3パーシャル (partial)
リスト5.1のレイアウトはその目的を果たしていますが、少々散らかっています。HTML shimは、それだけで3行も占有し、風変わりなIE特有の文法を使用しているので、これをうまく隠すことができたらどんなによいでしょう。また、HTMLヘッダーは論理的な単位を形成するため、一箇所にまとめる必要もあります。Railsでは、パーシャル (partial) と呼ばれる機能を使用してこれを実現することができます。最初に、パーシャルを定義するとレイアウトがどのように変わるかを見てみましょう (リスト5.8)。
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<%= stylesheet_link_tag "application", media: "all" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
<%= render 'layouts/shim' %>
</head>
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
</div>
</body>
</html>
リスト5.8では、以下のように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に示します。
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
を呼び出してレイアウトに挿入することができます。
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。
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)。
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<%= stylesheet_link_tag "application", media: "all" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
<%= render 'layouts/shim' %>
</head>
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
<%= render 'layouts/footer' %>
</div>
</body>
</html>
そのまま実際にフッタを表示してみるとどうにも見苦しいので、リスト5.13でスタイルを若干追加しましょう。スタイルを追加した結果を図5.7に示します。
app/assets/stylesheets/custom.css.scss
.
.
.
/* footer */
footer {
margin-top: 45px;
padding-top: 5px;
border-top: 1px solid #eaeaea;
color: #999;
}
footer a {
color: #555;
}
footer a:hover {
color: #222;
}
footer small {
float: left;
}
footer ul {
float: right;
list-style: none;
}
footer ul li {
float: left;
margin-left: 10px;
}
5.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)。
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/assets
やvendor/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
タグのルールが、劇的に向上していることを確認してみてください。
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 | /about | about_path |
Help | /help | help_path |
Contact | /contact | contact_path |
Sign up | /signup | signup_path |
Sign in | /signin | signin_path |
先に進む前にContactページを追加しましょう (これは第3章の演習の積み残しです)。Contactページのテストをリスト5.16に示します。これは単にリスト3.18で使用されているテストのパターンに従ったものです。リスト5.16のアプリケーションコードでは、Ruby 1.9スタイルのハッシュに変更していることに注意してください。
spec/requests/static_pages_spec.rb
require 'spec_helper'
describe "Static pages" do
.
.
.
describe "Contact page" do
it "should have the h1 'Contact'" do
visit '/static_pages/contact'
page.should have_selector('h1', text: 'Contact')
end
it "should have the title 'Contact'" do
visit '/static_pages/contact'
page.should have_selector('title',
text: "Ruby on Rails Tutorial Sample App | Contact")
end
end
end
以下のテストは失敗するはずです。
$ bundle exec rspec spec/requests/static_pages_spec.rb
アプリケーションコードは、3.2.2のAboutページへの追加と良く似ています。最初にルート (リスト5.17) を更新します。次にcontact
アクションをStaticPagesコントローラ (リスト5.18) に追加します。最後にContactビュー (リスト5.19) を作成します。
config/routes.rb
SampleApp::Application.routes.draw do
get "static_pages/home"
get "static_pages/help"
get "static_pages/about"
get "static_pages/contact"
.
.
.
end
app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
.
.
.
def contact
end
end
app/views/static_pages/contact.html.erb
<% provide(:title, 'Contact') %>
<h1>Contact</h1>
<p>
Contact Ruby on Rails Tutorial about the sample app at the
<a href="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に示します。
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ページです。
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 / (“スラッシュ”) 用の特別な記法が書かれています。
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ページへルーティングします。
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ページへのリンクを追加します。
app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
<div class="navbar-inner">
<div class="container">
<%= link_to "sample app", root_path, id: "logo" %>
<nav>
<ul class="nav pull-right">
<li><%= link_to "Home", root_path %></li>
<li><%= link_to "Help", help_path %></li>
<li><%= link_to "Sign in", '#' %></li>
</ul>
</nav>
</div>
</div>
</header>
[Sign in] リンクの名前付きルートは第8章で作成するため、今の段階では’#’
のままにしておきます。
フッターパーシャル_footer.html.erb
にもリンクがあります。これらはAboutページとContactページへのリンクです (リスト5.25)。
app/views/layouts/_footer.html.erb
<footer class="footer">
<small>
<a href="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で説明します。
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.2のfull_title
ヘルパーと同様のfull_title
を定義することで、さらに改良することができます。これを行うには、RSpecユーティリティで使用するspec/support
ディレクトリーとutilities.rb
ファイルを作成します (リスト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に示します。
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)。
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)。
new
アクションを持つ最初のUsersコントローラ。app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
end
end
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ページのh1
とtitle
タグの内容をテストするコードを記入します。
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)。
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)。
app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<p>Find me in app/views/users/new.html.erb</p>
この時点で、リスト5.31のテストはパスするはずです。残っている作業は、Homeページのボタンに適切なリンクを追加することです。他のルートと同様、match ’/signup’
と記述したことでsignup_path
という名前付きルートができ、それをリスト5.34で使用します。
app/views/static_pages/home.html.erb
<div class="center hero-unit">
<h1>Welcome to the Sample App</h1>
<h2>
This is the home page for the
<a href="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) に示します。
この時点で、テストはパスするはずです。
$ 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演習
- リスト5.27の静的ページのテストコードは簡潔ですが、まだ繰り返しが残っています。RSpecには、冗長性を排除するためのShared Examplesと呼ばれる機能があります。リスト5.35の例に従い、Help、AboutとContactページで不足しているテストを補ってください。リスト3.30で簡単に紹介した
let
コマンドは、要求 (変数が使用されるときなど) に応じて与えられた値を持つ「ローカル変数」を作成することに注意してください。対照的にインスタンス変数は、要素代入 (assignment) されたときに作成されます。 - 既にお気付きの方もいると思いますが、これまで行なってきた、レイアウト上のリンクのルーティングテストは、そのリンクが実際に正しいページへのリンクになっているかどうかをチェックしていません。このようなテストを実装する方法のひとつは、RSpecの結合テスト内で
visit
とclick_link
を使用することです。レイアウトのリンクが正しく定義されていることを確認するコードを追加して、リスト5.36を補ってください。 - リスト5.37に示すように、元のヘルパーメソッドに対するテストを書き、それによってリスト5.26の
full_title
テストヘルパーへの依存を解消してください (spec/helpers
ディレクトリとapplication_helper_spec.rb
ファイルの両方を作成する必要があるでしょう)。次に、リスト5.38のコードを使用して、それをテストへinclude
してください。テストスイートを実行して、新しいコードに問題がないことを確認してください。注意: リスト5.37では、6.2.4で学ぶ正規表現を使用しています (この演習を提案し、コードを提供してくれたAlex Chaffeeに感謝します)。
spec/requests/static_pages_spec.rb
require 'spec_helper'
describe "Static pages" do
subject { page }
shared_examples_for "all static pages" do
it { should have_selector('h1', text: heading) }
it { should have_selector('title', text: full_title(page_title)) }
end
describe "Home page" do
before { visit root_path }
let(:heading) { 'Sample App' }
let(:page_title) { '' }
it_should_behave_like "all static pages"
it { should_not have_selector 'title', text: '| Home' }
end
describe "Help page" do
.
.
.
end
describe "About page" do
.
.
.
end
describe "Contact page" do
.
.
.
end
end
spec/requests/static_pages_spec.rb
require 'spec_helper'
describe "Static pages" do
.
.
.
it "should have the right links on the layout" do
visit root_path
click_link "About"
page.should have_selector 'title', text: full_title('About Us')
click_link "Help"
page.should # fill in
click_link "Contact"
page.should # fill in
click_link "Home"
click_link "Sign up now!"
page.should # fill in
click_link "sample app"
page.should # fill in
end
end
full_title
ヘルパーのテスト。spec/helpers/application_helper_spec.rb
require 'spec_helper'
describe ApplicationHelper do
describe "full_title" do
it "should include the page title" do
full_title("foo").should =~ /foo/
end
it "should include the base title" do
full_title("foo").should =~ /^Ruby on Rails Tutorial Sample App/
end
it "should not include a bar for the home page" do
full_title("").should_not =~ /\|/
end
end
end
full_title
テストヘルパーを単純に include
で置換する。spec/support/utilities.rb
include ApplicationHelper
- サンプルアプリケーションでBootstrapを使用するための変換作業で、目覚ましい働きで助けてくれた読者のColm Tuiteに感謝します。↑
- Ruby on Railsチュートリアルのモックアップは、Mockingbirdというオンラインモックアップアプリケーションで作成しています。↑
- CSSクラスは、Rubyのクラスとはまったく関係がありません。↑
- 既にお気付きだと思いますが、
img
タグは<img>...</img>ではなく<img ... />と書きます。この書式に従うタグは 単独タグとして知られています。 ↑ - アセットパイプラインでLESSを使うこともできます。詳細はless-rails-bootstrap gemを参照してください。↑
- 多くのRails開発者は、異なるビューの間で共通に使用するパーシャルを保存するディレクトリとして、
shared
ディレクトリを使用します。著者は、複数のビューで共有するユーティリティパーシャルについてはshared
フォルダに保存し、文字どおり全ページ (サイトレイアウトの一部として) 共通のパーシャルについてはlayouts
ディレクトリへ保存することを好んでいます (shared
ディレクトリは第7章で作成します)。著者はこのように分割保存するのが論理的であると考えますが、shared
フォルダにすべて保存しても問題なく動作します。↑ footer
タグと.footer
クラスを両方使用していることについて疑問に思う方がいるかもしれません。その理由は、footerタグとする方が読み手にとって意味が明確であるのと、footerクラスはBootstrapで使用するためです。footer
をdiv
に置き換えても動作は変わりません。↑- このチュートリアル構成は、Michael Erasmusによる素晴らしいブログ記事「5分でわかるRails 3のアセットパイプライン (英語)」をもとにしています。詳細については、Railsガイドの「アセットパイプライン」の項を参照してください。↑
- Sassでもサポートされている古い
.sass
というフォーマットでは、冗長性の少ない (括弧の少ない) 新しい言語を定義しますが、既存のプロジェクトには若干不便であり、既にCSSに慣れ親しんだ人にとっては学習が面倒でもあります。↑
Railsチュートリアルは YassLab 社によって運営されています。
コンテンツを継続的に提供するため、書籍・動画・質問対応サービスなどもご検討していただけると嬉しいです。
研修支援や教材連携にも対応しています。note マガジンや YouTube チャンネルも始めたので、よければぜひ遊びに来てください!