kuma0319のブログ

webエンジニアになるためのアウトプットブログ

Ruby on Railsチュートリアル第3章

Railsチュートリアルの第3章を進めていきます。

第3章(ほぼ静的なページの作成)

本章からは14章まで通しで取り組むsample appを作成していきます。

静的ページ

コントローラの生成

静的ページ用のコントローラをgenerateする。※コントローラの名前はモデルと違って複数形。またキャメルケース表記で記載することで、自動的にスネークケースのコントローラが生成される。

$ rails generate controller StaticPages home help
rails generatedb:migrateの取り消し操作
  • rails generateコマンドで生成したコントローラはrails destoyで作成した一連の操作の取り消しが可能。
  • モデルについても同様に取り消しが可能。
  • rails db:migrateを一つ前に戻す操作はrails dc:rollback

最初のテスト

ここからテストが入ってきます。テスト駆動開発についてはこの後まとめておきます。
テスト駆動開発は真面目にやっておきたいので、テスト内にコメントを残してテストの内容を視覚化しておくようにします。

[test/controllers/static_pages_controller_test.rb]
require "test_helper"

class StaticPagesControllerTest < ActionDispatch::IntegrationTest
  test "should get home" do
    get static_pages_home_url   #home_urlへアクセス。
    assert_response :success    #HTTPステータスコード200を確認。
  end

  test "should get help" do
    get static_pages_help_url   #help_urlへアクセス。
    assert_response :success    #HTTPステータスコード200を確認。
  end
end
テストのGREENとREDの色付け、Guardによる自動化

テストは GREENなのかREDなのか視覚的に分かった方がよいので色付け(minitest-reporters gem)しておきます。

[test/test_helper.rb]
ENV["RAILS_ENV"] ||= "test"
require_relative "../config/environment"
require "rails/test_help"
require "minitest/reporters"
Minitest::Reporters.use!

class ActiveSupport::TestCase
  # 指定のワーカー数でテストを並列実行する
  parallelize(workers: :number_of_processors)

  # test/fixtures/*.ymlにあるすべてのfixtureをセットアップする
  fixtures :all

  # (すべてのテストで使うその他のヘルパーメソッドは省略)
end

また、Guardによる自動化もやっておくとかなり便利でしたので今のうちに設定しておきます。

  1. gemのguard、guard-minitestを入れておく。(既にインストール済み)
  2. $ bundle exec guard initを実行。
  3. Guadfileをカスタマイズ。
  4. $ bundle exec guardを実行。
Aboutページテスト駆動開発

テストの内容は他のページと同じ。

[test/controllers/static_pages_controller_test.rb]
require "test_helper"

class StaticPagesControllerTest < ActionDispatch::IntegrationTest
  test "should get home" do
    get static_pages_home_url   #home_urlへアクセス。
    assert_response :success    #HTTPステータスコード200を確認。
  end

  test "should get help" do
    get static_pages_help_url   #help_urlへアクセス。
    assert_response :success    #HTTPステータスコード200を確認。
  end

  test "should get about" do
    get static_pages_about_url   #about_urlへアクセス。
    assert_response :success    #HTTPステータスコード200を確認。
  end
end

aboutページへのルーティング、アクション、ビューの全てが出来ていないので作ります。(単純なので中身は省略)

テスト駆動開発に関して

テスト駆動開発には賛否あるようで、メリット・デメリットを挙げてみました。
以下のサイトとchatGPTへの質問を参考にしました。両者の意見は概ね一致してました。
techracho.bpsinc.jp

メリット

バグの減少と早期発見
テストを起点とした開発となるため、バグの早期発見に繋がる。参考サイトでもマイクロソフトでTDDを導入したものとしてないものでは、TDDを導入したことで欠陥密度が40~90%減少したと書いています。驚くべき数値ですね。
品質向上、シンプル化
TDDのサイクルにはリファクタリングも含まれており、自身のコードの品質改善にも寄与し、メンテナンス性も高いようです。

デメリット

開発時間の増加
これも一概には言えないようですが、”導入初期段階では遅くなる可能性がある”ようです。ただ、ここで時間が増加しても最終的にはバグの早期発見や品質の向上によってデメリットとならないとも考えられます。

Railsチュートリアルでも、絶対的にどっちが良いということは名言しておらず、場合分け的に後からテストするか先にテストする(TDD)かを選択すると良いとしておりました。
基本的にはTDDを優先的に考えて、動作の仕様が固まっていない場合やHTMLの細かい部分の修正などをおこなう場合はテストを後回しにすればよいのかなと考えています。

少しだけ動的なページ

ここではページのタイトルが表示ページに応じて動的に変わるように作成。

テストの記述

ここでは、assert_selectメソッドを使用。このメソッドの詳細はRailsガイドに記載。 Rails テスティングガイド - Railsガイド

[test/controllers/static_pages_controller_test.rb] 
require "test_helper"

class StaticPagesControllerTest < ActionDispatch::IntegrationTest
  test "should get home" do
    get static_pages_home_url   #home_urlへアクセス。
    assert_response :success    #HTTPステータスコード200を確認。
    assert_select "title", "Home | Ruby on Rails Tutorial Sample App" #タイトルタグを検証。
  end

  test "should get help" do
    get static_pages_help_url   #help_urlへアクセス。
    assert_response :success    #HTTPステータスコード200を確認。
    assert_select "title", "Help | Ruby on Rails Tutorial Sample App" #タイトルタグを検証。
  end

  test "should get about" do
    get static_pages_about_url   #about_urlへアクセス。
    assert_response :success    #HTTPステータスコード200を確認。
    assert_select "title", "About | Ruby on Rails Tutorial Sample App" #タイトルタグを検証。
  end
end

このテストに合うように、各viewを編集。Homeのみ記載。

[app/views/static_pages/home.html.erb]
<!DOCTYPE html>
<html>
  <head>
    <title>Home | Ruby on Rails Tutorial Sample App</title>
  </head>
  <body>
    <h1>Sample App</h1>
    <p>
      This is the home page for the
      <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
      sample application.
    </p>
  </body>
</html>

リファクタリング

自力でリファクタリングをやっていく。
ここでは、home、help、aboutページが同じ形になるようにERBを使用して記述を変え、最終的にlayputs/application.html.erbにまとめる。 まず、各ビューはprovideメソッド+yieldメソッドの関係を用いる。

[app/views/static_pages/home.html.erb]
<% provide(:title,'Home') %>
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
  </head>
  <body>
    <h1>Help</h1>
    <p>
      Get help on the Ruby on Rails Tutorial at the
      <a href="https://railstutorial.jp/help">Rails Tutorial help
      page</a>.
      To get help on this sample app, see the
      <a href="https://railstutorial.jp/#ebook">
      <em>Ruby on Rails Tutorial</em> book</a>.
    </p>
  </body>
</html>

layputs/application.html.erbにまとめる。

[app/views/layouts/application.html.erb]
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
    <%= javascript_importmap_tags %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>

各ページも必要最低限な要素のみにリファクタリング
※タグも必要無かったようです。タグの内側をレイアウトの部分と認識してyieldで表示されるのかと思いましたが、そうでは無く、レイアウトのyield部分にビューの内容全てが挿入されるようです。

[app/views/static_pages/home.html.erb]
<% provide(:title,'Home') %>
<body>
  <h1>Sample App</h1>
  <p>
    This is the home page for the
    <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
    sample application.
  </p>
</body>

一応これで、テストはGREENで、各ページにアクセスした際のタイトルも変わってたのでok。
正解の方のレイアウトには<meta charset="utf-8">も入っていた。

また、csp_meta_tagではクロスサイトスクリプティング攻撃対策用に、コンテンツセキュリティポリシーを実装。
csrf_meta_tagsクロスサイトリクエストフォージェリー攻撃対策用。

第3章ー演習ー

3.2.1

①Fooというコントローラを生成し、その中にbarとbazアクションを追加してみてください。
(解答)
rails g controllerでFooコントローラを作成。

$ rails g controller Foo bar baz
      create  app/controllers/foo_controller.rb
       route  get 'foo/bar'
              get 'foo/baz'
      invoke  erb
      create    app/views/foo
      create    app/views/foo/bar.html.erb
      create    app/views/foo/baz.html.erb
      invoke  test_unit
      create    test/controllers/foo_controller_test.rb
      invoke  helper
      create    app/helpers/foo_helper.rb
      invoke    test_unit

②コラム 3.1で紹介したテクニックを駆使して、Fooコントローラとそれに関連するアクションを削除してみてください。
(解答)
rails destroy controllerでFooコントローラを削除。

$ rails destroy controller Foo bar baz
      remove  app/controllers/foo_controller.rb
       route  get 'foo/bar'
              get 'foo/baz'
      invoke  erb
      remove    app/views/foo
      remove    app/views/foo/bar.html.erb
      remove    app/views/foo/baz.html.erb
      invoke  test_unit
      remove    test/controllers/foo_controller_test.rb
      invoke  helper
      remove    app/helpers/foo_helper.rb
      invoke    test_unit

3.4.2

①StaticPagesコントローラのテスト(リスト 3.26)には、いくつか繰り返しがあったことにお気づきでしょうか? 特に「Ruby on Rails Tutorial Sample App」という基本タイトルは、各テストで毎回同じ内容を書いてしまっています。そこで、setupという特別なメソッド(各テストが実行される直前で実行されるメソッド)を使って、この問題を解決したいと思います。まずは、リスト 3.32のテストが green になることを確認してみてください(リスト 3.32では、2.2.2で少し触れたインスタンス変数や文字列の式展開というテクニックを使っています。それぞれ4.4.5と4.2.1で詳しく解説するので、今はわからなくても問題ありません)
(解答)
assert_selectで評価するタイトルタグの記述が重複している。Ruby on Rails Tutorial部分はsetupメソッドでまとめ、式展開で記述。

[test/controllers/static_pages_controller_test.rb]
require "test_helper"

class StaticPagesControllerTest < ActionDispatch::IntegrationTest

  def setup
    @base_title = "Ruby on Rails Tutorial Sample App"
  end

  test "should get home" do
    get static_pages_home_url   #home_urlへアクセス。
    assert_response :success    #HTTPステータスコード200を確認。
    assert_select "title", "Home | #{@base_title}" #タイトルタグを検証。
  end

  test "should get help" do
    get static_pages_help_url   #help_urlへアクセス。
    assert_response :success    #HTTPステータスコード200を確認。
    assert_select "title", "Help | #{@base_title}" #タイトルタグを検証。
  end

  test "should get about" do
    get static_pages_about_url   #about_urlへアクセス。
    assert_response :success    #HTTPステータスコード200を確認。
    assert_select "title", "About | #{@base_title}" #タイトルタグを検証。
  end
end

3.4.3

①サンプルアプリケーションにContact(問い合わせ先)ページを作成してください17 。(ヒント: まずはリスト 3.17を参考にして、/static_pages/contactというURLのページに「Contact | Ruby on Rails Tutorial Sample App」というタイトルが存在するかどうかを確認するテストを最初に作成しましょう。次に、3.3.3でAboutページを作ったときのと同じように、Contactページにもリスト 3.42のコンテンツを表示してみましょう。)
(解答) 同じ内容なので飛ばします。

3.4.4

①リスト 3.43にrootルーティングを追加したことで、root_urlというRailsヘルパーが使えるようになりました(以前、static_pages_home_urlが使えるようになったときと同じです)。リスト 3.44の(コードを書き込む)と記された部分を置き換えて、rootルーティングのテストを書いてみてください。
(解答)

  test "should get root" do
    get root_url                #root_urlへアクセス。
    assert_response :success    #HTTPステータスコード200を確認
  end

②実はリスト 3.43のコードを書いていたので、先ほどの課題のテストは既に green になっているはずです。このような場合、テストを変更する前から成功していたのか、変更した後に成功するようになったのかを判断するのが難しくなります。リスト 3.43のコードがテスト結果に影響を与えていることを確認するため、リスト 3.45のようにrootルーティングを試しにコメントアウトし、 red になるかどうか確かめてみましょう(なおRubyのコメント機能については4.2で説明します)。最後に、コメントアウトした箇所を元に戻し(すなわちリスト 3.43に戻し)、テストが green になることを確認してみましょう。
(解答) rootルーティングをコメントアウトしたらテスト失敗になった。

第3章ーまとめー

  • コントローラをgenerateするときは、複数形+キャメルケースで記述。一般にファイル名はスネークケースで記載される。
  • rails generateで作成したコントローラやモデルは、付随して作成されたファイルも含めてdestroyコマンドでまとめて削除可能。DBのマイグレーションロールバック可能。
  • テスト駆動開発は、バグの発見やコードリファクタリング、メンテナンス性といった観点で非常に優れる手法。後からテスト手法も組み合わせて効率的に進める。
  • コードのリファクタリングはシンプル化、整頓の観点から非常に大事。
  • テストはGuardで自動化が可能。色付けしておくと、成功したか失敗したかが一目でわかる。

第3章終わり🐻