Rails Tutorial Chapter 3 Mostly static pages
Rails Tutorial Chapter 3 Mostly static pages
ほぼ静的なページの作成
DB と連携して動的な Web サイトを開発する前に、HTMLファイルだけで構成されている静的なページを作ってみようという趣旨の章。
セットアップ
rails new
して git リポジトリに登録して、最初期段階でデプロイまでやっておきましょう、といういつもの手順がまず解説されている。
どうせここから作り変えていくのにわざわざ hello world にするのはなんなんじゃろかと思っていたが、
デフォルトのRailsページはHerokuで破損してしまうことが多く、そのままだとデプロイが成功したのか失敗したのかがわかりにくいためです。
とのこと。なるほど。
bitbucket, heroku にそれぞれリポジトリ作成、デプロイできたら準備完了。
静的ページ
コントローラの生成
第2章では rails generate scaffold
としていたところを rails generate controller
とし、コントローラを生成する。
$ rails generate controller StaticPages home help Running via Spring preloader in process 4716 create app/controllers/static_pages_controller.rb route get 'static_pages/help' route get 'static_pages/home' invoke erb create app/views/static_pages create app/views/static_pages/home.html.erb create app/views/static_pages/help.html.erb invoke test_unit create test/controllers/static_pages_controller_test.rb invoke helper create app/helpers/static_pages_helper.rb invoke test_unit invoke assets invoke coffee create app/assets/javascripts/static_pages.coffee invoke scss create app/assets/stylesheets/static_pages.scss
これで /views/static_pages/home.html.erb
と app/views/static_pages/help.html.erb
が追加される。
テストから始める
何らかの変更を行う際には、常に「自動化テスト」を作成して、機能が正しく実装されたことを確認する習慣をぜひ身に付けましょう。
ということで早速章はテストについて進んでいく。
「テスト駆動」にするか「一括テスト」にするかを決める目安となるガイドラインがあると便利です。著者の経験を元に、以下のようにまとめてみました。
- アプリケーションのコードよりも明らかにテストコードの方が短くシンプルになる (=簡単に書ける) のであれば、テストを先に書けるようになることを目指す。
- 期待している動作がまだ固まりきっていないのであれば、先にアプリケーションのコードを書き上げ、続いて期待する動作をテストコードで記述することを目指す。
- セキュリティが最重要課題であれば、セキュリティモデルでエラーが発生した場合のテストを最初に書くべき。
- バグを見つけたら、そのバグを再現するテストを真っ先に書き、回帰バグを防ぐ体制を整えてからアプリケーションのコードの修正に取りかかる。
- 将来変更の可能性が少しでもあるコード (HTML構造の細部など) があれば必ずテストを書く。
- リファクタリングの前には必ずテストを書き、エラーを起こしそうなコードや、特に止まってしまいそうなコードを集中的にテストする。
ふむ。参考になる。
最初のテスト
rails generate
した時点ですでに test/controllers/static_pages_controller_test.rb
が出来上がっている。
require 'test_helper' class StaticPagesControllerTest < ActionDispatch::IntegrationTest test "should get home" do get static_pages_home_url assert_response :success end test "should get help" do get static_pages_help_url assert_response :success end end
以下のようなテストは
ruby test "should get home" do get :home assert_response :success end
言葉で表すと「Homeページのテスト。GETリクエストをhomeアクションに対して発行 (=送信) せよ。そうすれば、リクエストに対するレスポンスは[成功]になるはず。」となります。
なるほど、わかりやすい。
テストの実行は、Rails 5 にて rake
が rails
に一本化されているので以下のようになる。
$ rails test Running via Spring preloader in process 1757 /home/ubuntu/workspace/sample_app/db/schema.rb doesn't exist yet. Run `rails db:migrate` to create it, then try again. If you do not intend to use a database, you should instead alter /home/ubuntu/workspace/sample_app/config/application.rb to limit the frameworks that will be loaded. Run options: --seed 31511 # Running: .. Finished in 1.316032s, 1.5197 runs/s, 1.5197 assertions/s. 2 runs, 2 assertions, 0 failures, 0 errors, 0 skips
成功。これに対して以下の手順で進める。
- 「失敗するテストを最初に書く」
- 「次にアプリケーションのコードを書いてパスさせる」
- 「必要ならリファクタリングする」
home, help ページがある状態で、about ページを追加していく。
まず、about ページに関するテストを追記する。
require 'test_helper' class StaticPagesControllerTest < ActionDispatch::IntegrationTest test "should get home" do get static_pages_home_url assert_response :success end test "should get help" do get static_pages_help_url assert_response :success end test "should get about" do get static_pages_about_url assert_response :success end end
実行すると失敗する。
$ rails test Running via Spring preloader in process 1969 /home/ubuntu/workspace/sample_app/db/schema.rb doesn't exist yet. Run `rails db:migrate` to create it, then try again. If you do not intend to use a database, you should instead alter /home/ubuntu/workspace/sample_app/config/application.rb to limit the frameworks that will be loaded. Run options: --seed 44253 # Running: E Error: StaticPagesControllerTest#test_should_get_about: NameError: undefined local variable or method `static_pages_about_url' for #<StaticPagesControllerTest:0x0000000420cb10> test/controllers/static_pages_controller_test.rb:15:in `block in <class:StaticPagesControllerTest>' bin/rails test test/controllers/static_pages_controller_test.rb:14 .. Finished in 1.037955s, 2.8903 runs/s, 1.9269 assertions/s. 3 runs, 2 assertions, 0 failures, 1 errors, 0 skips
これをパスさせるように about ページを追加する。
NameError: undefined local variable or method 'static_pages_about_url'
ということで、about の URL がない、というエラー。
解消のために route を追加する。
Rails.application.routes.draw do get 'static_pages/home' get 'static_pages/help' get 'static_pages/about' root 'application#hello' end
この状態でテストを実行すると、
AbstractController::ActionNotFound: The action 'about' could not be found for StaticPagesController
となる。
今度は about の action がない、というエラー。
app/controllers/static_pages_controller.rb
に about action を追記。
class StaticPagesController < ApplicationController def home end def help end def about end end
テストを実行する。
ActionController::UnknownFormat: StaticPagesController#about is missing a template for this request format and variant.
というエラーになる。
Railsではテンプレートといえばすなわち「ビュー」のことです。
ということでビューを追加する。
$ touch app/views/static_pages/about.html.erb
<h1>About</h1> <p> The <a href="http://www.railstutorial.org/"><em>Ruby on Rails Tutorial</em></a> is a <a href="http://www.railstutorial.org/book">book</a> and <a href="http://screencasts.railstutorial.org/">screencast series</a> to teach web development with <a href="http://rubyonrails.org/">Ruby on Rails</a>. This is the sample application for the tutorial. </p>
テストを実行すると成功する。
$ rails t Running via Spring preloader in process 1740 /home/ubuntu/workspace/sample_app/db/schema.rb doesn't exist yet. Run `rails db:migrate` to create it, then try again. If you do not intend to use a database, you should instead alter /home/ubuntu/workspace/sample_app/config/application.rb to limit the frameworks that will be loaded. Run options: --seed 9551 # Running: ... Finished in 1.206133s, 2.4873 runs/s, 2.4873 assertions/s. 3 runs, 3 assertions, 0 failures, 0 errors, 0 skips
このようにしてテストのエラーを取り除きながら実装、という進め方ができる。
リファクタリング
テストがGREENになったので、安心してコードをリファクタリングできるようになりました。
いいね。
こまめにリファクタリングを繰り返してコードを常にすみずみまで美しくコンパクトに保ち、他の開発者や未来の自分の開発意欲を阻喪することのないようにしなければなりません。
静的なページができたところで、少しずつ動的に変えていくことで上記を体感する流れ。
少しだけ動的なページ
ページの内容に応じて、ページのタイトルを自ら書き換えて表示するように変更する。
タイトルのテスト
まずテストを、selector を使って下記のように変更する。
require 'test_helper' class StaticPagesControllerTest < ActionDispatch::IntegrationTest test "should get home" do get static_pages_home_url assert_response :success assert_select "title", "Home | Ruby on Rails Tutorial Sample App" end test "should get help" do get static_pages_help_url assert_response :success assert_select "title", "Help | Ruby on Rails Tutorial Sample App" end test "should get about" do get static_pages_about_url assert_response :success assert_select "title", "About | Ruby on Rails Tutorial Sample App" end end
テストを実行し、失敗することを確認する。
タイトルの追加
title タグを各 .html.erb
ファイルに追加することでテストは通過するようになる。
が
- ページのタイトルがどれもほぼ同じ (完全にではないが)。
- 「Ruby on Rails Tutorial Sample App」という文字が3つのタイトルで繰り返し使われている。
- HTMLの構造全体が各ページで重複している。
と DRY に反する。
レイアウトと埋め込み
ビューに Embedded Ruby を使用する。
<% provide(:title, "Home") %> <!DOCTYPE html> <html> <head> <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title> </head> <body> <h1>Sample App</h1> <p> This is the home page for the <a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a> sample application. </p> </body> </html>
各ページで <% provide(:title, "Home") %>
の箇所をページごとに書き換えればよい。
これでもまだ重複するコードが多い。
application.html.erb
application.html.erb
というレイアウトファイルに各ページ共通のレイアウトを定義できる。
<!DOCTYPE html> <html> <head> <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> <%= csrf_meta_tags %> </head> <body> <%= yield %> </body> </html>
DRY になったところでテスト実行。
$ rails test 3 tests, 6 assertions, 0 failures, 0 errors, 0 skips
成功。
ルーティングの設定
アプリケーションルートのルーティングを設定する。
Rails.application.routes.draw do root 'static_pages#home' get 'static_pages/home' get 'static_pages/help' get 'static_pages/about' end
これで root への GET リクエストが StaticPagesコントローラのhomeアクションにルーティングされるようになる。
まとめ
第3章のまとめは以下。
- 新しいRailsアプリケーションをゼロから作成したのはこれで3度目。今回も必要なgemのインストール、リモートリポジトリへのプッシュ、production環境まで行った。
- コントローラを新規作成するためのrailsのスクリプトはrails generate controller ControllerName
。訳注: コントローラ名はキャメルケース、アクション名はスネークケースにする。 - 新しいルーティングはconfig/routes.rbファイルで定義する。
- Railsのビューでは、静的HTMLの他にERB (埋め込みRuby: Embedded RuBy) も使用できる。
- 常に自動化テストを使用して新機能開発を進めることで、自信を持ってリファクタリングできるようになり、回帰バグもいちはやくキャッチできるようになる。
- テスト駆動開発では「レッド・グリーン・リファクタリング」サイクルを繰り返す。
- Railsのレイアウトでは、アプリケーションのページの共通部分をテンプレートに置くことでコードの重複を解決することができる。