新米パパの育児留学

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

新米パパの育児留学

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

【第14章】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

演習問題と解答

演習14.1.1

演習14.1.1.1

<問題> 図 14.7のid=1のユーザーに対してuser.following.map(&:id)を実行すると、結果はどのようになるでしょうか? 想像してみてください。ヒント: 4.3.2で紹介したmap(&:method_name)のパターンを思い出してください。例えばuser.following.map(&:id)の場合、idの配列を返します。

<解答> [2,7,8,10]

演習14.1.1.2

<問題> 図 14.7を参考にして、id=2のユーザーに対してuser.followingを実行すると、結果はどのようになるでしょうか? また、同じユーザーに対してuser.following.map(&:id)を実行すると、結果はどのようになるでしょうか? 想像してみてください。

<解答> user.following [user_id:1, name:Michael Hartl, email:mhartl@example.com] user.following.map(&:id) [1]

演習14.1.2

演習14.1.2.1

<問題> コンソールを開き、表 14.1のcreateメソッドを使ってActiveRelationshipを作ってみましょう。データベース上に2人以上のユーザーを用意し、最初のユーザーが2人目のユーザーをフォローしている状態を作ってみてください。

<解答>

>> user=User.first
  User Load (1.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2017-02-23 23:09:36", updated_at: "2017-02-23 23:09:36", password_digest: "$2a$10$JZHMQYjhJjqvbfW8QGWJ6.UEWjfuhbi1r8GL3/apYKv...", remember_digest: nil, admin: true, activation_digest: "$2a$10$ozX2hLov2jSJ6FpCku7vHO5Ys9EUjY1ZAGzcKQcw938...", activated: true, activated_at: "2017-02-23 23:09:36", reset_digest: nil, reset_sent_at: nil>

>> other_user=User.second
  User Load (0.3ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? OFFSET ?  [["LIMIT", 1], ["OFFSET", 1]]
=> #<User id: 2, name: "Jamey Windler MD", email: "example-1@railstutorial.org", created_at: "2017-02-23 23:09:37", updated_at: "2017-02-23 23:09:37", password_digest: "$2a$10$HbJ9qu3236xnHHulFCbmweSa3bThCR5XIG2MX2z8yry...", remember_digest: nil, admin: false, activation_digest: "$2a$10$fLt.yeQ3C2hABfXORioyPOHd0X.zNBGyFjIDuRAhqra...", activated: true, activated_at: "2017-02-23 23:09:37", reset_digest: nil, reset_sent_at: nil>

>> user.active_relationships.create(followed_id: other_user.id)                                                                             
   (0.1ms)  SAVEPOINT active_record_1
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  SQL (0.5ms)  INSERT INTO "relationships" ("follower_id", "followed_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["follower_id", 1], ["followed_id", 2], ["created_at", 2017-02-24 12:29:46 UTC], ["updated_at", 2017-02-24 12:29:46 UTC]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> #<Relationship id: 1, follower_id: 1, followed_id: 2, created_at: "2017-02-24 12:29:46", updated_at: "2017-02-24 12:29:46">

演習14.1.2.2

<問題> 先ほどの演習を終えたら、active_relationship.followedの値とactive_relationship.followerの値を確認し、それぞれの値が正しいことを確認してみましょう。

<解答> 演習14.1.2.1参照。

演習14.1.3

演習14.1.3.1

<問題> リスト 14.5のバリデーションをコメントアウトしても、テストが成功したままになっていることを確認してみましょう。(以前のRailsのバージョンでは、このバリデーションが必須でしたが、Rails 5から必須ではなくなりました。今回はフォロー機能の実装を優先しますが、この手のバリデーションが省略されている可能性があることを頭の片隅で覚えておくと良いでしょう。)

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

演習14.1.4

演習14.1.4.1

<問題> コンソールを開き、リスト 14.9のコードを順々に実行してみましょう。

<解答>

>> michael=User.first
  User Load (1.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2017-02-23 23:09:36", updated_at: "2017-02-23 23:09:36", password_digest: "$2a$10$JZHMQYjhJjqvbfW8QGWJ6.UEWjfuhbi1r8GL3/apYKv...", remember_digest: nil, admin: true, activation_digest: "$2a$10$ozX2hLov2jSJ6FpCku7vHO5Ys9EUjY1ZAGzcKQcw938...", activated: true, activated_at: "2017-02-23 23:09:36", reset_digest: nil, reset_sent_at: nil>

>> archer=User.second
  User Load (0.3ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? OFFSET ?  [["LIMIT", 1], ["OFFSET", 1]]
=> #<User id: 2, name: "Jamey Windler MD", email: "example-1@railstutorial.org", created_at: "2017-02-23 23:09:37", updated_at: "2017-02-23 23:09:37", password_digest: "$2a$10$HbJ9qu3236xnHHulFCbmweSa3bThCR5XIG2MX2z8yry...", remember_digest: nil, admin: false, activation_digest: "$2a$10$fLt.yeQ3C2hABfXORioyPOHd0X.zNBGyFjIDuRAhqra...", activated: true, activated_at: "2017-02-23 23:09:37", reset_digest: nil, reset_sent_at: nil>

>> michael.following?(archer)
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? AND "users"."id" = ? LIMIT ?  [["follower_id", 1], ["id", 2], ["LIMIT", 1]]
=> false

>> michael.follow(archer)
   (0.1ms)  SAVEPOINT active_record_1
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  SQL (0.5ms)  INSERT INTO "relationships" ("follower_id", "followed_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["follower_id", 1], ["followed_id", 2], ["created_at", 2017-02-24 12:58:52 UTC], ["updated_at", 2017-02-24 12:58:52 UTC]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> #<Relationship id: 1, follower_id: 1, followed_id: 2, created_at: "2017-02-24 12:58:52", updated_at: "2017-02-24 12:58:52">

>> michael.following?(archer)
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? AND "users"."id" = ? LIMIT ?  [["follower_id", 1], ["id", 2], ["LIMIT", 1]]
=> true

>> michael.unfollow(archer)
  Relationship Load (0.3ms)  SELECT  "relationships".* FROM "relationships" WHERE "relationships"."follower_id" = ? AND "relationships"."followed_id" = ? LIMIT ?  [["follower_id", 1], ["followed_id", 2], ["LIMIT", 1]]
   (0.1ms)  SAVEPOINT active_record_1
  SQL (0.4ms)  DELETE FROM "relationships" WHERE "relationships"."id" = ?  [["id", 1]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> #<Relationship id: 1, follower_id: 1, followed_id: 2, created_at: "2017-02-24 12:58:52", updated_at: "2017-02-24 12:58:52">

>> michael.following?(archer)
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? AND "users"."id" = ? LIMIT ?  [["follower_id", 1], ["id", 2], ["LIMIT", 1]]
=> false

演習14.1.4.2

<問題> 先ほどの演習の各コマンド実行時の結果を見返してみて、実際にはどんなSQLが出力されたのか確認してみましょう。

<解答>

 SQL (0.4ms)  DELETE FROM "relationships" WHERE "relationships"."id" = ?

演習14.1.5

演習14.1.5.1

<問題> コンソールを開き、何人かのユーザーが最初のユーザーをフォローしている状況を作ってみてください。最初のユーザーをuserとすると、user.followers.map(&:id)の値はどのようになっているでしょうか?

<解答>

>> user=User.first
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2017-02-23 23:09:36", updated_at: "2017-02-23 23:09:36", password_digest: "$2a$10$JZHMQYjhJjqvbfW8QGWJ6.UEWjfuhbi1r8GL3/apYKv...", remember_digest: nil, admin: true, activation_digest: "$2a$10$ozX2hLov2jSJ6FpCku7vHO5Ys9EUjY1ZAGzcKQcw938...", activated: true, activated_at: "2017-02-23 23:09:36", reset_digest: nil, reset_sent_at: nil>

>> user2=User.second
  User Load (0.3ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? OFFSET ?  [["LIMIT", 1], ["OFFSET", 1]]
=> #<User id: 2, name: "Jamey Windler MD", email: "example-1@railstutorial.org", created_at: "2017-02-23 23:09:37", updated_at: "2017-02-23 23:09:37", password_digest: "$2a$10$HbJ9qu3236xnHHulFCbmweSa3bThCR5XIG2MX2z8yry...", remember_digest: nil, admin: false, activation_digest: "$2a$10$fLt.yeQ3C2hABfXORioyPOHd0X.zNBGyFjIDuRAhqra...", activated: true, activated_at: "2017-02-23 23:09:37", reset_digest: nil, reset_sent_at: nil>

>> user3=User.third
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? OFFSET ?  [["LIMIT", 1], ["OFFSET", 2]]
=> #<User id: 3, name: "Micaela Crona", email: "example-2@railstutorial.org", created_at: "2017-02-23 23:09:37", updated_at: "2017-02-23 23:09:37", password_digest: "$2a$10$ng2IzsKTNkOxB5zteVxszuNRdr8YB56vvfLn5aCKNvd...", remember_digest: nil, admin: false, activation_digest: "$2a$10$FYKD7pHxhchKv662YYqIAuvQpuY1HRDHOsoHWtQrt1D...", activated: true, activated_at: "2017-02-23 23:09:37", reset_digest: nil, reset_sent_at: nil>

>> user2.active_relationships.create(followed_id: 1)                                                                                        
   (0.1ms)  SAVEPOINT active_record_1
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  SQL (0.4ms)  INSERT INTO "relationships" ("follower_id", "followed_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["follower_id", 2], ["followed_id", 1], ["created_at", 2017-02-24 13:12:55 UTC], ["updated_at", 2017-02-24 13:12:55 UTC]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> #<Relationship id: 1, follower_id: 2, followed_id: 1, created_at: "2017-02-24 13:12:55", updated_at: "2017-02-24 13:12:55">

>> user3.active_relationships.create(followed_id: 1)                                                                                        
   (0.1ms)  SAVEPOINT active_record_1
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  SQL (0.3ms)  INSERT INTO "relationships" ("follower_id", "followed_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["follower_id", 3], ["followed_id", 1], ["created_at", 2017-02-24 13:13:08 UTC], ["updated_at", 2017-02-24 13:13:08 UTC]]
   (0.0ms)  RELEASE SAVEPOINT active_record_1
=> #<Relationship id: 2, follower_id: 3, followed_id: 1, created_at: "2017-02-24 13:13:08", updated_at: "2017-02-24 13:13:08">

>> user.followers.map(&:id)
  User Load (0.3ms)  SELECT "users".* FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = ?  [["followed_id", 1]]
=> [2, 3]

演習14.1.5.2

<問題> 上の演習が終わったら、user.followers.countの実行結果が、先ほどフォローさせたユーザー数と一致していることを確認してみましょう。

<解答>

>> user.followers.count
   (0.2ms)  SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = ?  [["followed_id", 1]]
=> 2

演習14.1.5.3

<問題> user.followers.countを実行した結果、出力されるSQL文はどのような内容になっているでしょうか? また、user.followers.to_a.countの実行結果と違っている箇所はありますか? ヒント: もしuserに100万人のフォロワーがいた場合、どのような違いがあるでしょうか? 考えてみてください。

<解答>

>> user.followers.count
   (0.2ms)  SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = ?  [["followed_id", 1]]
=> 2

>> user.followers.to_a.count
=> 2

演習14.2.1

演習14.2.1.1

<問題> コンソールを開き、User.first.followers.countの結果がリスト 14.14で期待している結果と合致していることを確認してみましょう。

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

演習14.2.1.2

<問題> 先ほどの演習と同様に、User.first.following.countの結果も合致していることを確認してみましょう。

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

演習14.2.2

演習14.2.2.1

<問題> ブラウザから /users/2 にアクセスし、フォローボタンが表示されていることを確認してみましょう。同様に、/users/5 では [Unfollow] ボタンが表示されているはずです。さて、/users/1 にアクセスすると、どのような結果が表示されるでしょうか?

<解答> /users/1では、follow/unfollowボタンは表示されない。

演習14.2.2.2

<問題> ブラウザからHomeページとプロフィールページを表示してみて、統計情報が正しく表示されているか確認してみましょう。

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

演習14.2.3

演習14.2.3.1

<問題> ブラウザから /users/1/followers と /users/1/following を開き、それぞれが適切に表示されていることを確認してみましょう。サイドバーにある画像は、リンクとしてうまく機能しているでしょうか?

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

演習14.2.3.2

<問題> リスト 14.29のassert_selectに関連するコードをコメントアウトしてみて、テストが正しく red に変わることを確認してみましょう。

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

演習14.2.4

演習14.2.4.1

<問題> ブラウザ上から /users/2 を開き、[Follow] と [Unfollow] を実行してみましょう。うまく機能しているでしょうか?

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

演習14.2.4.2

<問題> 先ほどの演習を終えたら、Railsサーバーのログを見てみましょう。フォロー/フォロー解除が実行されると、それぞれどのテンプレートが描画されているでしょうか?

<解答> 両方とも、"users/show.html.erb"

演習14.2.5

演習14.2.5.1

<問題> ブラウザから /users/2 にアクセスし、うまく動いているかどうか確認してみましょう。

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

演習14.2.5.2

<問題> 先ほどの演習で確認が終わったら、Railsサーバーのログを閲覧し、フォロー/フォロー解除を実行した直後のテンプレートがどうなっているか確認してみましょう。

<解答> フォロー :relationships/create.js.erb フォロー解除 :relationships/destroy.js.erb

演習14.2.6

演習14.2.6.1

<問題> リスト 14.36のrespond_toブロック内の各行を順にコメントアウトしていき、テストが正しくエラーを検知できるかどうか確認してみましょう。実際、どのテストケースが落ちたでしょうか?

<解答> createアクションの"format.html { redirect_to @user }“をコメントアウトした場合、 "should_follow_a_user_the_standard_way"がエラー。

createアクションの"format.html { redirect_to @user }“,"format.js"をコメントアウトした場合、 "should_follow_a_user_the_standard_way"と "should_follow_a_user_with_Ajax"がエラー。

destroyアクションの"format.html { redirect_to @user }“をコメントアウトした場合、 "should_unfollow_a_user_the_standard_way"がエラー。

destroyアクションの"format.html { redirect_to @user }“,"format.js"をコメントアウトした場合、 "should_unfollow_a_user_the_standard_way"と "should_unfollow_a_user_with_Ajax"がエラー。

演習14.2.6.2

<問題> リスト 14.40のxhr: trueがある行のうち、片方のみを削除するとどういった結果になるでしょうか? このとき発生する問題の原因と、なぜ先ほどの演習で確認したテストがこの問題を検知できたのか考えてみてください。

<解答> “format.js"のみをコメントアウトしてもエラーは発生しない。

演習14.3.1

演習14.3.1.1

<問題> マイクロポストのidが正しく並んでいると仮定して (すなわち若いidの投稿ほど古くなる前提で)、図 14.22のデータセットでuser.feed.map(&:id)を実行すると、どのような結果が表示されるでしょうか? 考えてみてください。ヒント: 13.1.4で実装したdefault_scopeを思い出してください。

<解答> [10,9,7,5,4,2,1]

演習14.3.3

演習14.3.3.1

<問題> Homeページで表示される1ページ目のフィードに対して、統合テストを書いてみましょう。リスト 14.49はそのテンプレートです。

<解答>

[test/integration/following_test.rb]

require 'test_helper'

class FollowingTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
    log_in_as(@user)
  end
  .
  .
  .
  test "feed on Home page" do
    get root_path
    @user.feed.paginate(page: 1).each do |micropost|
      assert_match CGI.escapeHTML(micropost.content), response.body
    end
  end
end

演習14.3.3.2

<問題> リスト 14.49のコードでは、期待されるHTMLをCGI.escapeHTMLメソッドでエスケープしています (このメソッドは11.2.3で扱ったCGI.escapeと同じ用途です)。このコードでは、なぜHTMLをエスケープさせる必要があったのでしょうか? 考えてみてください。ヒント: 試しにエスケープ処理を外して、得られるHTMLの内容を注意深く調べてください。マイクロポストの内容が何かおかしいはずです。また、ターミナルの検索機能 (Cmd-FもしくはCtrl-F) を使って「sorry」を探すと原因の究明に役立つはずです。

<解答> エスケープ処理を外すと、HTML中に"I’m sorry.“があるが、"I'm sorry."となる。

関連記事

【第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版)演習と解答まとめ - 新米パパの育児留学

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

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

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

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