第5章レイアウトを作成する
5.1.1 ナビゲーション
RailsはデフォルトでHTML5を使います (<!DOCTYPE html>
と書いてHTML5であることを宣言します)。ただHTML5は比較的新しく、一部のブラウザ (特に旧式のInternet Explorer) ではHTML5のサポートが不完全である場合があります。そのため、次のようなJavaScriptのコード (通称: HTML5 shim (or shiv))を使ってこの問題を回避します。
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js">
</script>
<![endif]-->
[if lt IE 9]
は、Railsの一部ではありません。これは実は、条件付きコメントと呼ばれるもので、今回のような状況のためにInternet Explorerで特別にサポートされています。link_to
を使います (アンカータグa
が最終的に生成されます)。yield
メソッドはWebサイトのレイアウトにページごとの内容を挿入します。curl -o app/assets/images/rails.png -OL railstutorial.jp/rails.png
演習
1:Webページと言ったらネコ画像、というぐらいにはWebにはネコ画像が溢れていますよね。コマンドを使って、ネコ画像をダウンロードしてきましょう
curl -OL cdn.learnenough.com/kitten.jpg
2:mvコマンドを使って、ダウンロードしたkitten.jpgファイルを適切なアセットディレクトリに移動してください
mv kitten.jpg ~/environment/sample_app/app/assets/images/
3:image_tagを使って、kitten.jpg画像を表示してみてください
<%= image_tag("kitten.jpg")%>
5.1.2 BootstrapとカスタムCSS
rails generateコマンドを実行することでコントローラーごとに分けられたCSSファイルが自動的に生成されますが、
これらのファイルを正しい順序で読み込ませるのは至難の技なので、
すべてのCSSを1つにまとめる方針を採っています。
カスタムCSSを動かすための最初の一歩は、カスタムCSSファイルを作ることです。
Gemfile
にbootstrap-sass
を追加するsource 'https://rubygems.org'
gem 'rails', '5.1.6'
gem 'bootstrap-sass', '3.3.7'
Bootstrapをインストール
bundle install
カスタムCSSファイルを作る
touch app/assets/stylesheets/custom.scss
Bootstrap CSSを追加する
app/assets/stylesheets/custom.scss
@importを使って、Bootstrap(とそれに関連するSprockets)を読み込みますよ。
@import "bootstrap-sprockets";
@import "bootstrap";
/* universal */
body {
padding-top: 60px;
}
section {
overflow: auto;
}
textarea {
resize: vertical;
}
.center {
text-align: center;
}
.center h1 {
margin-bottom: 10px;
}
/* typography */
h1, h2, h3, h4, h5, h6 {
line-height: 1;
}
h1 {
font-size: 3em;
letter-spacing: -2px;
margin-bottom: 30px;
text-align: center;
}
h2 {
font-size: 1.2em;
letter-spacing: -1px;
margin-bottom: 30px;
text-align: center;
font-weight: normal;
color: #777;
}
p {
font-size: 1.1em;
line-height: 1.7em;
}
/* 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;
}
// マウスオーバーしたときの設定だよ
#logo:hover {
color: #fff;
text-decoration: none;
}
演習
1:前の演習で使ったネコ画像をコメントアウトしてみてください。また、ブラウザのHTMLインスペクタ機能を使って、コメントアウトするとHTMLのソースからも消えていることを確認してみてください。
<%#= image_tag("kitten.jpg", alt: "Kitten") %>
2:※1のコードをcustom.scss
に追加し、すべての画像を非表示にしてみてください。うまくいけば、Railsのロゴ画像がHomeページから消えるはずです。先ほどと同様にインスペクタ機能を使って、今度はHTMLのソースコードは残ったままで、画像だけが表示されなくなっていることを確認してみてください。
#※1
img {
display: none;
}
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<%= csrf_meta_tags %>
<%= stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application',
'data-turbolinks-track': 'reload' %>
<%= render 'layouts/shim' %>
</head>
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
</div>
</body>
</html>
5.1.3 パーシャル (partial)
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<%= csrf_meta_tags %>
<%= stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application',
'data-turbolinks-track': 'reload' %>
<%= render 'layouts/shim' %>
</head>
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
<%= render 'layouts/footer' %> </div>
</body>
</html>
header用のパーシャル
app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
<div class="container">
<%= link_to "sample app", '#', id: "logo" %>
<nav>
<ul class="nav navbar-nav navbar-right">
<li><%= link_to "Home", '#' %></li>
<li><%= link_to "Help", '#' %></li>
<li><%= link_to "Log in", '#' %></li>
</ul>
</nav>
</div>
</header>
renderで
呼び出してレイアウトに挿入することができます。演習
1:Railsがデフォルトで生成するheadタグの部分を、renderに置き換えてみてください。
ヒント: 単純に削除してしまうと後でパーシャルを1から書き直す必要が出てくるので、削除する前にどこかに退避しておきましょう。
Railsのデフォルトのheadタグをrender
に置き換える
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<%= render 'layouts/rails_default' %>
<%= render 'layouts/shim' %>
</head>
layouts/_rails_default.html.erbとlayouts/_shim.html.erbを作成し
置き換えたものを貼り付ける
2:※2のようなパーシャルはまだ作っていないので、現時点ではテストは redになっているはずです。実際にテストを実行して確認してみましょう。
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<%= render 'layouts/rails_default' %>
<%= render 'layouts/shim' %>
</head>
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
<%= render 'layouts/footer' %>
</div>
</body>
</html>
動作確認なので実際にやってみる
3:layoutsディレクトリにheadタグ用のパーシャルを作成し、先ほど退避しておいたコードを書き込み、最後にテストが green に戻ることを確認しましょう。
_shim.html.erbをviews/layouts/_shim.html.erbに置けばOK
5.2.1 アセットパイプライン
Rails開発者の視点からは、アセットディレクトリ、マニフェストファイル、プリプロセッサエンジンという、3つの主要な機能が理解の対象となります。
アセットディレクトリ
Railsのアセットパイプラインでは、静的ファイルを目的別に分類する、標準的な3つのディレクトリが使われています。
- app/assets: 現在のアプリケーション固有のアセット
- lib/assets: あなたの開発チームによって作成されたライブラリ用のアセット
- vendor/assets: サードパーティのアセット
これらのディレクトリには、それぞれのアセットクラス用のサブディレクトリがあります。
custom.scss
はサンプルアプリケーション固有のアセットなので、app/assets/stylesheets
に配置されていたのです。
マニフェストファイル
静的ファイル (アセット) を上記の場所へそれぞれ配置すれば、マニフェストファイルを使って、それらをどのように1つのファイルにまとめるのかをRailsに指示することができます。
なお、実際にアセットをまとめる処理を行うのはSprocketsというgemです。
マニフェストファイルはCSSとJavaScriptには適用されますが、画像ファイルには適用されません。
コメント内の次の部分は、Sprocketsが適切なファイルを読み込むために使われます。
app/assets/stylesheets/application.css
/* . . *= require_tree . *= require_self */
プリプロセッサエンジン
必要なアセットをディレクトリに配置してまとめた後、Railsはさまざまなプリプロセッサエンジンを介してそれらを実行し、ブラウザに配信できるようにそれらをマニフェストファイルを用いて結合し、サイトテンプレート用に準備します。
Rilsはどのプリプロセッサを使うのかを、ファイル名の拡張子を使って判断します。
最も一般的な拡張子は、Sass用の.scss、CoffeeScript用の.coffee、
埋め込みRuby (ERb) 用の.erbです。
CoffeeScriptはエレガントかつ簡潔な言語で、JavaScriptにコンパイルしてくれる
JavaScriptの拡張言語だよ。
プリプロセッサエンジンは、繋げて実行する (chain) ことができる。
foobar.js.coffee
上の拡張子の場合、CoffeeScriptプロセッサ経由で実行されるよ。
foobar.js.erb.coffee
上の拡張子の場合は、CoffeeScriptとERbの両方で実行されます
(コードは右から左へと実行されるので、この例ではCoffeeScriptが最初に実行されます)。
Asset Pipelineの最大のメリットの1つは、本番のアプリケーションで効率的に
なるように最適化されたアセットも自動的に生成されることです。
開発環境と本番環境という、2つの異なった状況に対して最高の環境を提供してくれます。
5.2.2 SCSS
ネスト
スタイルシート内に共通のパターンがある場合は、要素をネストさせることができます。、次のように.center
と.center h1
の両方に対してルールがあります。
.center {
text-align: center;
}
.center h1 {
margin-bottom: 10px;
}
上のルールは、Sassを使って次のように書き換えることができます。
.center {
text-align: center;
h1 {
margin-bottom: 10px;
}
}
上の例では、ネストの内側にあるh1
というルールは、.center
のルールを継承しています。
もう少し異なるルールに対してネスト機能を使う例を見てみましょう。
次のコードがあります。
#logo {
float: left;
margin-right: 10px;
font-size: 1.7em;
color: #fff;
text-transform: uppercase;
letter-spacing: -1px;
padding-top: 9px;
font-weight: bold;
}
#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;
&:hover {
color: #fff;
text-decoration: none;
}
}
Sassは、SCSSをCSSに変換する際に、&:hover
を#logo:hover
に置き換えています。
変数
Sassでは、冗長なコードを削除し、より自由な表現を可能にするために、変数が定義できるようになっています。例えば↓を見てみると、同じ色を繰り返し参照している箇所があります。
h2 {
.
color: #777;
}
.
.
.
footer {
.
.
.
color: #777;
}
上のコードの#777
は薄い灰色を指しています。Sassでは、このような値を変数として定義し、次のように変数名を与えることができます。
$light-gray: #777;
この機能を使って、SCSSを次のように書き直すことができます。
$light-gray: #777;
.
.
.
h2 {
.
.
.
color: $light-gray;
}
.
.
.
footer {
.
.
.
color: $light-gray;
}
$light-gray
のような変数名は、#777
のような値よりも分かりやすいので、たとえその変数が繰り返し使われないとしても、変数名を与えることは多くの場合有用です。
演習
footerのCSSを手作業で変換してみましょう。具体的には、1つずつ変換していき、最後のようにしてみてください
リスト 5.20のようにするだけ
5.3.2RailsのルートURL
ルートURLを定義すると、root_pathやroot_urlといったメソッドを通してURLを参照することができます。
ちなみに前者はルートURL以下の文字列を、後者は完全なURLの文字列を返します。
root_path -> ‘/’
root_url -> ‘http://www.example.com/’
なお、Railsチュートリアルでは一般的な規約に従い、基本的には_path書式を使い、
リダイレクトの場合のみ_url書式を使うようにします。
get
ルールを使って定義
get '/help', to: 'static_pages#help'
このようにgetルールを使って変更すると、GET
リクエストが /help に送信されたときにStaticPagesコントローラーのhelp
アクションを呼び出してくれるようになります。また、ルートURLのときと同様に、help_path
やhelp_url
といった名前付きルートも使えるようになります。
help_path -> '/help'
help_url -> 'http://www.example.com/help'
演習
1:実は名前付きルートは、as:オプションを使って変更することができます。Helpページの名前付きルートをhelfに変更してみてください
get '/help', to: 'static_pages#help', as: 'helf'
2:先ほどの変更により、テストが redになっていることを確認してください。リスト 5.28を参考にルーティングを更新して、テストを greenにして見てください。
コントローラーを get helf_pathに変更するだけ
3:エディタのUndo機能を使って、今回の演習で行った変更を元に戻して見てください。
実際にやってみるだけ
5.3.3 名前付きルート
演習
1:前の演習のようにhelfルーティングを作成し、レイアウトのリンクを
更新してみてください。
routes.rb Rails.application.routes.draw do (中略) get '/help', to: 'static_pages#help', as: 'helf' (中略) end
_header.html.erb (前略) <li><%= link_to "Help", helf_path %></li> (後略)
2:前回の演習と同様に、エディタのUndo機能を使ってこの演習で行った変更を
元に戻してみてください。
Undo機能を使うだけ
5.3.4 リンクのテスト
統合テストを作成
rails generate integration_test site_layout
test/integration/site_layout_test.rb
require 'test_helper' class SiteLayoutTest < ActionDispatch::IntegrationTest test "layout links" do get root_path assert_template 'static_pages/home' assert_select "a[href=?]", root_path, count: 2 assert_select "a[href=?]", help_path assert_select "a[href=?]", about_path assert_select "a[href=?]", contact_path end end
rootで2回やるのは1つはロゴに、もう1つはナビゲーションバーにありますので
Railsは自動的にはてなマーク “?” を(aboutの場合)about_pathに置換しています
(このとき “about_path” 内に特殊記号があればエスケープ処理されるので注意)
assert_selectのいくつかの使用例
assert_select “div” <div>foobar</div>
assert_select “div”, “foobar” <div>foobar</div>
assert_select “div.nav” <div class=”nav”>foobar</div>
assert_select “div#profile” <div id=”profile”>foobar</div>
assert_select “div[name=yo]” <div name=”yo”>hey</div>
assert_select “a[href=?]”, ‘/’, count: 1 <a href=”/”>foo</a>
assert_select “a[href=?]”, ‘/’, text: “foo” <a href=”/”>foo</a>
演習
1:footerパーシャルのabout_pathをcontact_pathに変更してみて、
テストが正しくエラーを捕まえてくれるかどうか確認してみてください。
エラーが発生したらOK
2:Applicationヘルパーで使っているfull_titleヘルパーを、
test環境でも使えるようにすると便利で正しいタイトルをテストすることができます。
ただし、full_titleヘルパーに対するテストを書く必要があります。
そこで、Applicationヘルパーをテストするファイルを作成し、リスト 5.37のFILL_INの部分を適切なコードに置き換えてみてくだい。
ヒント: リスト 5.37ではassert_equal <期待される値>, <実際の値>といった形で使っていましたが、
内部では==演算子で期待される値と実際の値を比較し、
正しいかどうかのテストをしています。
application_helper.rb module ApplicationHelper # ページごとの完全なタイトルを返します。 # コメント行 def full_title(page_title = '') # メソッド定義とオプション引数 base_title = "Ruby on Rails Tutoial Sample App" # 変数への代入 if page_title.empty? # 論理値テスト base_title # 暗黙の戻り値 else page_title + " | " + base_title # 文字列の結合 end end end
test/helpers/application_helper_test.rb require 'test_helper' class ApplicationHelperTest < ActionView::TestCase test "full title helper" do assert_equal full_title, 'Ruby on Rails Tutorial Sample App' assert_equal full_title("Help"), 'Help | Ruby on Rails Tutorial Sample App' end end
5.4.1 Usersコントローラ
演習
1:users_new_urlではなくsignup_pathを使えるようにしてみて下さい。
users_controller_test.rb require 'test_helper' class UsersControllerTest < ActionDispatch::IntegrationTest test "should get new" do get signup_path assert_response :success end end
5.4.2 ユーザー登録用URL
演習
1:signupルートの部分をコメントアウトし、テストredになることを確認して確認できたら、コメントアウトを解除して greenの状態に戻して下さい。
コメントアウトして、テストするだけ
2:統合テストにsignupページにアクセスするコードを追加してください
(getメソッドを使います)。コードを追加したら実際にテストを実行し、
結果が正しいことを確認してください。
get signup_pathを追加してテストする
5.5.1 本章のまとめ
-
HTML5を使ってheaderやfooter、logoやbodyといったコンテンツのレイアウトを定義しました
-
Railsのパーシャルは効率化のために使われ、別ファイルにマークアップを
切り出すことができます
-
CSSは、CSSクラスとidを使ってレイアウトやデザインを調整します
-
Bootstrapフレームワークを使うと、いい感じのデザインを素早く実装できる
-
SassとAsset Pipelineは、(開発効率のために切り分けられた)
CSSの冗長な部分を圧縮し、本番環境に最適化した結果を出力する
-
Railsのルーティングでは自由にルールを定義することができ、
また、その際に名前付きルートも使えるようになる
-
統合テストは、ブラウザによるページ間の遷移を効率的にシミュレートする