techium

このブログは何かに追われないと頑張れない人たちが週一更新をノルマに技術情報を発信するブログです。もし何か調査して欲しい内容がありましたら、@kobashinG or @muchiki0226 までいただけますと気が向いたら調査するかもしれません。

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.erbapp/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 にて rakerails に一本化されているので以下のようになる。

$ 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のレイアウトでは、アプリケーションのページの共通部分をテンプレートに置くことでコードの重複を解決することができる。