Edited at

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

More than 1 year has passed since last update.

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

演習問題を自分なりに実施しました。

もし間違い等あればコメントいただけると嬉しいです。


演習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."となる。


関連記事

Ruby on Rails チュートリアル 完全攻略 概要と演習解答総まとめ

http://mochikichi.hatenablog.com/entry/rails_tutorial_guide