新米パパの育児留学

読者です 読者をやめる 読者になる 読者になる

新米パパの育児留学

『育児留学』とは、育児を通して異なる視点を得たり新しいことに挑戦して自己成長に繋げること。育児奮闘中の新米パパが育児を通して得た気づきや感じたこと、育休中に習得したプログラミングに関する話題を発信していきます。

【第12章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ

Ruby on Rails Tutorial最新版の演習と解答です。

f:id:mochikichi321:20170206212242p:plain

目的

Ruby on Rails チュートリアル 5.0(第4版)を学習中です。

学習を進める中で演習問題の解答がなかった(*1,2)ので、自分なりにまとめていくこととしました。

アウトプットし、自分の理解を深めることを目的としています。 もし、記載内容に誤りがあった場合はコメントいただけると幸いです。

(*1)著者による有償版の解答はあるようです。正式な解答をご希望の方はこちらを参照ください。

Learn Web Development with Rails: Michael Hartl's Ruby on Rails Tutorial | Softcover.io

(*2)旧版に関する解答はありましたが、最新版 5.0(第4版)に関しては検索しても出てきませんでした。

プログラミングを学習し始めたきっかけについてはこちら

mochikichi.hatenablog.com

演習問題と解答

演習12.1.1

演習12.1.1.1

<問題> この時点で、テストスイートが greenになっていることを確認してみましょう。

<解答> 動作確認のみなので省略。

演習12.1.1.2

<問題> 表 12.1の名前付きルートでは、pathではなくurlを使うように記してあります。なぜでしょうか? 考えてみましょう。ヒント: アカウント有効化で行った演習 (11.1.1.1) と同じ理由です。

<解答> 演習 (11.1.1.1) と同じ。

演習12.1.2

演習12.1.2.1

<問題> リスト 12.4のform_forメソッドでは、なぜ@password_resetではなく:password_resetを使っているのでしょうか? 考えてみてください。

<解答> シンボルを使うとよりシンプルなformタグが生成され、オブジェクトを渡すとそのオブジェクトに寄って良しなに出し分けてくれる。newやeditのviewを準備する時に、同じ_form部分テンプレートを利用した時、同じ書き方で出し分けてくれる。 (参考:http://ja.stackoverflow.com/questions/18099/rails%E3%81%AEform-for%E3%81%AB%E3%82%B7%E3%83%B3%E3%83%9C%E3%83%AB%E3%82%92%E4%B8%8E%E3%81%88%E3%82%8B%E3%81%A8%E3%81%8D%E3%81%AF%E3%81%A9%E3%81%AE%E3%82%88%E3%81%86%E3%81%AA%E3%81%A8%E3%81%8D%E3%81%8B

演習12.1.3

演習12.1.3.1

<問題> 試しに有効なメールアドレスをフォームから送信してみましょう (図 12.6)。どんなエラーメッセージが表示されたでしょうか?

<解答> f:id:mochikichi321:20170309084455p:plain

演習12.1.3.2

<問題> コンソールに移り、先ほどの演習課題で送信した結果、(エラーと表示されてはいるものの) 該当するuserオブジェクトにはreset_digestとreset_sent_atがあることを確認してみましょう。また、それぞれの値はどのようになっていますか?

<解答> reset_digestとreset_sent_at共に値は"nil"となっている。

演習12.2.1

演習12.2.1.1

<問題> ブラウザから、送信メールのプレビューをしてみましょう。「Date」の欄にはどんな情報が表示されているでしょうか?

<解答> Dateはブラウザでメールを表示した時点の日時。

演習12.2.1.2

<問題> パスワード再設定フォームから有効なメールアドレスを送信してみましょう。また、Railsサーバーのログを見て、生成された送信メールの内容を確認してみてください。

<解答> 動作確認のみなので省略。

演習12.2.1.3

<問題> コンソールに移り、先ほどの演習課題でパスワード再設定をしたUserオブジェクトを探してください。オブジェクトを見つけたら、そのオブジェクトが持つreset_digestとreset_sent_atの値を確認してみましょう。

<解答>

reset_digest: "$2a$10$ymOyGT8NRw5KEL/EvODcMupxE8/YKvSklgHMT0ZT3a1...", 
reset_sent_at: "2017-02-22 13:33:50"

演習12.2.2

演習12.2.2.1

<問題> メイラーのテストだけを実行してみてください。このテストは greenになっているでしょうか?

<解答> GREEN

演習12.2.2.2

<問題> リスト 12.12にある2つ目のCGI.escapeを削除すると、テストが redになることを確認してみましょう。

<解答> 動作確認のみなので省略。

演習12.3.1

演習12.3.1.1

<問題> 12.2.1.1で示した手順に従って、Railsサーバーのログから送信メールを探し出し、そこに記されているリンクを見つけてください。そのリンクをブラウザから表示してみて、図 12.11のように表示されるか確かめてみましょう。

<解答> 動作確認のみなので省略。

演習12.3.1.2

<問題> 先ほど表示したページから、実際に新しいパスワードを送信してみましょう。どのような結果になるでしょうか?

<解答> f:id:mochikichi321:20170309084532p:plain

演習12.3.2

演習12.3.2.1

<問題> 12.2.1.1で得られたリンク (Railsサーバーのログから取得) をブラウザで表示し、passwordとconfirmationの文字列をわざと間違えて送信してみましょう。どんなエラーメッセージが表示されるでしょうか?

<解答> The form contains 1 error. Password confirmation doesn’t match Password

演習12.3.2.2

<問題> コンソールに移り、パスワード再設定を送信したユーザーオブジェクトを見つけてください。見つかったら、そのオブジェクトのpassword_digestの値を取得してみましょう。次に、パスワード再設定フォームから有効なパスワードを入力し、送信してみましょう (図 12.13)。パスワードの再設定は成功したら、再度password_digestの値を取得し、先ほど取得した値と異なっていることを確認してみましょう。ヒント: 新しい値はuser.reloadを通して取得する必要があります。

<解答> 動作確認のみなので省略。

演習12.3.3

演習12.3.3.1

<問題> リスト 12.6にあるcreate_reset_digestメソッドはupdate_attributeを2回呼び出していますが、これは各行で1回ずつデータベースへ問い合わせしていることになります。リスト 12.20に記したテンプレートを使って、update_attributeの呼び出しを1回のupdate_columns呼び出しにまとめてみましょう (これでデータベースへの問い合わせが1回で済むようになります)。また、変更後にテストを実行し、 greenになることも確認してください。ちなみにリスト 12.20にあるコードには、前章の演習 (リスト 11.39) の解答も含まれています。

<解答>

[user.rb]

(前略)
  # パスワード再設定の属性を設定する
  def create_reset_digest
    self.reset_token = User.new_token
    update_columns(reset_digest:  User.digest(reset_token), reset_sent_at: Time.zone.now)
  end

演習12.3.3.2

<問題> リスト 12.16のテンプレートを埋めて、期限切れのパスワード再設定で発生する分岐 (リスト 12.21) を統合テストで網羅してみましょう (12.21 のコードにあるresponse.bodyは、そのページのHTML本文をすべて返すメソッドです)。 期限切れをテストする方法はいくつかありますが、リスト 12.21でオススメした手法を使えば、レスポンスの本文に「expired」という語があるかどうかでチェックできます (なお、大文字と小文字は区別されません)。

<解答>

[password_resets_test.rb]

(前略)
  test "expired token" do
    get new_password_reset_path
    post password_resets_path,
         params: { password_reset: { email: @user.email } }

    @user = assigns(:user)
    @user.update_attribute(:reset_sent_at, 3.hours.ago)
    patch password_reset_path(@user.reset_token),
          params: { email: @user.email,
                    user: { password:              "foobar",
                            password_confirmation: "foobar" } }
    assert_response :redirect
    follow_redirect!
    assert_match "expired", response.body   <---注目
  end

演習12.3.3.3

<問題> 2時間経ったらパスワードを再設定できなくする方針は、セキュリティ的に好ましいやり方でしょう。しかし、もっと良くする方法はまだあります。例えば、公共の (または共有された) コンピューターでパスワード再設定が行われた場合を考えてみてください。仮にログアウトして離席したとしても、2時間以内であれば、そのコンピューターの履歴からパスワード再設定フォームを表示させ、パスワードを更新してしまうことができてしまいます (しかもそのままログイン機構まで突破されてしまいます!)。この問題を解決するために、リスト 12.22のコードを追加し、パスワードの再設定に成功したらダイジェストをnilになるように変更してみましょう3。

<解答> 動作確認のみなので省略。

演習12.3.3.4

<問題> リスト 12.18に1行追加し、1つ前の演習課題に対するテストを書いてみましょう。ヒント: リスト 9.25のassert_nilメソッドとリスト 11.33のuser.reloadメソッドを組み合わせて、reset_digest属性を直接テストしてみましょう。

<解答>

[password_resets_test.rb]

require 'test_helper'

class PasswordResetsTest < ActionDispatch::IntegrationTest

(中略)

  test "password resets" do

(中略)

    assert_redirected_to user
    assert_nil user.reload['reset_digest']   <---注目
  end

演習12.4

演習12.4.1

<問題> production環境でユーザー登録を試してみましょう。ユーザー登録時に入力したメールアドレスにメールは届きましたか?

<解答> 動作確認のみなので省略。

演習12.4.2

<問題> メールを受信できたら、実際にメールをクリックしてアカウントを有効化してみましょう。また、Heroku上のログを調べてみて、有効化に関するログがどうなっているのか調べてみてください。ヒント: ターミナルからheroku logsコマンドを実行してみましょう。

<解答> 動作確認のみなので省略。

演習12.4.3

<問題> アカウントを有効化できたら、今度はパスワードの再設定を試してみましょう。正しくパスワードの再設定ができたでしょうか?

<解答> 動作確認のみなので省略。

関連記事

【第1章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第2章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第3章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第4章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第5章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第6章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第7章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第8章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第9章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第10章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第11章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第13章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第14章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

Rails 検索機能拡張 (rails tutorial) - 新米パパの育児留学

プログラミング未経験から2か月でエンジニアへ転職したノウハウを知りたい方の相談も受け付けております。

【第11章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ

Ruby on Rails Tutorial最新版の演習と解答です。

f:id:mochikichi321:20170206212242p:plain

目的

Ruby on Rails チュートリアル 5.0(第4版)を学習中です。

学習を進める中で演習問題の解答がなかった(*1,2)ので、自分なりにまとめていくこととしました。

アウトプットし、自分の理解を深めることを目的としています。 もし、記載内容に誤りがあった場合はコメントいただけると幸いです。

(*1)著者による有償版の解答はあるようです。正式な解答をご希望の方はこちらを参照ください。

Learn Web Development with Rails: Michael Hartl's Ruby on Rails Tutorial | Softcover.io

(*2)旧版に関する解答はありましたが、最新版 5.0(第4版)に関しては検索しても出てきませんでした。

プログラミングを学習し始めたきっかけについてはこちら

mochikichi.hatenablog.com

演習問題と解答

演習11.1.1

演習11.1.1.1

<問題> 現時点でテストスイートを実行すると greenになることを確認してみましょう。

<解答> 動作確認のみなので省略。

演習11.1.1.2

<問題> 表 11.2の名前付きルートでは、pathではなくurlを使うように記してあります。なぜでしょうか? 考えてみましょう。ヒント: 私達はこれからメールで名前付きルートを使います。

<解答> メールのURLからアクセスするため。

演習11.1.2

演習11.1.2.1

<問題> 本項での変更を加えた後、テストスイートが green のままになっていることを確認してみましょう。

<解答> 動作確認のみなので省略。

演習11.1.2.2

<問題> コンソールからUserクラスのインスタンスを生成し、そのオブジェクトからcreate_activation_digestメソッドを呼び出そうとすると (Privateメソッドなので) NoMethodErrorが発生することを確認してみましょう。また、そのUserオブジェクトからダイジェストの値も確認してみましょう。

<解答> 動作確認のみなので省略。

演習11.1.2.3

<問題> リスト 6.34で、メールアドレスの小文字化にはemail.downcase!という (代入せずに済む) メソッドがあることを知りました。このメソッドを使って、リスト 11.3のdowncase_emailメソッドを改良してみてください。また、うまく変更できれば、テストスイートは成功したままになっていることも確認してみてください。

<解答>

[user.rb]

class User < ApplicationRecord
  attr_accessor :remember_token, :activation_token
  before_save { email.downcase! }  <---注目
(中略)

  private

    # メールアドレスをすべて小文字にする
    # def downcase_email                 <---不要
    #   self.email = email.downcase
    # end
(中略)
end

テストはGREEN。

演習11.2.1

演習11.2.1.1

<問題> コンソールを開き、CGIモジュールのescapeメソッド (リスト 11.15) でメールアドレスの文字列をエスケープできることを確認してみましょう。このメソッドで"Don’t panic!“をエスケープすると、どんな結果になりますか?

<解答>

>> CGI.escape('Don’t panic!')
=> "Don%E2%80%99t+panic%21"

演習11.2.2

演習11.2.2.1

<問題> Railsプレビュー機能を使って、ブラウザから先ほどのメールを表示してみてください。「Date」の欄にはどんな内容が表示されているでしょうか?

<解答> Dateはブラウザでメールを表示した時点の日時。

演習11.2.3

演習11.2.3.1

<問題> この時点で、テストスイートが greenになっていることを確認してみましょう。

<解答> 動作確認のみなので省略。

演習11.2.3.2

<問題> リスト 11.20で使ったCGI.escapeの部分を削除すると、テストが redに変わることを確認してみましょう。

<解答> 動作確認のみなので省略。

演習11.2.4

演習11.2.4.1

<問題> 新しいユーザーを登録したとき、リダイレクト先が適切なURLに変わったことを確認してみましょう。その後、Railsサーバーのログから送信メールの内容を確認してみてください。有効化トークンの値はどうなっていますか?

<解答>

https://rails-tutorial-mhartl.c9users.io/account_activations/wGEP2viu2WQbS9n36A41Lg/edit?email=2%40gmail.com

演習11.2.4.2

<問題> コンソールを開き、データベース上にユーザーが作成されたことを確認してみましょう。また、このユーザーはデータベース上にはいますが、有効化のステータスがfalseのままになっていることを確認してください。

<解答> 動作確認のみなので省略。

演習11.3.1

演習11.3.1.1

<問題> コンソール内で新しいユーザーを作成してみてください。新しいユーザーの記憶トークンと有効化トークンはどのような値になっているでしょうか? また、各トークンに対応するダイジェストの値はどうなっているでしょうか?

<解答> 動作確認のみなので省略。

演習11.3.1.2

<問題> リスト 11.26で抽象化したauthenticated?メソッドを使って、先ほどの各トークン/ダイジェストの組み合わせで認証が成功することを確認してみましょう。

<解答> 動作確認のみなので省略。

演習11.3.2

演習11.3.2.1

<問題> コンソールから、11.2.4で生成したメールに含まれているURLを調べてみてください。URL内のどこに有効化トークンが含まれているでしょうか?

<解答>

https://rails-tutorial-mhartl.c9users.io/account_activations/wGEP2viu2WQbS9n36A41Lg/edit?email=2%40gmail.com

“wGEP2viu2WQbS9n36A41Lg"が有効化トークン。

演習11.3.2.2

<問題> 先ほど見つけたURLをブラウザに貼り付けて、そのユーザーの認証に成功し、有効化できることを確認してみましょう。また、有効化ステータスがtrueになっていることをコンソールから確認してみてください。

<解答> 動作確認のみなので省略。

演習11.3.3

演習11.3.3.1

<問題> リスト 11.35にあるactivateメソッドはupdate_attributeを2回呼び出していますが、これは各行で1回ずつデータベースへ問い合わせしていることになります。リスト 11.39に記したテンプレートを使って、update_attributeの呼び出しを1回のupdate_columns呼び出しにまとめてみましょう (これでデータベースへの問い合わせが1回で済むようになります)。また、変更後にテストを実行し、 greenになることも確認してください。

<解答>

[user.rb]

class User < ApplicationRecord

(中略)

  # アカウントを有効にする
  def activate
    update_columns(activated: true, activated_at: Time.zone.now)
  end

(後略)

演習11.3.3.2

<問題> 現在は、/usersのユーザーindexページを開くとすべてのユーザーが表示され、/users/:idのようにIDを指定すると個別のユーザーを表示できます。しかし考えてみれば、有効でないユーザーは表示する意味がありません。そこで、リスト 11.40のテンプレートを使って、この動作を変更してみましょう8。なお、ここで使っているActive Recordのwhereメソッドについては、13.3.3でもう少し詳しく説明します。

<解答>

[users_controller.rb]

class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
  before_action :correct_user,   only: [:edit, :update]
  before_action :admin_user,     only: :destroy
  
  def index
    @users = User.where(activated: true).paginate(page: params[:page])
  end

  def show
    @user = User.find(params[:id])
    redirect_to root_url and return unless @user.activated?
  end

(後略)

演習11.4

演習11.4.1

<問題> 実際に本番環境でユーザー登録をしてみましょう。ユーザー登録時に入力したメールアドレスにメールは届きましたか?

<解答> 動作確認のみなので省略。

演習11.4.2

<問題> メールを受信できたら、実際にメールをクリックしてアカウントを有効化してみましょう。また、Heroku上のログを調べてみて、有効化に関するログがどうなっているのか調べてみてください。ヒント: ターミナルからheroku logsコマンドを実行してみましょう。

<解答> 動作確認のみなので省略。

<a href="https://&lt;your heroku app&gt;.herokuapp.com/account_activations/uiXkg8zRNKlluX3pX7-vwg/edit?email=mochikichi%40live.jp">Activate</a>

関連記事

【第1章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第2章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第3章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第4章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第5章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第6章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第7章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第8章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第9章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第10章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第12章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第13章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第14章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

Rails 検索機能拡張 (rails tutorial) - 新米パパの育児留学

プログラミング未経験から2か月でエンジニアへ転職したノウハウを知りたい方の相談も受け付けております。

【第10章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ

Ruby on Rails Tutorial最新版の演習と解答です。

f:id:mochikichi321:20170206212242p:plain

目的

Ruby on Rails チュートリアル 5.0(第4版)を学習中です。

学習を進める中で演習問題の解答がなかった(*1,2)ので、自分なりにまとめていくこととしました。

アウトプットし、自分の理解を深めることを目的としています。 もし、記載内容に誤りがあった場合はコメントいただけると幸いです。

(*1)著者による有償版の解答はあるようです。正式な解答をご希望の方はこちらを参照ください。

Learn Web Development with Rails: Michael Hartl's Ruby on Rails Tutorial | Softcover.io

(*2)旧版に関する解答はありましたが、最新版 5.0(第4版)に関しては検索しても出てきませんでした。

プログラミングを学習し始めたきっかけについてはこちら

mochikichi.hatenablog.com

演習問題と解答

演習10.1.1

演習10.1.1.1

<問題> 先ほど触れたように、target=“_blank"で新しいページを開くときには、セキュリティ上の小さな問題があります。それは、リンク先のサイトがHTMLドキュメントのwindowオブジェクトを扱えてしまう、という点です。具体的には、フィッシング (Phising) サイトのような、悪意のあるコンテンツを導入させられてしまう可能性があります。Gravatarのような著名なサイトではこのような事態は起こらないと思いますが、念のため、このセキュリティ上のリスクも排除しておきましょう。対処方法は、リンク用のaタグのrel (relationship) 属性に、"noopener"と設定するだけです。早速、リスト 10.2で使ったGravatarの編集ページへのリンクにこの設定をしてみましょう。

<解答> 指示通り実行するのみなので省略。

演習10.1.1.2

<問題> リスト 10.5のパーシャルを使って、new.html.erbビュー (リスト 10.6) とedit.html.erbビュー (リスト 10.7) をリファクタリングしてみましょう (コードの重複を取り除いてみましょう)。ヒント: 3.4.3で使ったprovideメソッドを使うと、重複を取り除けます3。(関連するリスト 7.27の演習課題を既に解いている場合、この演習課題をうまく解けない可能性があります。うまく解けない場合は、既存のコードのどこに差異があるのか考えながらこの課題に取り組んでみましょう。例えば筆者であれば、リスト 10.5のテクニックをリスト 10.6に適用してみたり、リスト 10.7のテクニックをリスト 10.5に適用してみたりするでしょう。)

<解答> リスト10.5,10.6,10.7参照。

リスト 7.27の演習課題を既に解いている場合は以下。

[new.html.erb]の以下の",url: signup_path"が[edit.html.erb]と異なるため、provideで指定。

<%= form_for(@user,url: signup_path) do |f| %>

[_form.html.erb]

<%= form_for(@user, url: yield(:url)) do |f| %>   <---注目
  <%= render 'shared/error_messages', object: @user %>

  <%= f.label :name %>
  <%= f.text_field :name, class: 'form-control' %>

  <%= f.label :email %>
  <%= f.email_field :email, class: 'form-control' %>

  <%= f.label :password %>
  <%= f.password_field :password, class: 'form-control' %>

  <%= f.label :password_confirmation %>
  <%= f.password_field :password_confirmation, class: 'form-control' %>

  <%= f.submit yield(:button_text), class: "btn btn-primary" %>
<% end %>

[new.html.erb]

<% provide(:title, 'Sign up') %>
<% provide(:url, signup_path) %>   <---注目
<% provide(:button_text, 'Create my account') %>
<h1>Sign up</h1>
<div class="row">
  <div class="col-md-6 col-md-offset-3">
      <%= render 'form' %>
  </div>
</div>

[edit.html.erb]
<% provide(:title, "Edit user") %>
<% provide(:url, user_path) %>   <---注目
<% provide(:button_text, 'Save changes') %>
<h1>Update your profile</h1>
<div class="row">
  <div class="col-md-6 col-md-offset-3">
      <%= render 'form' %>
    <div class="gravatar_edit">
      <%= gravatar_for @user %>
      <a href="http://gravatar.com/emails" target="_blank" rel="noopener">change</a>
    </div>
  </div>
</div>

演習10.1.2

演習10.1.2.1

<問題> 編集フォームから有効でないユーザー名やメールアドレス、パスワードを使って送信した場合、編集に失敗することを確認してみましょう。

<解答> 動作確認のみなので省略。

演習10.1.3

演習10.1.3.1

<問題> リスト 10.9のテストに1行追加し、正しい数のエラーメッセージが表示されているかテストしてみてましょう。ヒント: 表 5.2で紹介したassert_selectを使ってalertクラスのdivタグを探しだし、「The form contains 4 errors.」というテキストを精査してみましょう。

<解答>

[users_edit_test.rb]

require 'test_helper'

class UsersEditTest < ActionDispatch::IntegrationTest

   (中略)

  test "unsuccessful edit" do
 
   (中略)

    assert_template 'users/edit'
    assert_select "div.alert", "The form contains 4 errors."   <---注目
  end
end

演習10.1.4

演習10.1.4.1

<問題> 実際に編集が成功するかどうか、有効な情報を送信して確かめてみましょう。

<解答> 動作確認のみなので省略。

演習10.1.4.2

<問題> もしGravatarと紐付いていない適当なメールアドレス (foobar@example.comなど) に変更した場合、プロフィール画像はどのように表示されるでしょうか? 実際に編集フォームからメールアドレスを変更して、確認してみてましょう。

<解答> 動作確認のみなので省略。

演習10.2.1

演習10.2.1.1

<問題> デフォルトのbeforeフィルターは、すべてのアクションに対して制限を加えます。今回のケースだと、ログインページやユーザー登録ページにも制限の範囲が及んでしまうはずです (結果としてテストも失敗するはずです)。リスト 10.15のonly:オプションをコメントアウトしてみて、テストスイートがそのエラーを検知できるかどうか (テストが失敗するかどうか) 確かめてみましょう。

<解答> 動作確認のみなので省略。

演習10.2.2

演習10.2.2.1

<問題> 何故editアクションとupdateアクションを両方とも保護する必要があるのでしょうか? 考えてみてください。

<解答> それぞれのパスが異なるため。 edit_user GET /users/:id/edit(.:format) users#edit user PATCH /users/:id(.:format) users#update

演習10.2.2.2

<問題> 上記のアクションのうち、どちらがブラウザで簡単にテストできるアクションでしょうか?

<解答> editアクション。 GETメソッドのeditの方がURLを打ち込んでアクセスするのみなので簡単。

演習10.2.3

演習10.2.3.1

<問題> フレンドリーフォワーディングで、最初に渡されたURLにのみ確実に転送されていることを確認するテストを作成してみましょう。続けて、ログインを行った後、転送先のURLはデフォルト (プロフィール画面) に戻る必要もありますので、これもテストで確認してみてください。ヒント: リスト10.29のsession[:forwarding_url]が正しい値かどうかを確認するテストを追加してみましょう。

<解答>

[users_edit_test.rb]

require 'test_helper'

class UsersEditTest < ActionDispatch::IntegrationTest

(中略)

  test "successful edit with friendly forwarding" do
    get edit_user_path(@user)
    assert session[:forwarding_url]   <---注目
    log_in_as(@user)
    assert_redirected_to edit_user_url(@user) || default   <---注目
    name  = "Foo Bar"
    email = "foo@bar.com"
    patch user_path(@user), params: { user: { name:  name,
                                              email: email,
                                              password:              "",
                                              password_confirmation: "" } }
    assert_not flash.empty?
    assert_redirected_to @user
    @user.reload
    assert_equal name,  @user.name
    assert_equal email, @user.email
  end
end

演習10.2.3.2

<問題> 7.1.3で紹介したdebuggerメソッドをSessionsコントローラのnewアクションに置いてみましょう。その後、ログアウトして /users/1/edit にアクセスしてみてください (デバッガーが途中で処理を止めるはずです)。ここでコンソールに移り、session[:forwarding_url]の値が正しいかどうか確認してみましょう。また、newアクションにアクセスしたときのrequest.get?の値も確認してみましょう (デバッガーを使っていると、ときどき予期せぬ箇所でターミナルが止まったり、おかしい挙動を見せたりします。熟練の開発者になった気になって (コラム 1.1)、落ち着いて対処してみましょう)。

<解答>

(byebug) session[:forwarding_url]
"https://rails-tutorial-******.c9users.io/users/1/edit"

正しくない。ルートが正。

(byebug) request.get?
true

演習10.3.1

演習10.3.1.1

<問題> レイアウトにあるすべてのリンクに対して統合テストを書いてみましょう。ログイン済みユーザーとそうでないユーザーのそれぞれに対して、正しい振る舞いを考えてください。ヒント: log_in_asヘルパーを使ってリスト 5.32にテストを追加してみましょう。

<解答>

[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
    assert_select "a[href=?]", login_path
    get contact_path
    assert_select "title", full_title("Contact")
    get signup_path
    assert_select "title", full_title("Sign up")
  end
  
  def setup
    @user       = users(:michael)
  end
  
  test "layout links when logged in" do
    log_in_as(@user)
    get root_path
    assert_template 'static_pages/home'
    assert_select "a[href=?]", users_path
    assert_select "a[href=?]", user_path(@user)
    assert_select "a[href=?]", edit_user_path(@user)    
    assert_select "a[href=?]", logout_path
  end
end

演習10.3.2

演習10.3.2.1

<問題> 試しに他人の編集ページにアクセスしてみて、10.2.2で実装したようにリダイレクトされるかどうかを確かめてみましょう。

<解答> 動作確認のみなので省略。

演習10.3.3

演習10.3.3.1

<問題> Railsコンソールを開き、pageオプションにnilをセットして実行すると、1ページ目のユーザーが取得できることを確認してみましょう。

<解答>

演習10.3.3.2

<問題> 先ほどの演習課題で取得したpaginationオブジェクトは、何クラスでしょうか? また、User.allのクラスとどこが違うでしょうか? 比較してみてください。

<解答> class: User::ActiveRecord_Relation User.allと同じ。

演習10.3.4

演習10.3.4.1

<問題> 試しにリスト 10.45にあるページネーションのリンク (will_paginateの部分) を2つともコメントアウトしてみて、リスト 10.48のテストが redに変わるかどうか確かめてみましょう。

<解答> 動作確認のみなので省略。

演習10.3.4.2

<問題> 先ほどは2つともコメントアウトしましたが、1つだけコメントアウトした場合、テストが greenのままであることを確認してみましょう。will_paginateのリンクが2つとも存在していることをテストしたい場合は、どのようなテストを追加すれば良いでしょうか? ヒント: 表 5.2を参考にして、数をカウントするテストを追加してみましょう。

<解答>

[users_index_test.rb]

require 'test_helper'

class UsersIndexTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "index including pagination" do
    log_in_as(@user)
    get users_path
    assert_template 'users/index'
    assert_select 'div.pagination', count:2  <---注目
    User.paginate(page: 1).each do |user|
      assert_select 'a[href=?]', user_path(user), text: user.name
    end
  end
end

演習10.3.5

演習10.3.5.1

<問題> リスト 10.52にあるrenderの行をコメントアウトし、テストの結果が redに変わることを確認してみましょう。

<解答> 動作確認のみなので省略。

演習10.4.1

演習10.4.1.1

<問題> Web経由でadmin属性を変更できないことを確認してみましょう。具体的には、リスト 10.56に示したように、PATCHを直接ユーザーのURL (/users/:id) に送信するテストを作成してみてください。テストが正しい振る舞いをしているかどうか確信を得るために、まずはadminをuser_paramsメソッド内の許可されたパラメータ一覧に追加するところから始めてみましょう。最初のテストの結果は redになるはずです。

<解答>

[users_controller_test.rb]

require 'test_helper'

class UsersControllerTest < ActionDispatch::IntegrationTest

(中略)

  test "should not allow the admin attribute to be edited via the web" do
    log_in_as(@other_user)
    assert_not @other_user.admin?
    patch user_path(@other_user), params: {
                                    user: { password:              @other_user.password,
                                            password_confirmation: @other_user.password,
                                            admin: true } }
    assert_not @other_user.reload.admin?
  end
end

演習10.4.2

演習10.4.2.1

<問題> 管理者ユーザーとしてログインし、試しにサンプルユーザを2〜3人削除してみましょう。ユーザーを削除すると、Railsサーバーのログにはどのような情報が表示されるでしょうか?

<解答> 動作確認のみなので省略。

演習10.4.3

演習10.4.3.1

<問題> 試しにリスト 10.59にある管理者ユーザーのbeforeフィルターをコメントアウトしてみて、テストの結果が redに変わることを確認してみましょう

<解答> 動作確認のみなので省略。

関連記事

【第1章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第2章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第3章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第4章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第5章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第6章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第7章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第8章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第9章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第11章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第12章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第13章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第14章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

Rails 検索機能拡張 (rails tutorial) - 新米パパの育児留学

プログラミング未経験から2か月でエンジニアへ転職したノウハウを知りたい方の相談も受け付けております。

【第9章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ

Ruby on Rails Tutorial最新版の演習と解答です。

f:id:mochikichi321:20170206212242p:plain

目的

Ruby on Rails チュートリアル 5.0(第4版)を学習中です。

学習を進める中で演習問題の解答がなかった(*1,2)ので、自分なりにまとめていくこととしました。

アウトプットし、自分の理解を深めることを目的としています。 もし、記載内容に誤りがあった場合はコメントいただけると幸いです。

(*1)著者による有償版の解答はあるようです。正式な解答をご希望の方はこちらを参照ください。

Learn Web Development with Rails: Michael Hartl's Ruby on Rails Tutorial | Softcover.io

(*2)旧版に関する解答はありましたが、最新版 5.0(第4版)に関しては検索しても出てきませんでした。

プログラミングを学習し始めたきっかけについてはこちら

mochikichi.hatenablog.com

演習問題と解答

演習9.1.1

演習9.1.1.1

<問題> コンソールを開き、データベースにある最初のユーザーを変数userに代入してください。その後、そのuserオブジェクトからrememberメソッドがうまく動くかどうか確認してみましょう。また、remember_tokenとremember_digestの違いも確認してみてください。

<解答>

>> user=User.first
  User Load (0.3ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2017-02-09 07:39:54", updated_at: "2017-02-09 07:39:54", password_digest: "$2a$10$NCGVBX5axnKjyHVDxfUo5eco71WXZlmOX/9/gfBb2Gm...", remember_digest: nil>

>> user.remember
   (0.1ms)  begin transaction
  SQL (0.6ms)  UPDATE "users" SET "updated_at" = ?, "remember_digest" = ? WHERE "users"."id" = ?  [["updated_at", 2017-02-12 05:54:01 UTC], ["remember_digest", "$2a$10$DZBeoa8QGU5fxq9.stitr.SIH7uSUlLliaunThO2yWeZevK59.fge"], ["id", 1]]
   (10.2ms)  commit transaction
=> true

>> user.remember_token
=> "9jkAE-aydjad4QFuCj7TCQ"

>> user.remember_digest
=> "$2a$10$DZBeoa8QGU5fxq9.stitr.SIH7uSUlLliaunThO2yWeZevK59.fge"

演習9.1.1.1

<問題> リスト 9.3では、明示的にUserクラスを呼び出すことで、新しいトークンやダイジェスト用のクラスメソッドを定義しました。実際、User.new_tokenやUser.digestを使って呼び出せるようになったので、おそらく最も明確なクラスメソッドの定義方法であると言えるでしょう。 しかし実は、より「Ruby的に正しい」クラスメソッドの定義方法が2通りあります。1つはややわかりにくく、もう1つは非常に混乱するでしょう。テストスイートを実行して、リスト 9.4 (ややわかりにくい) や、リスト 9.5 (非常に混乱する) の実装でも、正しく動くことを確認してみてください。ヒント: selfは、通常の文脈ではUser「モデル」、つまりユーザーオブジェクトのインスタンスを指しますが、リスト 9.4やリスト 9.5の文脈では、selfはUser「クラス」を指すことにご注意ください。 わかりにくさの原因の一部はこの点にあります。

<解答> 動作確認のみなので省略。

演習9.1.2

演習9.1.2.1

<問題> ブラウザのcookieを調べ、ログイン後のブラウザではremember_tokenと暗号化されたuser_idがあることを確認してみましょう。

<解答> 動作確認のみなので省略。

演習9.1.2.2

<問題> コンソールを開き、リスト 9.6のauthenticated?メソッドがうまく動くかどうか確かめてみましょう。

<解答> 動作確認のみなので省略。

演習9.1.3

演習9.1.3.1

<問題> ログアウトした後に、ブラウザの対応するcookiesが削除されていることを確認してみましょう。

<解答> 動作確認のみなので省略。

演習9.1.4

演習9.1.4.1

<問題> リスト 9.16で修正した行をコメントアウトし、2つのログイン済みのタブによるバグを実際に確かめてみましょう。まず片方のタブでログアウトし、その後、もう1つのタブで再度ログアウトを試してみてください。

<解答> 動作確認のみなので省略。

演習9.1.4.2

<問題> リスト 9.19で修正した行をコメントアウトし、2つのログイン済みのブラウザによるバグを実際に確かめてみましょう。まず片方のブラウザでログアウトし、もう一方のブラウザを再起動してサンプルアプリケーションにアクセスしてみてください。

<解答> 動作確認のみなので省略。

演習9.1.4.3

<問題> 上のコードでコメントアウトした部分を元に戻し、テストスイートが red から greenになることを確認しましょう。

<解答> 動作確認のみなので省略。

演習9.2

演習9.2.1

<問題> ブラウザでcookies情報を調べ、[remember me] をチェックしたときに意図した結果になっているかどうかを確認してみましょう。

<解答> 動作確認のみなので省略。

演習9.2.2

<問題> コンソールを開き、三項演算子を使った実例を考えてみてください (コラム 9.2)。

<解答> 省略。

演習9.3.1

演習9.3.1.1

<問題> リスト 9.25の統合テストでは、仮想のremember_token属性にアクセスできないと説明しましたが、実は、assignsという特殊なテストメソッドを使うとアクセスできるようになります。コントローラで定義したインスタンス変数にテストの内部からアクセスするには、テスト内部でassignsメソッドを使用します。このメソッドにはインスタンス変数に対応するシンボルを渡します。 たとえば、createアクションで@userというインスタンス変数が定義されていれば、テスト内部ではassigns(:user)と書くことでインスタンス変数にアクセスできます。 本チュートリアルのアプリケーションの場合、Sessionsコントローラのcreateアクションでは、userを (インスタンス変数ではない) 通常のローカル変数として定義しましたが、これをインスタンス変数に変えてしまえば、cookiesにユーザーの記憶トークンが正しく含まれているかどうかをテストできるようになります。 このアイディアに従ってリスト 9.27とリスト 9.28の不足分を埋め (ヒントとして?やFILL_INを目印に置いてあります)、[remember me] チェックボックスのテストを改良してみてください。

<解答>

class SessionsController < ApplicationController

(中略)

  def create
    @user = User.find_by(email: params[:session][:email].downcase)
    if @user && @user.authenticate(params[:session][:password])
      log_in @user
      params[:session][:remember_me] == '1' ? remember(@user) : forget(@user)
      redirect_to @user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end

(中略)

end

[users_login_test.rb]


(前略)

  test "login with remembering" do
    log_in_as(@user, remember_me: '1')
    assert_equal cookies['remember_token'], assigns(:user).remember_token
  end

(後略)

演習9.3.2

演習9.3.2.1

<問題> リスト 9.33にあるauthenticated?の式を削除すると、リスト 9.31の2つ目のテストで失敗することを確かめてみましょう (このテストが正しい対象をテストしていることを確認してみましょう)。

<解答> 動作確認のみなので省略。

関連記事

【第1章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第2章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第3章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第4章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第5章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第6章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第7章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第8章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第10章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第11章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第12章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第13章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第14章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

Rails 検索機能拡張 (rails tutorial) - 新米パパの育児留学

プログラミング未経験から2か月でエンジニアへ転職したノウハウを知りたい方の相談も受け付けております。

【第8章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ

Ruby on Rails Tutorial最新版の演習と解答です。

f:id:mochikichi321:20170206212242p:plain

目的

Ruby on Rails チュートリアル 5.0(第4版)を学習中です。

学習を進める中で演習問題の解答がなかった(*1,2)ので、自分なりにまとめていくこととしました。

アウトプットし、自分の理解を深めることを目的としています。 もし、記載内容に誤りがあった場合はコメントいただけると幸いです。

(*1)著者による有償版の解答はあるようです。正式な解答をご希望の方はこちらを参照ください。

Learn Web Development with Rails: Michael Hartl's Ruby on Rails Tutorial | Softcover.io

(*2)旧版に関する解答はありましたが、最新版 5.0(第4版)に関しては検索しても出てきませんでした。

プログラミングを学習し始めたきっかけについてはこちら

mochikichi.hatenablog.com

演習問題と解答

演習8.1.1

演習8.1.1.1

<問題> GET login_pathとPOST login_pathとの違いを説明できますか? 少し考えてみましょう。

<解答> リスト 8.2より、

  get    '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'

GET login_path: “/login"へアクセスされた際に"session#new"アクションを実行

POST login_path: “session#create"アクションの情報を”/login"へ送信

以下の「コラム 3.2. GETやその他のHTTPメソッドについて」を参照。

https://railstutorial.jp/chapters/static_pages?version=5.0#aside-get_etc

演習8.1.1.2

<問題> ターミナルのパイプ機能を使ってrails routesの実行結果とgrepコマンドを繋ぐことで、Usersリソースに関するルーティングだけを表示させることができます。同様にして、Sessionsリソースに関する結果だけを表示させてみましょう。現在、いくつのSessionsリソースがあるでしょうか? ヒント: パイプやgrepの使い方が分からない場合は Learn Enough Command Line to Be Dangerousの Section on Grep (英語) を参考にしてみてください。

<解答>

$ rails routes | grep users#
   signup GET    /signup(.:format)         users#new
    users GET    /users(.:format)          users#index
          POST   /users(.:format)          users#create
 new_user GET    /users/new(.:format)      users#new
edit_user GET    /users/:id/edit(.:format) users#edit
     user GET    /users/:id(.:format)      users#show
          PATCH  /users/:id(.:format)      users#update
          PUT    /users/:id(.:format)      users#update
          DELETE /users/:id(.:format)      users#destroy

$ rails routes | grep sessions#
    login GET    /login(.:format)          sessions#new
          POST   /login(.:format)          sessions#create
   logout DELETE /logout(.:format)         sessions#destroy

Sessionsリソースは「3つ」です。

参考:

パイプ「|」を使って 複数のコマンドを組み合わせる:http://webkaru.net/linux/commands-pipeline/

grepコマンド:https://hydrocul.github.io/wiki/commands/grep.html

演習8.1.2

演習8.1.2.1

<問題> リスト 8.4で定義したフォームで送信すると、Sessionsコントローラのcreateアクションに到達します。Railsはこれをどうやって実現しているでしょうか? 考えてみてください。ヒント:表 8.1とリスト 8.5の1行目に注目してください。

<解答> リスト8.5の1行目の以下部分で、

action="/login" method="post"

表8.1の以下のcreateアクションに到達することがわかる。 [POST /login login_path create 新しいセッションの作成 (ログイン)]

演習8.1.3

演習8.1.3.1

<問題> Railsコンソールを使って、表 8.2のそれぞれの式が合っているか確かめてみましょう. まずはuser = nilの場合を、次にuser = User.firstとした場合を確かめてみてください。ヒント: 必ず論理値オブジェクトとなるように、4.2.3で紹介した!!のテクニックを使ってみましょう。例: !!(user && user.authenticate(’foobar’))

<解答> [(nil && [オブジェクト]) == false]の確認。

>> user=nil
=> nil

>> !!(user && user.authenticate(’foobar’))
=> false

[(true && false) == false]の確認。(正しいパスワードは[123456])

>> user=User.first
  User Load (1.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2017-02-09 07:39:54", updated_at: "2017-02-09 07:39:54", password_digest: "$2a$10$NCGVBX5axnKjyHVDxfUo5eco71WXZlmOX/9/gfBb2Gm...">

>> !!(user && user.authenticate('111111'))
=> false

[(true && true) == true]の確認。(正しいパスワードは[123456])

>> !!(user && user.authenticate('123456'))
=> true

演習8.1.5

演習8.1.5.1

<問題> 8.1.4の処理の流れが正しく動いているかどうか、ブラウザで確認してみてください。特に、flashがうまく機能しているかどうか、フラッシュメッセージの表示後に違うページに移動することを忘れないでください。

<解答> 動作確認のみなので省略。

演習8.2.1

演習8.2.1.1

<問題> 有効なユーザーで実際にログインし、ブラウザからcookiesの情報を調べてみてください。このとき、sessionの値はどうなっているでしょうか? ヒント: ブラウザでcookiesを調べる方法が分からない? 今こそググってみるときです! (コラム 1.1)

<解答> f:id:mochikichi321:20170225202336p:plain

演習8.2.1.2

<問題> 先ほどの演習課題と同様に、Expiresの値について調べてみてください。

<解答> 有効期限:ブラウザセッションの終了時(演習8.2.1.1の画像参照)

演習8.2.2

演習8.2.2.1

<問題> Railsコンソールを使って、User.find_by(id: …)で対応するユーザーが検索に引っかからなかったとき、nilを返すことを確認してみましょう。

<解答>

idは1のみ存在する状態で以下を実行。id:1はtrue,id:2はnil

>> User.find_by(id:1)
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2017-02-09 07:39:54", updated_at: "2017-02-09 07:39:54", password_digest: "$2a$10$NCGVBX5axnKjyHVDxfUo5eco71WXZlmOX/9/gfBb2Gm...">

>> User.find_by(id:2)
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
=> nil

演習8.2.2.2

<問題> 先ほどと同様に、今度は:user_idキーを持つsessionハッシュを作成してみましょう。リスト 8.17に記したステップにしたがって、||=演算子がうまく動くことも確認してみましょう。

<解答>

>> session = {}
=> {}
>> session[:user_id] = nil
=> nil
>> @current_user ||= User.find_by(id: session[:user_id])
=> nil
>> session[:user_id]= User.first.id
=> 1
>> @current_user ||= User.find_by(id: session[:user_id])
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2017-02-09 07:39:54", updated_at: "2017-02-09 07:39:54", password_digest: "$2a$10$NCGVBX5axnKjyHVDxfUo5eco71WXZlmOX/9/gfBb2Gm...">
>> @current_user ||= User.find_by(id: session[:user_id])
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2017-02-09 07:39:54", updated_at: "2017-02-09 07:39:54", password_digest: "$2a$10$NCGVBX5axnKjyHVDxfUo5eco71WXZlmOX/9/gfBb2Gm...">

演習8.2.3

演習8.2.3.1

<問題> ブラウザのcookieインスペクタ機能を使って (8.2.1.1)、セッション用のcookieを削除してみてください。ヘッダー部分にあるリンクは非ログイン状態のものになっているでしょうか? 確認してみましょう。

<解答> 動作確認のみなので省略。

演習8.2.3.2

<問題> もう一度ログインしてみて、ヘッダーのレイアウトが変わったことを確認してみましょう。その後、ブラウザを再起動させ、再び非ログイン状態に戻ったことも確認してみてください。注意: もしブラウザの [閉じたときの状態に戻す] 機能をオンにしていると、セッション情報も復元される可能性があります。もしその機能をオンにしている場合、忘れずにオフにしておきましょう (コラム 1.1)。

<解答> 動作確認のみなので省略。

演習8.2.4

演習8.2.4.1

<問題> 試しにSessionヘルパーのlogged_in?メソッドから!を削除してみて、リスト 8.23が redになることを確認してみましょう。

<解答> 動作確認のみなので省略。

演習8.2.4.1

<問題> 先ほど削除した部分 (!) を元に戻して、テストが greenに戻ることを確認してみましょう。

<解答> 動作確認のみなので省略。

演習8.2.5

演習8.2.5.1

<問題> リスト 8.25のlog_inの行をコメントアウトすると、テストスイートは red になるでしょうか? それとも green になるでしょうか? 確認してみましょう。

<解答> RED

演習8.2.5.2

<問題> 現在使用しているテキストエディタの機能を使って、リスト 8.25をまとめてコメントアウトできないか調べてみましょう。また、コメントアウトの前後でテストスイートを実行し、コメントアウトすると red に、コメントアウトを元に戻すと green になることを確認してみましょう。ヒント: コメントアウト後にファイルを保存することを忘れないようにしましょう。また、テキストエディタコメントアウト機能については Test Editor Tutorial の Commenting Out (英語) などを参照してみてください。

<解答> 動作確認のみなので省略。

演習8.3

演習8.3.1

<問題> ブラウザから [Log out] リンクをクリックし、どんな変化が起こるか確認してみましょう。また、リスト 8.31で定義した3つのステップを実行してみて、うまく動いているかどうか確認してみましょう。

<解答> 動作確認のみなので省略。

演習8.3.2

<問題> cookiesの内容を調べてみて、ログアウト後にはsessionが正常に削除されていることを確認してみましょう。

<解答> 動作確認のみなので省略。

関連記事

【第1章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第2章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第3章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第4章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第5章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第6章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第7章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第9章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第10章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第11章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第12章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第13章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第14章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

Rails 検索機能拡張 (rails tutorial) - 新米パパの育児留学

プログラミング未経験から2か月でエンジニアへ転職したノウハウを知りたい方の相談も受け付けております。

【第7章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ

Ruby on Rails Tutorial最新版の演習と解答です。

f:id:mochikichi321:20170206212242p:plain

目的

Ruby on Rails チュートリアル 5.0(第4版)を学習中です。

学習を進める中で演習問題の解答がなかった(*1,2)ので、自分なりにまとめていくこととしました。

アウトプットし、自分の理解を深めることを目的としています。 もし、記載内容に誤りがあった場合はコメントいただけると幸いです。

(*1)著者による有償版の解答はあるようです。正式な解答をご希望の方はこちらを参照ください。

Learn Web Development with Rails: Michael Hartl's Ruby on Rails Tutorial | Softcover.io

(*2)旧版に関する解答はありましたが、最新版 5.0(第4版)に関しては検索しても出てきませんでした。

プログラミングを学習し始めたきっかけについてはこちら

mochikichi.hatenablog.com

演習問題と解答

演習7.1.1

演習7.1.1.1

<問題> ブラウザから /about にアクセスし、デバッグ情報が表示されていることを確認してください。このページを表示するとき、どのコントローラとアクションが使われていたでしょうか? paramsの内容から確認してみましょう。

<解答> 以下のデバッグ情報が表示されます。 “static_pages"コントローラーの"about"アクションが実行された。

--- !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
  controller: static_pages
  action: about
permitted: false

演習7.1.1.2

<問題> Railsコンソールを開き、データベースから最初のユーザー情報を取得し、変数userに格納してください。その後、puts user.attributes.to_yamlを実行すると何が表示されますか? ここで表示された結果と、yメソッドを使ったy user.attributesの実行結果を比較してみましょう。

<解答> 実行結果は以下の通り。 “puts user.attributes.to_yaml"の実行結果と"y user.attributes"の実行結果は同じです。

>> user=User.first
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, name: "Yamada", email: "mhartl@example.com", created_at: "2017-02-05 02:01:37", updated_at: "2017-02-05 02:36:54", password_digest: "$2a$10$E.gAC.d0Ch5dvmrM6vnNhu6gIpNtwFD8JhHmKPyc/V3...">

>> puts user.attributes.to_yaml
---
id: 1
name: Yamada
email: mhartl@example.com
created_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &1 2017-02-05 02:01:37.355601000 Z
  zone: &2 !ruby/object:ActiveSupport::TimeZone
    name: Etc/UTC
  time: *1
updated_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &3 2017-02-05 02:36:54.834356000 Z
  zone: *2
  time: *3
password_digest: "$2a$10$E.gAC.d0Ch5dvmrM6vnNhu6gIpNtwFD8JhHmKPyc/V3u1di9B67dO"
=> nil

>> y user.attributes
---
id: 1
name: Yamada
email: mhartl@example.com
created_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &1 2017-02-05 02:01:37.355601000 Z
  zone: &2 !ruby/object:ActiveSupport::TimeZone
    name: Etc/UTC
  time: *1
updated_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &3 2017-02-05 02:36:54.834356000 Z
  zone: *2
  time: *3
password_digest: "$2a$10$E.gAC.d0Ch5dvmrM6vnNhu6gIpNtwFD8JhHmKPyc/V3u1di9B67dO"
=> nil

演習7.1.2

演習7.1.2.1

<問題> 埋め込みRubyを使って、マジックカラム (created_atとupdated_at) の値をshowページに表示してみましょう (リスト 7.4)。

<解答>

<%= @user.name %>, <%= @user.email %>, <%= @user.created_at %>, <%= @user.updated_at %>

f:id:mochikichi321:20170219201416p:plain

演習7.1.2.2

<問題> 埋め込みRubyを使って、Time.nowの結果をshowページに表示してみましょう。ページを更新すると、その結果はどう変わっていますか? 確認してみてください。

<解答>

<%= @user.name %>, <%= @user.email %>, <%= @user.created_at %>, <%= @user.updated_at %>,
<%= Time.now %>

f:id:mochikichi321:20170219201506p:plain

更新すると表示時間も更新されます。

演習7.1.3

演習7.1.3.1

<問題> showアクションの中にdebuggerを差し込み (リスト 7.6)、ブラウザから /users/1 にアクセスしてみましょう。その後コンソールに移り、putsメソッドを使ってparamsハッシュの中身をYAML形式で表示してみましょう。ヒント: 7.1.1.1の演習を参考にしてください。その演習ではdebugメソッドで表示したデバッグ情報を、どのようにしてYAML形式で表示していたでしょうか?

<解答>

(byebug) a=params
<ActionController::Parameters {"controller"=>"users", "action"=>"show", "id"=>"1"} permitted: false>

(byebug) puts a.to_yaml
--- !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
  controller: users
  action: show
  id: '1'
permitted: false
nil

7.1.1.1の演習に関して、 debugヘルパーは、YAMLフォーマットを使ったオブジェクトを描画した\<pre>タグを返します。

演習7.1.3.2

<問題> newアクションの中にdebuggerを差し込み、/users/new にアクセスしてみましょう。@userの内容はどのようになっているでしょうか? 確認してみてください。

<解答>

(byebug) @user
nil

演習7.1.4

演習7.1.4.1

<問題> (任意) Gravatar上にアカウントを作成し、あなたのメールアドレスと適当な画像を紐付けてみてください。メールアドレスをMD5ハッシュ化して、紐付けた画像がちゃんと表示されるかどうか試してみましょう。

<解答> 省略

演習7.1.4.2

<問題> 7.1.4で定義したgravatar_forヘルパーをリスト 7.12のように変更して、sizeをオプション引数として受け取れるようにしてみましょう。うまく変更できると、gravatar_for user, size: 50といった呼び出し方ができるようになります。重要: この改善したヘルパーは10.3.1で実際に使います。忘れずに実装しておきましょう。

<解答> リスト7.12参照。

演習7.1.4.3

<問題> オプション引数は今でもRubyコミュニティで一般的に使われていますが、Ruby 2.0から導入された新機能「キーワード引数 (Keyword Arguments)」でも実現することができます。先ほど変更したリスト 7.12を、リスト 7.13のように置き換えてもうまく動くことを確認してみましょう。この2つの実装方法はどういった違いがあるのでしょうか? 考えてみてください。

<解答> 動作確認のみなので省略。 オプション引数よりもキーワード引数の方が簡潔に実装できる。

演習7.2.1

演習7.2.1.1

<問題> 試しに、リスト 7.15にある:nameを:nomeに置き換えてみましょう。どんなエラーメッセージが表示されるようになりますか?

<解答> undefined method `nome'

演習7.2.1.2

<問題> 試しに、ブロックの変数fをすべてfoobarに置き換えてみて、結果が変わらないことを確認してみてください。確かに結果は変わりませんが、変数名をfoobarとするのはあまり良い変更ではなさそうですね。その理由について考えてみてください。

<解答> 変数fは “form” のfを使用している。 “f.label"等がHTMLフォーム要素に対応することがわかるようにする。 "foobar"はメタ構文変数(意味を持たない名前)として認識されるため適さない。

演習7.2.2

演習7.2.2.1

<問題> Learn Enough HTML to Be DangerousではHTMLをすべて手動で書き起こしていますが、なぜformタグを使わなかったのでしょうか? 理由を考えてみてください。

<解答> formタグは、入力・送信フォームを作成するために使用される。 本文中では、入力・送信が必要ないので使用していない。 (ログインページにはformタグが使用されています。)

演習7.3.2

演習7.3.2.1

<問題> /users/new?admin=1 にアクセスし、paramsの中にadmin属性が含まれていることをデバッグ情報から確認してみましょう。

<解答>

--- !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
  admin: '1'
  controller: users
  action: new
permitted: false

演習7.3.3

演習7.3.3.1

<問題> 最小文字数を5に変更すると、エラーメッセージも自動的に更新されることを確かめてみましょう。

<解答>

[user.rb]


class User < ApplicationRecord
(中略)
  validates :password, presence: true, length: { minimum: 5 }  
end

f:id:mochikichi321:20170219201509p:plain

演習7.3.3.2

<問題> 未送信のユーザー登録フォーム (図 7.12) のURLと、送信済みのユーザー登録フォーム (図 7.18) のURLを比べてみましょう。なぜURLは違っているのでしょうか? 考えてみてください。

<解答> 未送信のユーザー登録フォーム (図 7.12) のURLは以下。 “・・・/signup”

ルーティングの以下が実行されている。

[routes.rb]

(前略)
  get '/signup',    to: 'users#new'
(後略)

送信済みのユーザー登録フォーム (図 7.18) のURLは以下。 “・・・/users”

ルーティングの以下が実行されている。

[routes.rb]

(前略)
  resources :users
(後略)

これは、表7.1のルートを指し、その中の"ユーザーを作成するアクション"が実行されている。

  post '/users'  'users#create'

演習7.3.4

演習7.3.4.1

<問題> リスト 7.20で実装したエラーメッセージに対するテストを書いてみてください。どのくらい細かくテストするかはお任せします。リスト 7.25にテンプレートを用意しておいたので、参考にしてください。

<解答>

[users_signup_test.rb]

require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest

  test "invalid signup information" do
    get signup_path

(中略)

    assert_template 'users/new'
    assert_select 'div#error_explanation'
    assert_select 'div.alert'   
  end
end

演習7.3.4.2

<問題> 未送信のユーザー登録フォームと送信直後のURLは、それぞれ /signup と /users になり、URLが異なっています。これは、リスト 5.43で追加した名前付きルートと、デフォルトのRESTfulなルーティング (リスト 7.3) を設定したことによって生じた差異です。リスト 7.26とリスト 7.27の内容を追加し、この問題を解決してみてください。うまくいけば、いずれのURLも /signup となるはずです。あれ、でもテストは greenのままになっていますね…、なぜでしょうか? (考えてみてください)

<解答> リスト7.26とリスト7.27の内容を追加すると、いずれのURLも /signup となる。 GREENのままとなっている理由は演習7.3.4.3及び演習7.3.4.4参照。

演習7.3.4.3

<問題> リスト 7.25のpost部分を変更して、上の演習課題で作られた新しいURLに合わせてみましょう。また、テストが依然として greenのままになっている点も確認してください。

<解答>

[users_signup_test.rb]

require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest

  test "invalid signup information" do
    get signup_path
    assert_no_difference 'User.count' do
      post signup_path, params: { user: { name:  "",
(後略)

テストはGREEN。

演習7.3.4.4

<問題> リスト 7.27のフォームを以前の状態 (リスト 7.20) に戻してみて、テストがやはり greenになっていることを確認してください。これは問題です! なぜなら、現在postが送信されているURLは正しくないのですから。assert_selectを使ったテストをリスト 7.25に追加し、このバグを検知できるようにしてみましょう (テストを追加して redになれば成功です)。その後、変更後のフォーム (リスト 7.27) に戻してみて、テストが green になることを確認してみましょう。ヒント: フォームから送信してテストするのではなく、’form[action=“/signup”]’という部分が存在するかどうかに着目してテストしてみましょう。

<解答> リスト 7.27のフォームを以前の状態 (リスト 7.20) に戻してもテストはGREEN。

テストを以下の通りとするとテストはRED。

[users_signup_test.rb]

require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest

  test "invalid signup information" do

(中略)

    assert_select 'form[action="/signup"]'
  end
end

リスト7.27に戻してテストするとGREEN。

演習7.4.1

演習7.4.1.1

<問題> 有効な情報を送信し、ユーザーが実際に作成されたことを、Railsコンソールを使って確認してみましょう。

<解答> 以下のユーザー情報を送信

f:id:mochikichi321:20170219201516p:plain

二人目のユーザー情報が作成されました。(表示のさせ方はいろいろあります。)

>> User.second
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? OFFSET ?  [["LIMIT", 1], ["OFFSET", 1]]
=> #<User id: 2, name: "Yamada", email: "yamada@mail.com", created_at: "2017-02-09 06:56:06", updated_at: "2017-02-09 06:56:06", password_digest: "$2a$10$0JljS52iRoEmJQ5uhNbnVeDc9/cAuYWtKrMmour10cG...">

演習7.4.1.2

<問題> リスト 7.28を更新し、redirect_to user_url(@user)とredirect_to @userが同じ結果になることを確認してみましょう。

<解答> 動作確認のみなので省略。

演習7.4.2

演習7.4.2.1

<問題> コンソールに移り、文字列内の式展開 (4.2.2) でシンボルを呼び出してみましょう。例えば"#{:success}“といったコードを実行すると、どんな値が返ってきますか? 確認してみてください。

<解答>

>> "#{:success}"
=> "success"

演習7.4.2.2

<問題> 先ほどの演習で試した結果を参考に、リスト 7.30のflashはどのような結果になるか考えてみてください。

<解答>

>> flash = { success: "It worked!", danger: "It failed." }
=> {:success=>"It worked!", :danger=>"It failed."}

>> "#{flash[:success]}"
=> "It worked!"

>> "#{flash[:danger]}"
=> "It failed."

演習7.4.3

演習7.4.3.1

<問題> Railsコンソールを使って、新しいユーザーが本当に作成されたのかもう一度チェックしてみましょう。結果は、リスト 7.32のようになるはずです。

<解答> 動作確認のみなので省略。

演習7.4.3.2

<問題> 自分のメールアドレスでユーザー登録を試してみましょう。既にGravatarに登録している場合、適切な画像が表示されているか確認してみてください。

<解答> 動作確認のみなので省略。

演習7.4.4

演習7.4.4.1

<問題> 7.4.2で実装したflashに対するテストを書いてみてください。どのくらい細かくテストするかはお任せします。リスト 7.34に最小限のテンプレートを用意しておいたので、参考にしてください (FILL_INの部分を適切なコードに置き換えると完成します)。ちなみに、テキストに対するテストは壊れやすいです。文量の少ないflashのキーであっても、それは同じです。筆者の場合、flashが空でないかをテストするだけの場合が多いです。

<解答>

[users_signup_test.rb]

require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest

(中略)
  
  test "valid signup information" do
(中略)
    assert_not flash.empty?    
  end  
end

演習7.4.4.2

<問題> 本文中でも指摘しましたが、flash用のHTML (リスト 7.31) は読みにくいです。より読みやすくしたリスト 7.35のコードに変更してみましょう。変更が終わったらテストスイートを実行し、正常に動作することを確認してください。なお、このコードでは、Railsのcontent_tagというヘルパーを使っています。

<解答> 動作確認のみなので省略。

演習7.4.4.3

<問題> リスト 7.28のリダイレクトの行をコメントアウトすると、テストが失敗することを確認してみましょう。

<解答> 動作確認のみなので省略。

演習7.4.4.4

<問題> リスト 7.28で、@user.saveの部分をfalseに置き換えたとしましょう (バグを埋め込んでしまったと仮定してください)。このとき、assert_differenceのテストではどのようにしてこのバグを検知するでしょうか? テストコードを追って考えてみてください。

<解答> 以下のテストでは、assert_differenceブロック内の処理を実行する直前と、実行した直後のUser.countの値を比較し、1増えた際にtrueとなる。@user.saveが実行されないことによって、User.countは増えないためerrorとなる。

assert_difference 'User.count', 1 do
  post users_path, ...
end

演習7.5.3

演習7.5.3.1

<問題> ブラウザから本番環境 (Heroku) にアクセスし、SSLの鍵マークがかかっているか、URLがhttpsになっているかどうかを確認してみましょう。

<解答> 動作確認のみなので省略。

演習7.5.3.1

<問題> 本番環境でユーザーを作成してみましょう。Gravatarの画像は正しく表示されているでしょうか?

<解答> 動作確認のみなので省略。

関連記事

【第1章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第2章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第3章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第4章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第5章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第6章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第8章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第9章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第10章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第11章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第12章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第13章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第14章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

Rails 検索機能拡張 (rails tutorial) - 新米パパの育児留学

プログラミング未経験から2か月でエンジニアへ転職したノウハウを知りたい方の相談も受け付けております。

【第6章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ

Ruby on Rails Tutorial最新版の演習と解答です。

f:id:mochikichi321:20170206212242p:plain

目的

Ruby on Rails チュートリアル 5.0(第4版)を学習中です。

学習を進める中で演習問題の解答がなかった(*1,2)ので、自分なりにまとめていくこととしました。

アウトプットし、自分の理解を深めることを目的としています。 もし、記載内容に誤りがあった場合はコメントいただけると幸いです。

(*1)著者による有償版の解答はあるようです。正式な解答をご希望の方はこちらを参照ください。

Learn Web Development with Rails: Michael Hartl's Ruby on Rails Tutorial | Softcover.io

(*2)旧版に関する解答はありましたが、最新版 5.0(第4版)に関しては検索しても出てきませんでした。

プログラミングを学習し始めたきっかけについてはこちら

mochikichi.hatenablog.com

演習問題と解答

演習6.1.1

演習6.1.1.1

<問題> Railsはdb/ディレクトリの中にあるschema.rbというファイルを使っています。これはデータベースの構造 (スキーマ (schema) と呼びます) を追跡するために使われます。さて、あなたの環境にあるdb/schema.rbの内容を調べ、その内容とマイグレーションファイル (リスト 6.2) の内容を比べてみてください。

<解答>

[schema.rb]

ActiveRecord::Schema.define(version: 20170203234505) do

  create_table "users", force: :cascade do |t|
    t.string   "name"
    t.string   "email"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

end

[20170203234505_create_users.rb]

class CreateUsers < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      t.string :name
      t.string :email

      t.timestamps
    end
  end
end

異なる点は以下の2つ。

①「created_at,updated_at」と「t.timestamps」 ⇒本文中に「t.timestampsは特別なコマンドで、created_atとupdated_atという2つの「マジックカラム (Magic Columns)」を作成します。」とあるので、実質構成は同じ。

②「create_table」に対して「force: :cascade」の有無 ⇒「force: :cascade」:外部キーが適切であればスキーマが再読み込みできるようになります。(参照元https://railsguides.jp/4_2_release_notes.html

ないとどうなるのかは現時点ではわからない。。。

演習6.1.1.2

<問題> ほぼすべてのマイグレーションは、元に戻すことが可能です (少なくとも本チュートリアルにおいてはすべてのマイグレーションを元に戻すことができます)。元に戻すことを「ロールバック (rollback)と呼び、Railsではdb:rollbackというコマンドで実現できます。

  $ rails db:rollback

上のコマンドを実行後、db/schema.rbの内容を調べてみて、ロールバックが成功したかどうか確認してみてください (コラム 3.1ではマイグレーションに関する他のテクニックもまとめているので、参考にしてみてください)。 上のコマンドでは、データベースからusersテーブルを削除するためにdrop_tableコマンドを内部で呼び出しています。 これがうまくいくのは、drop_tableとcreate_tableがそれぞれ対応していることをchangeメソッドが知っているからです。この対応関係を知っているため、ロールバック用の逆方向のマイグレーションを簡単に実現することができるのです。なお、あるカラムを削除するような不可逆なマイグレーションの場合は、changeメソッドの代わりに、upとdownのメソッドを別々に定義する必要があります。 詳細については、Railsガイドの「Active Record マイグレーション」を参照してください。

<解答> 指示通り以下を実行

  $ rails db:rollback

[schema.rb]

ActiveRecord::Schema.define(version: 0) do

end

ロールバック完了。

演習6.1.1.3

<問題> もう一度rails db:migrateコマンドを実行し、db/schema.rbの内容が元に戻ったことを確認してください。

<解答> 動作確認のみなので省略。

演習6.1.2

演習6.1.2.1

<問題> Railsコンソールを開き、User.newでUserクラスのオブジェクトが生成されること、そしてそのオブジェクトがApplicationRecordを継承していることを確認してみてください (ヒント: 4.4.4で紹介したテクニックを使ってみてください)。

<解答>

>> User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
>> User.superclass
=> ApplicationRecord(abstract)

演習6.1.2.2

<問題> 同様にして、ApplicationRecordがActiveRecord::Baseを継承していることについて確認してみてください。

<解答>

>> ApplicationRecord.superclass
=> ActiveRecord::Base

演習6.1.3

演習6.1.3.1

<問題> user.nameとuser.emailが、どちらもStringクラスのインスタンスであることを確認してみてください。

<解答>

>> user.name.class
=> String

>> user.email.class
=> String

演習6.1.3.2

<問題> created_atとupdated_atは、どのクラスのインスタンスでしょうか?

<解答>

>> user.created_at.class
=> ActiveSupport::TimeWithZone

>> user.updated_at.class
=> ActiveSupport::TimeWithZone

演習6.1.4

演習6.1.4.1

<問題> nameを使ってユーザーオブジェクトを検索してみてください。また、 find_by_nameメソッドが使えることも確認してみてください (古いRailsアプリケーションでは、古いタイプのfind_byをよく見かけることでしょう)。

<解答>

>> User.find_by(name: "Michael Hartl")
  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."name" = ? LIMIT ?  [["name", "Michael Hartl"], ["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2017-02-04 05:46:02", updated_at: "2017-02-04 05:46:02">

>> User.find_by_name("Michael Hartl")                                                                            
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."name" = ? LIMIT ?  [["name", "Michael Hartl"], ["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2017-02-04 05:46:02", updated_at: "2017-02-04 05:46:02">

演習6.1.4.2

<問題> 実用的な目的のため、User.allはまるで配列のように扱うことができますが、実際には配列ではありません。 User.allで生成されるオブジェクトを調べ、ArrayクラスではなくUser::ActiveRecord_Relationクラスであることを確認してみてください。

<解答>

>> User.all.class
=> User::ActiveRecord_Relation

演習6.1.4.3

<問題> User.allに対してlengthメソッドを呼び出すと、その長さを求められることを確認してみてください (4.2.3)。

<解答>

>> User.all.length
  User Load (0.3ms)  SELECT "users".* FROM "users"
=> 2

演習6.1.5

演習6.1.5.1

<問題> userオブジェクトへの代入を使ってname属性を使って更新し、saveで保存してみてください。

<解答>

>> user.name="Taro Yamada"
=> "Taro Yamada"
>> user.save
   (0.2ms)  SAVEPOINT active_record_1
  SQL (1.1ms)  UPDATE "users" SET "updated_at" = ?, "name" = ? WHERE "users"."id" = ?  [["updated_at", 2017-02-04 06:34:26 UTC], ["name", "Taro Yamada"], ["id", 1]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> true

演習6.1.5.2

<問題> 今度はupdate_attributes(*)を使って、email属性を更新および保存してみてください。

<解答>

>> user.update_attribute(:email,"yamada@mail.com")
   (0.1ms)  SAVEPOINT active_record_1
  SQL (0.1ms)  UPDATE "users" SET "email" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["email", "yamada@mail.com"], ["updated_at", 2017-02-04 06:37:31 UTC], ["id", 1]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> true

>> user.email
=> "yamada@mail.com"

*問題では「update_attributes」となっていますが、最後の「s」は不要です。

演習6.1.5.3

<問題> 同様にして、マジックカラムであるcreated_atも直接更新できることを確認してみてください。ヒント: 更新するときは「1.year.ago」を使うと便利です。これはRails流の時間指定の1つで、現在の時刻から1年前の時間を算出してくれます。

<解答>

>> user.update_attribute(:created_at,1.year.ago)
   (0.1ms)  SAVEPOINT active_record_1
  SQL (0.2ms)  UPDATE "users" SET "created_at" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["created_at", 2016-02-04 06:43:54 UTC], ["updated_at", 2017-02-04 06:42:00 UTC], ["id", 1]]
   (0.0ms)  RELEASE SAVEPOINT active_record_1
=> true

>> user.created_at
=> Thu, 04 Feb 2016 06:43:54 UTC +00:00

演習6.2.1

演習6.2.1.1

<問題> コンソールから、新しく生成したuserオブジェクトが有効 (valid) であることを確認してみましょう。

<解答>

>> user=User.new(name:"Taro Yamada")                                                                              
=> #<User id: nil, name: "Taro Yamada", email: nil, created_at: nil, updated_at: nil>

>> user.valid?
=> true

演習6.2.1.2

<問題> 6.1.3で生成したuserオブジェクトも有効であるかどうか、確認してみましょう。

<解答>

>> User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>

>> user = User.new(name: "Michael Hartl", email: "mhartl@example.com")
=> #<User id: nil, name: "Michael Hartl", email: "mhartl@example.com", created_at: nil, updated_at: nil>

>> user.valid?
=> true

演習6.2.2

演習6.2.2.1

<問題> 新しいユーザーuを作成し、作成した時点では有効ではない (invalid) ことを確認してください。なぜ有効ではないのでしょうか? エラーメッセージを確認してみましょう。

<解答>

>> u=User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>

>> u.valid?
=> false

>> u.errors.full_messages
=> ["Name can't be blank", "Email can't be blank"]

演習6.2.2.2

<問題> u.errors.messagesを実行すると、ハッシュ形式でエラーが取得できることを確認してください。emailに関するエラー情報だけを取得したい場合、どうやって取得すれば良いでしょうか?

<解答>

>> u.errors.messages
=> {:name=>["can't be blank"], :email=>["can't be blank"]}

>> u.errors.messages[:email]
=> ["can't be blank"]

演習6.2.3

演習6.2.3.1

<問題> 長すぎるnameとemail属性を持ったuserオブジェクトを生成し、有効でないことを確認してみましょう。

<解答>

>> user=User.new(name:"123456789012345678901234567890123456789012345678901",email:"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890@mail.com")
=> #<User id: nil, name: "12345678901234567890123456789012345678901234567890...", email: "12345678901234567890123456789012345678901234567890...", created_at: nil, updated_at: nil>

>> user.valid?
=> false

演習6.2.3.2

<問題> 長さに関するバリデーションが失敗した時、どんなエラーメッセージが生成されるでしょうか? 確認してみてください。

<解答>

>> user.errors.full_messages
=> ["Name is [f:id:mochikichi321:20170218200721p:plain][f:id:mochikichi321:20170218200723p:plain]too long (maximum is 50 characters)", "Email is too long (maximum is 255 characters)"]

演習6.2.4

演習6.2.4.1

<問題> リスト 6.18にある有効なメールアドレスのリストと、リスト 6.19にある無効なメールアドレスのリストをRubularのYour test string:に転記してみてください。その後、リスト 6.21の正規表現をYour regular expression:に転記して、有効なメールアドレスのみがすべてマッチし、無効なメールアドレスはすべてマッチしないことを確認してみましょう。

<解答> f:id:mochikichi321:20170218200721p:plain

演習6.2.4.2

<問題> 先ほど触れたように、リスト 6.21のメールアドレスチェックする正規表現は、foo@bar..comのようにドットが連続した無効なメールアドレスを許容してしまいます。まずは、このメールアドレスをリスト 6.19の無効なメールアドレスリストに追加し、これによってテストが失敗することを確認してください。次に、リスト 6.23で示した、少し複雑な正規表現を使ってこのテストがパスすることを確認してください。 <解答> “foo@bar..com"を追加。

[user_test.rb]

require 'test_helper'

class UserTest < ActiveSupport::TestCase
  def setup
    @user = User.new(name: "Example User", email: "user@example.com")
  end

(中略)

  test "email validation should reject invalid addresses" do
    invalid_addresses = %w[user@example,com user_at_foo.org user.name@example.
                           foo@bar_baz.com foo@bar+baz.com foo@bar..com]
    invalid_addresses.each do |invalid_address|
      @user.email = invalid_address
      assert_not @user.valid?, "#{invalid_address.inspect} should be invalid"
    end
  end 
end

テスト結果はRED。 リスト 6.23のとおり修正してテストすると、GREEN。

演習6.2.4.3

<問題> foo@bar..comをRubularのメールアドレスのリストに追加し、リスト 6.23の正規表現をRubularで使ってみてください。有効なメールアドレスのみがすべてマッチし、無効なメールアドレスはすべてマッチしないことを確認してみましょう。

<解答>

f:id:mochikichi321:20170218200723p:plain

演習6.2.5

演習6.2.5.1

<問題> リスト 6.33を参考に、メールアドレスを小文字にするテストをリスト 6.32に追加してみましょう。ちなみに追加するテストコードでは、データベースの値に合わせて更新するreloadメソッドと、値が一致しているかどうか確認するassert_equalメソッドを使っています。リスト 6.33のテストがうまく動いているか確認するためにも、before_saveの行をコメントアウトして redになることを、また、コメントアウトを解除すると greenになることを確認してみましょう。

<解答> 動作確認のみなので省略。

演習6.2.5.2

<問題> テストスイートの実行結果を確認しながら、before_saveコールバックをemail.downcase!に書き換えてみましょう。ヒント: メソッドの末尾に!を付け足すことにより、email属性を直接変更できるようになります (リスト 6.34)。

<解答> リスト6.34参照。

演習6.3.2

演習6.3.2.1

<問題> この時点では、userオブジェクトに有効な名前とメールアドレスを与えても、valid?で失敗してしまうことを確認してみてください。

<解答>

>> user=User.new(name:"Taro Yamada",email:"yamada@mail.com")
=> #<User id: nil, name: "Taro Yamada", email: "yamada@mail.com", created_at: nil, updated_at: nil, password_digest: nil>

>> user.valid?
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "yamada@mail.com"], ["LIMIT", 1]]
=> false

演習6.3.2.2

<問題> なぜ失敗してしまうのでしょうか? エラーメッセージを確認してみてください。

<解答>

>> user.errors.full_messages
=> ["Password can't be blank"]

演習6.3.3

演習6.3.3.1

<問題> 有効な名前とメールアドレスでも、パスワードが短すぎるとuserオブジェクトが有効にならないことを確認してみましょう。

<解答>

>> user=User.new(name:"Taro Yamada",email:"yamada@mail.com",password:"12345")
=> #<User id: nil, name: "Taro Yamada", email: "yamada@mail.com", created_at: nil, updated_at: nil, password_digest: "$2a$10$W699oKChtVu6Hd2D4uDSsOb9KI6i0HiDcNvobt7xhfW...">

>> user.valid?
  User Exists (0.3ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "yamada@mail.com"], ["LIMIT", 1]]
=> false

演習6.3.3.2

<問題> 上で失敗した時、どんなエラーメッセージになるでしょうか? 確認してみましょう。

<解答>

>> user.errors.full_messages
=> ["Password is too short (minimum is 6 characters)"]

演習6.3.4

演習6.3.4.1

<問題> コンソールを一度再起動して (userオブジェクトを消去して)、このセクションで作ったuserオブジェクトを検索してみてください。

<解答>

>> user = User.find_by(email: "mhartl@example.com")
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "mhartl@example.com"], ["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2017-02-05 02:01:37", updated_at: "2017-02-05 02:01:37", password_digest: "$2a$10$E.gAC.d0Ch5dvmrM6vnNhu6gIpNtwFD8JhHmKPyc/V3...">

演習6.3.4.2

<問題> オブジェクトが検索できたら、名前を新しい文字列に置き換え、saveメソッドで更新してみてください。うまくいきませんね…、なぜうまくいかなかったのでしょうか?

<解答>

>> user.name="Yamada"
=> "Yamada"

>> user.save
   (0.2ms)  begin transaction
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) AND ("users"."id" != ?) LIMIT ?  [["email", "mhartl@example.com"], ["id", 1], ["LIMIT", 1]]
   (0.1ms)  rollback transaction
=> false

name属性を変更し、.saveを実行すると、変更していない箇所も全て保存を要求される。 すなわち、パスワードをレコードに保存することを要求するため、エラーが生じる。 変更及び保存をname属性のみに限定する必要がある。(6.3.4.3参照)

演習6.3.4.3

<問題> 今度は6.1.5で紹介したテクニックを使って、userの名前を更新してみてください。

<解答>

>> user.update_attribute(:name, "Yamada")
   (0.1ms)  begin transaction
  SQL (0.4ms)  UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["name", "Yamada"], ["updated_at", 2017-02-05 02:36:54 UTC], ["id", 1]]
   (10.0ms)  commit transaction
=> true

“updated_at"が更新されており、保存がうまくいったことがわかる。

関連記事

【第1章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第2章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第3章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第4章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第5章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第7章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第8章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第9章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第10章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第11章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第12章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第13章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

【第14章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ - 新米パパの育児留学

Rails 検索機能拡張 (rails tutorial) - 新米パパの育児留学

プログラミング未経験から2か月でエンジニアへ転職したノウハウを知りたい方の相談も受け付けております。