v-crn Code Log

主に備忘録

Rails Tutorial 第4版 第5章 演習 解答例

f:id:v-crn:20190711155257p:plain

この文書はRails Tutorial 第4版 第5章 レイアウトを作成するの演習に対する個人の解答例です。解答には誤りや不適切な表現が含まれていることがありますが、もし誤謬を見つけたらコメント頂けると嬉しいです。
それでは、やっていきましょう!

5.1 構造を追加する

5.1.1 ナビゲーション

1. Webページと言ったらネコ画像、というぐらいにはWebにはネコ画像が溢れていますよね。リスト 5.4のコマンドを使って、図 5.3のネコ画像をダウンロードしてきましょう。

リスト 5.4のコマンドを実行してネコ画像をダウンロードします。

$ curl -OL cdn.learnenough.com/kitten.jpg

2. mvコマンドを使って、ダウンロードしたkitten.jpgファイルを適切なアセットディレクトリに移動してください (参考: 5.2.1)。

$ mv kitten.jpg app/assets/images

3. image_tagを使って、kitten.jpg画像を表示してみてください (図 5.4)。

home.html.erbに次のコードを追加します。

<%= link_to image_tag("kitten.jpg", alt: "Kitten image"),
            '/assets/kitten-44dde42a7808d66c6f314c6587e2c0faabb03d8a3c2c47e4d07e09c08e3c1c6b.jpg' %>

5.1.2 BootstrapとカスタムCSS

1. リスト 5.10を参考にして、5.1.1.1で使ったネコ画像をコメントアウトしてみてください。また、ブラウザのHTMLインスペクタ機能を使って、コメントアウトするとHTMLのソースからも消えていることを確認してみてください。

home.html.erbのkitten.jpgに関する記述をコメントアウトします。

<%#= link_to image_tag("kitten.jpg", alt: "Kitten image"),
            '/assets/kitten-44dde42a7808d66c6f314c6587e2c0faabb03d8a3c2c47e4d07e09c08e3c1c6b.jpg' %>

f:id:v-crn:20190713175424p:plain
HTMLでの記述がなくなりネコ画像が表示されない
ネコ画像が画面とHTMLソースの両方から消えていることを確認できました。

2. リスト 5.11のコードをcustom.scssに追加し、すべての画像を非表示にしてみてください。うまくいけば、Railsのロゴ画像がHomeページから消えるはずです。先ほどと同様にインスペクタ機能を使って、今度はHTMLのソースコードは残ったままで、画像だけが表示されなくなっていることを確認してみてください。

リスト 5.11: すべての画像を非表示にするCSS

img {
  display: none;
}

f:id:v-crn:20190713175509p:plain
HTMLに記述されているがRailsのロゴは表示されない

HTMLのソースコードは残ったままで、Railsのロゴ画像だけが表示されなくなっています。

5.1.3 パーシャル (partial)

1. Railsがデフォルトで生成するheadタグの部分を、リスト 5.18のようにrenderに置き換えてみてください。ヒント: 単純に削除してしまうと後でパーシャルを1から書き直す必要が出てくるので、削除する前にどこかに退避しておきましょう。

リスト5.18のように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 %>
    </div>
    <%= render 'layouts/footer' %>
</body>

</html>

2. リスト 5.18のようなパーシャルはまだ作っていないので、現時点ではテストはredになっているはずです。実際にテストを実行して確認してみましょう。

$ rails t
Running via Spring preloader in process 5524
...

  /0: [] 0% Time: 00:00:00,  ETERROR["test_should_get_about", #<Minitest::Reporters::Suite:0x00007fa0e6bbae20 @name="StaticPagesControllerTest">, 0.14099599997280166]
 test_should_get_about#StaticPagesControllerTest (0.14s)
ActionView::Template::Error:         ActionView::Template::Error: Missing partial layouts/_rails_default with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :coffee, :jbuilder]}. Searched in:
          * "/Users/Username/Documents/Programming/Ruby/Rails/workspace/Rails-Tutorial/sample_app/app/views"
        
            app/views/layouts/application.html.erb:6:in `_app_views_layouts_application_html_erb___2719366376557442984_70164521289380'
            test/controllers/static_pages_controller_test.rb:16:in `block in <class:StaticPagesControllerTest>'

  3/0: [] 0% Time: 00:00:00,  EERROR["test_should_get_home", #<Minitest::Reporters::Suite:0x00007fa0ebb6d648 @name="StaticPagesControllerTest">, 0.359660999965854]
 test_should_get_home#StaticPagesControllerTest (0.36s)
ActionView::Template::Error:         ActionView::Template::Error: Missing partial layouts/_rails_default with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :coffee, :jbuilder]}. Searched in:
          * "/Users/Username/Documents/Programming/Ruby/Rails/workspace/Rails-Tutorial/sample_app/app/views"
        
            app/views/layouts/application.html.erb:6:in `_app_views_layouts_application_html_erb___2719366376557442984_70164521289380'
            test/controllers/static_pages_controller_test.rb:5:in `block in <class:StaticPagesControllerTest>'

  3/1: [] 33% Time: 00:00:00,  ERROR["test_should_get_help", #<Minitest::Reporters::Suite:0x00007fa0ebb9ad00 @name="StaticPagesControllerTest">, 0.37114999996265396]
 test_should_get_help#StaticPagesControllerTest (0.37s)
ActionView::Template::Error:         ActionView::Template::Error: Missing partial layouts/_rails_default with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :coffee, :jbuilder]}. Searched in:
          * "/Users/Username/Documents/Programming/Ruby/Rails/workspace/Rails-Tutorial/sample_app/app/views"
        
            app/views/layouts/application.html.erb:6:in `_app_views_layouts_application_html_erb___2719366376557442984_70164521289380'
            test/controllers/static_pages_controller_test.rb:11:in `block in <class:StaticPagesControllerTest>'

  3/2: [] 66% Time: 00:00:00,    3/3: [] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.37221s
3 tests, 0 assertions, 0 failures, 3 errors, 0 skips

redになることが確認できました。

3. layoutsディレクトリにheadタグ用のパーシャルを作成し、先ほど退避しておいたコードを書き込み、最後にテストが green に戻ることを確認しましょう。

退避しておいたコードを/app/views/layouts/_rails_default.html.erbとして保存します。

<%= csrf_meta_tags %>
<%= csp_meta_tag %>

<%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>

続いてテストを実行します。

$ rails t
...

  3/3: [==============] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.64040s
3 tests, 4 assertions, 0 failures, 0 errors, 0 skips

greenに戻ることが確認できました。

5.2 Sassとアセットパイプライン

5.2.1 アセットパイプライン

演習なし。

5.2.2 素晴らしい構文を備えたスタイルシート

1. 5.2.2で提案したように、footerのCSSを手作業で変換してみましょう。具体的には、リスト 5.17の内容を1つずつ変換していき、リスト 5.20のようにしてみてください。

リスト5.20と同じなので省略。

5.3 レイアウトのリンク

5.3.1 Contactページ

演習なし。

5.3.2 RailsのルートURL

1. 実は名前付きルートは、as:オプションを使って変更することができます。有名なFar Sideの漫画に倣って、Helpページの名前付きルートをhelfに変更してみてください (リスト 5.29)。

リスト5.29と同じなので省略。

2. 先ほどの変更により、テストが redになっていることを確認してください。リスト 5.28を参考にルーティングを更新して、テストを greenにして見てください。

$ rails t
...

ERROR["test_should_get_help", #<Minitest::Reporters::Suite:0x00007ff4edb0d710 @name="StaticPagesControllerTest">, 0.5129970000125468]
 test_should_get_help#StaticPagesControllerTest (0.51s)
NameError:         NameError: undefined local variable or method `help_path' for #<StaticPagesControllerTest:0x00007ff4edee9578>
            test/controllers/static_pages_controller_test.rb:12:in `block in <class:StaticPagesControllerTest>'

  4/4: [==============] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.56098s
4 tests, 6 assertions, 0 failures, 1 errors, 0 skips

redになることが確認できました。次にhelpページに関するテストで扱うパスをhelf_pathに変更します。

...
test "should get help" do
  get helf_path
  assert_response :success
  assert_select "title", "Help | Ruby on Rails Tutorial Sample App"
end
...

テストを実行すると、

$ rails t
...

  4/4: [==============] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.58349s
4 tests, 8 assertions, 0 failures, 0 errors, 0 skips

greenになることが確認できました。

3. エディタのUndo機能を使って、今回の演習で行った変更を元に戻して見てください。

戻します。

5.3.3 名前付きルート

1. リスト 5.29のようにhelfルーティングを作成し、レイアウトのリンクを更新してみてください。

routes.rb

Rails.application.routes.draw do
    root 'static_pages#home'
    get '/help', to: 'static_pages#help', as: 'helf'
    get '/about', to: 'static_pages#about'
    get '/contact', to: 'static_pages#contact'
end

help.html.erb

<% provide(:title, "Helf") %>
<h1>Helf</h1>
<p> Get helf on the Ruby on Rails Tutorial at the
    <a href="https://railstutorial.jp/helf">Rails Tutorial helf page</a>.
    To get helf on this sample app, see the
    <a href="https://railstutorial.jp/#ebook">
        <em>Ruby on Rails Tutorial</em> book</a>.
</p>

2. 前回の演習と同様に、エディタのUndo機能を使ってこの演習で行った変更を元に戻してみてください。

戻します。

5.3.4 リンクのテスト

1. footerパーシャルのabout_pathをcontact_pathに変更してみて、テストが正しくエラーを捕まえてくれるかどうか確認してみてください。

_footer.html.erbのabout_pathをcontact_pathに変更します。

<footer class="footer">
    <small>
        The <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
      by <a href="http://www.michaelhartl.com/">Michael Hartl</a>
  </small>
  <nav>
      <ul>
          <li><%= link_to "About",   contact_path %></li>
          <li><%= link_to "Contact", contact_path %></li>
          <li><a href="http://news.railstutorial.org/">News</a></li>
      </ul>
  </nav>
</footer>

統合テストを実行します。

$ rails test:integration
...

 FAIL["test_layout_links", #<Minitest::Reporters::Suite:0x00007faf629ad120 @name="SiteLayoutTest">, 0.5238879999378696]
 test_layout_links#SiteLayoutTest (0.52s)
        Expected at least 1 element matching "a[href="/about"]", found 0..
        Expected 0 to be >= 1.
        test/integration/site_layout_test.rb:9:in `block in <class:SiteLayoutTest>'

  1/1: [==============] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.52591s
1 tests, 4 assertions, 1 failures, 0 errors, 0 skips

エラーを捕まえてくれました。

2. リスト 5.35で示すように、Applicationヘルパーで使っているfull_titleヘルパーを、test環境でも使えるようにすると便利です。こうしておくと、リスト 5.36のようなコードを使って、正しいタイトルをテストすることができます。ただし、これは完璧なテストではありません。例えばベースタイトルに「Ruby on Rails Tutoial」といった誤字があったとしても、このテストでは発見することができないでしょう。この問題を解決するためには、full_titleヘルパーに対するテストを書く必要があります。そこで、Applicationヘルパーをテストするファイルを作成し、リスト 5.37のFILL_INの部分を適切なコードに置き換えてみてください。ヒント: リスト 5.37ではassert_equal <期待される値>, <実際の値>といった形で使っていましたが、内部では==演算子で期待される値と実際の値を比較し、正しいかどうかのテストをしています。

リスト5.35、リスト5.36を記述した上で、次の内容を持つ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

統合テストを実施するとgreenになることを確認できます。

$ rails test:integration
...

  1/1: [==============] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.53891s
1 tests, 6 assertions, 0 failures, 0 errors, 0 skips

5.4 ユーザー登録: 最初のステップ

5.4.1 Usersコントローラ

1. 表 5.1を参考にしながらリスト 5.41を変更し、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

2. 先ほどの変更を加えたことにより、テストが redになったことを確認してください。なお、この演習はテスト駆動開発 (コラム 3.3) で説明した red/green のリズムを作ることを目的としています。このテストは次の5.4.2で greenになるよう修正します。

$ rails t
...

ERROR["test_should_get_new", #<Minitest::Reporters::Suite:0x00007fa652327990 @name="UsersControllerTest">, 0.5956730000907555]
 test_should_get_new#UsersControllerTest (0.60s)
NameError:         NameError: undefined local variable or method `signup_path' for #<UsersControllerTest:0x00007fa65231c2e8>
            test/controllers/users_controller_test.rb:5:in `block in <class:UsersControllerTest>'

  6/6: [==============] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.66642s
6 tests, 13 assertions, 0 failures, 1 errors, 0 skips

redになることが確認できました。

5.4.2

1. もしまだ5.4.1.1の演習に取り掛かっていなければ、まずはリスト 5.41のように変更し、名前付きルートsignup_pathを使えるようにしてください。また、リスト 5.43で名前付きルートが使えるようになったので、現時点でテストが greenになっていることを確認してください。

$ rails t
...

  6/6: [==============] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.60987s
6 tests, 14 assertions, 0 failures, 0 errors, 0 skips

greenになっていることを確認しました。

2. 先ほどのテストが正しく動いていることを確認するため、signupルートの部分をコメントアウトし、テストredになることを確認してください。確認できたら、コメントアウトを解除してgreenの状態に戻してください。

routes.rbにおいてsignupルートの部分をコメントアウトします。

Rails.application.routes.draw do
    root 'static_pages#home'
    get '/help', to: 'static_pages#help'
    get '/about', to: 'static_pages#about'
    get '/contact', to: 'static_pages#contact'
    # get '/signup', to:'users#new'
end

テストを実行します。

$ rails t
...

ERROR["test_should_get_new", #<Minitest::Reporters::Suite:0x00007fd483473420 @name="UsersControllerTest">, 0.1346680000424385]
 test_should_get_new#UsersControllerTest (0.13s)
NameError:         NameError: undefined local variable or method `signup_path' for #<UsersControllerTest:0x00007fd483632a18>
            test/controllers/users_controller_test.rb:5:in `block in <class:UsersControllerTest>'

ERROR["test_layout_links", #<Minitest::Reporters::Suite:0x00007fd4843036e8 @name="SiteLayoutTest">, 0.25056299997959286]
 test_layout_links#SiteLayoutTest (0.25s)
ActionView::Template::Error:         ActionView::Template::Error: undefined local variable or method `signup_path' for #<#<Class:0x00007fd483629cd8>:0x00007fd487089b30>
            app/views/static_pages/home.html.erb:10:in `_app_views_static_pages_home_html_erb___2879737312358609488_70275357037440'
            test/integration/site_layout_test.rb:5:in `block in <class:SiteLayoutTest>'

ERROR["test_should_get_home", #<Minitest::Reporters::Suite:0x00007fd482c67e20 @name="StaticPagesControllerTest">, 0.5597980000311509]
 test_should_get_home#StaticPagesControllerTest (0.56s)
ActionView::Template::Error:         ActionView::Template::Error: undefined local variable or method `signup_path' for #<#<Class:0x00007fd483629cd8>:0x00007fd489029288>
            app/views/static_pages/home.html.erb:10:in `_app_views_static_pages_home_html_erb___2879737312358609488_70275357037440'
            test/controllers/static_pages_controller_test.rb:6:in `block in <class:StaticPagesControllerTest>'

  6/6: [==============] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.57788s
6 tests, 6 assertions, 0 failures, 3 errors, 0 skips

redになることが確認できました。コメントアウトを解除してgreenの状態に戻しておきます。

3. リスト 5.32の統合テストにsignupページにアクセスするコードを追加してください (getメソッドを使います)。コードを追加したら実際にテストを実行し、結果が正しいことを確認してください。ヒント: リスト 5.36で紹介したfull_titleヘルパーを使ってみてください。

テストでfull_titleヘルパーを使えるようにするため/test/test_helper.rbにApplicationHelperを含める記述をしておきます。

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

class ActiveSupport::TestCase
  # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
  fixtures :all

    # Add more helper methods to be used by all tests here...
    include ApplicationHelper
end

test/integration/site_layout_test.rbにおいてsignup_pathにアクセスできること、さらにそのページに"Sign up"という文字が含まれていることをテストします。

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
        get signup_path
        assert_select "title", full_title("Sign up")
    end
end

テストを実行します。

$ rails t
...

  6/6: [==============] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.60866s
6 tests, 15 assertions, 0 failures, 0 errors, 0 skips

greenになることが確認できました。

5.5 最後に

5.5.1 本章のまとめ

演習なし。