第14章Railsチュートリアル演習問題と解答まとめ

Ruby on Railsチュートリアルの演習問題と解答をまとめる。


第14章 ユーザーをフォローする - Railsチュートリアル

f:id:yukitoku_sw:20191019165508p:plain


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



14.1.1 データモデルの問題(および解決策)

演習1

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

解答

id=1にフォローされているユーザー(id: 2, 7, 10,)のidをそれぞれ1つずつ返す

演習2

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

解答

id=2にフォローされているユーザー(id: 1)を返す


14.1.2 User/Relationshipの関連付け

演習1

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

解答

>> user = User.first
(省略)
>> user2 = User.second
(省略)
>> user.active_relationships.create(followed_id: user2.id)
   (0.1ms)  begin transaction
  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 (2.5ms)  INSERT INTO "relationships" ("follower_id", "followed_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["follower_id", 1], ["followed_id", 2], ["created_at", "2019-11-04 10:27:41.371417"], ["updated_at", "2019-11-04 10:27:41.371417"]]
   (1.6ms)  commit transaction
=> #<Relationship id: 1, follower_id: 1, followed_id: 2, created_at: "2019-11-04 10:27:41", updated_at: "2019-11-04 10:27:41">


演習2

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

解答

=> #<Relationship id: 1, follower_id: 1, followed_id: 2, created_at: "2019-11-04 10:27:41", updated_at: "2019-11-04 10:27:41">


14.1.3 Relationshipのバリデーション

演習1

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

解答

動作確認のみなので省略


14.1.4 フォローしているユーザー

演習1

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

解答

>> michael = User.third
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? OFFSET ?  [["LIMIT
", 1], ["OFFSET", 2]]
=> #<User id: 3, name: "Ryley Abbott DVM", email: "example-2@railstutorial.org", created_at: "2019-11-03
 00:46:18", updated_at: "2019-11-03 00:46:18", password_digest: "$2a$10$LsvPDhcVk3jlVoEyO4PSmOyH/n6mXynvrwC1MpSxgYs...", remember_digest: nil, admin: false, activation_digest: "$2a$10$KC4QN336HKYIOXoQH3fJ2.T1Gy8C6GJvA9du.cmc85m...", activated: true, activated_at: "2019-11-03 00:46:18", reset_digest: nil, reset_
sent_at: nil>
>> archer = User.fourth
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? OFFSET ?  [["LIMIT", 1], ["OFFSET", 3]]
=> #<User id: 4, name: "Grace Bernier DDS", email: "example-3@railstutorial.org", created_at: "2019-11-03 00:46:18", updated_at: "2019-11-03 00:46:18", password_digest: "$2a$10$kw8OUcJpxH6.qYhDOqCiYuCR/jHjNCp8w/e2gcCn/Ad...", remember_digest: nil, admin: false, activation_digest: "$2a$10$/CmJZJhdONUJYJCkobvs8eynbxyZiDGEUCpMJkTbyze...", activated: true, activated_at: "2019-11-03 00:46:18", reset_digest: nil, reset_sent_at: nil>
>> michael.following?(archer)
  User Exists (0.3ms)  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", 3], ["id", 4], ["LIMIT", 1]]
=> false
>> michael.follow(archer)
   (0.1ms)  begin transaction
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
  SQL (1.3ms)  INSERT INTO "relationships" ("follower_id", "followed_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["follower_id", 3], ["followed_id", 4], ["created_at", "2019-11-04 11:07:29.366144"], ["updated_at", "2019-11-04 11:07:29.366144"]]
   (2.4ms)  commit transaction
  User Load (0.3ms)  SELECT  "users".* FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? LIMIT ?  [["follower_id", 3], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<User id: 4, name: "Grace Bernier DDS", email: "example-3@railstutorial.org", created_at: "2019-11-03 00:46:18", updated_at: "2019-11-03 00:46:18", password_digest: "$2a$10$kw8OUcJpxH6.qYhDOqCiYuCR/jHjNCp8w/e2gcCn/Ad...", remember_digest: nil, admin: false, activation_digest: "$2a$10$/CmJZJhdONUJYJCkobvs8eynbxyZiDGEUCpMJkTbyze...", activated: true, activated_at: "2019-11-03 00:46:18", reset_digest: nil, reset_sent_at: nil>]>
>> michael.following?(archer)
  User Exists (0.4ms)  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", 3], ["id", 4], ["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", 3], ["followed_id", 4], ["LIMIT", 1]]
   (0.1ms)  begin transaction
  SQL (0.6ms)  DELETE FROM "relationships" WHERE "relationships"."id" = ?  [["id", 2]]
   (1.3ms)  commit transaction
=> #<Relationship id: 2, follower_id: 3, followed_id: 4, created_at: "2019-11-04 11:07:29", updated_at: "2019-11-04 11:07:29">
>> michael.following?(archer)
  User Exists (0.4ms)  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", 3], ["id", 4], ["LIMIT", 1]]
=> false
>> 


演習2

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

解答

演習1参照


14.1.5 フォロワー

演習1

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

解答

>> user = User.first

>> user2 = User.second
 
>> user3 = User.third
 
>> user4 = User.fourth

>> user2.follow(user)

>> user3.follow(user)

>> user4.follow(user)

>> 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, 4]


演習2

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

解答

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

user_idが2. 3. 4の3人なので一致している


演習3

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

解答

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

>> user.followers.to_a.count
=> 3

実行結果にsql文が出力されない。

100万人のフォロワーがいたとする
user.followers.countの場合、DB内でfollowersの合計を算出し返す。
user.followers.to_a.countの場合、DBから100万回データを取り出し配列を生成しその数の合計を返す。

下記実行結果をcountしているため100万人もいたら時間がかかる。
たった3人でこの量、DBの負担がすごそう。。

>> user.followers.to_a
=> [#<User id: 2, name: "Mr. Jade Renner", email: "example-1@railstutorial.org", created_at: "2019-11-03 00:46:17", updated_at: "2019-11-03 00:46:17", password_digest: "$2a$10$MBzfZ.ZcjOUs9GYGkTZBH.kY6U25hrORxv8DUsZ1C6o...", remember_digest: nil, admin: false, activation_digest: "$2a$10$7X19s7oIJNvcz.QJWJkvXOFDD9abC0alx/hdNtvzGCD...", activated: true, activated_at: "2019-11-03 00:46:17", reset_digest: nil, reset_sent_at: nil>, #<User id: 3, name: "Ryley Abbott DVM", email: "example-2@railstutorial.org", created_at: "2019-11-03 00:46:18", updated_at: "2019-11-03 00:46:18", password_digest: "$2a$10$LsvPDhcVk3jlVoEyO4PSmOyH/n6mXynvrwC1MpSxgYs...", remember_digest: nil, admin: false, activation_digest: "$2a$10$KC4QN336HKYIOXoQH3fJ2.T1Gy8C6GJvA9du.cmc85m...", activated: true, activated_at: "2019-11-03 00:46:18", reset_digest: nil, reset_sent_at: nil>, #<User id: 4, name: "Grace Bernier DDS", email: "example-3@railstutorial.org", created_at: "2019-11-03 00:46:18", updated_at: "2019-11-03 00:46:18", password_digest: "$2a$10$kw8OUcJpxH6.qYhDOqCiYuCR/jHjNCp8w/e2gcCn/Ad...", remember_digest: nil, admin: false, activation_digest: "$2a$10$/CmJZJhdONUJYJCkobvs8eynbxyZiDGEUCpMJkTbyze...", activated: true, activated_at: "2019-11-03 00:46:18", reset_digest: nil, reset_sent_at: nil>]


14.2.1 フォローのサンプルデータ

演習問題1

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

解答

>> User.first.followers.count
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
   (0.3ms)  SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = ?  [["followed_id", 1]]
=> 38


演習問題2

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

解答

>> User.first.following.count
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
   (0.3ms)  SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ?  [["follower_id", 1]]
=> 49


14.2.2 統計と [Follow] フォーム

演習問題1

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

解答

f:id:yukitoku_sw:20191105200417p:plain

f:id:yukitoku_sw:20191105200433p:plain

unfollowボタンが白くて見えない。

f:id:yukitoku_sw:20191105200552p:plain

loginしているのでボタンがない


演習問題2

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

解答

統計情報は一致している

Homeページ

f:id:yukitoku_sw:20191105200915p:plain

プロフィールページ

f:id:yukitoku_sw:20191105200927p:plain


演習問題3

Homeページに表示されている統計情報に対してテストを書いてみましょう。ヒント: リスト 13.28で示したテストに追加してみてください。同様にして、プロフィールページにもテストを追加してみましょう。

解答

Homeページのテストは下記に追加

[test/integration/site_layout_test.rb]
require 'test_helper'

class SiteLayoutTest < ActionDispatch::IntegrationTest
  (中略)
  test "layout links when logged in user" do
    log_in_as(@user)
    get root_path
    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=?]", users_path
    assert_select "a[href=?]", user_path(@user)
    assert_select "a[href=?]", edit_user_path(@user)
    assert_select "a[href=?]", logout_path
    assert_match @user.active_relationships.count.to_s, response.body
    assert_match @user.passive_relationships.count.to_s, response.body
  end
end

プロフィールページは下記に追加

[test/integration/users_profile_test.rb]

require 'test_helper'

class UsersProfileTest < ActionDispatch::IntegrationTest
  include ApplicationHelper

  def setup
    @user = users(:michael)
  end

  test "profile display" do
    get user_path(@user)
    assert_template 'users/show'
    assert_select 'title', full_title(@user.name)
    assert_select 'h1', text: @user.name
    assert_select 'h1>img.gravatar'
    assert_match @user.microposts.count.to_s, response.body
    assert_select 'div.pagination', count: 1
    @user.microposts.paginate(page: 1).each do |micropost|
      assert_match micropost.content, response.body
    end
    assert_match @user.active_relationships.count.to_s, response.body
    assert_match @iser.passive_relationhips.count.to_s, response.body
  end
end


14.2.3 [ Following ] と [ Followers ] ページ

演習問題1

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

解答

following

f:id:yukitoku_sw:20191105214509p:plain

followers

f:id:yukitoku_sw:20191105214547p:plain


演習問題2

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

解答

コメントアウト2箇所必要です。
・link_to gravatar...の行のみだと、SyntaxErrorが出てしまうのでeach分ごとコメントアウトする。
・render @usersも忘れずにコメントアウト

[app/views/users/show_follow.html.erb]

<% provide(:title, @title) %>
<div class="row">
  <aside class="col-md-4">
    <section class="uesr_info">
      <%= gravatar_for @user %>
      <h1><%= @user.name %></h1>
      <span><%= link_to "view my profile", @user %></span>
      <span><b>Microposts:</b> <%= @user.microposts.count %></span>
    </section>
    <section class="stats">
      <%= render 'shared/stats' %>
      <% if @users.any? %>
        <div class="user_avatars">
          <%# @users.each do |user| %>
            <%#= link_to gravatar_for(user, size: 30), user %>
          <%# end %>
        </div>
      <% end %>
    </section>
  </aside>
  <div class="col-md-8">
    <h3><%= @title %></h3>
    <% if @users.any? %>
      <ul class="users follow">
        <%#= render @users %>
      </ul>
      <%= will_paginate %>
    <% end %>
  </div>
</div>

テストでassert_selectの行がred(Expected at least 1 element matching "a[href="/users/409608538"]", found 0..)になるのを確認。

最後直すのも忘れずに!


14.2.4 [ Follow ] ボタン(基本編)

演習問題1

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

解答

実行してみましょう!

演習問題2

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

解答

フォロー
Rendering users/show.html.erb

Started POST "/relationships" for 127.0.0.1 at 2019-11-05 22:51:57 +0900
Processing by RelationshipsController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"DtJHn4u00RaMzwLppiLdfSHm2mqE8zoN2p4JbZSbtYV1Ngurl+wXDvr8kb2b8wEsh9DY40oBBZRMCpL7gQ8nDQ==", "followed_id"=>"2", "commit"=>"Follow"}
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
   (0.1ms)  begin transaction
  CACHE User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  SQL (1.6ms)  INSERT INTO "relationships" ("follower_id", "followed_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["follower_id", 1], ["followed_id", 2], ["created_at", "2019-11-05 13:51:57.239627"], ["updated_at", "2019-11-05 13:51:57.239627"]]
   (1.2ms)  commit transaction
Redirected to http://localhost:3000/users/2
Completed 302 Found in 15ms (ActiveRecord: 3.6ms)


Started GET "/users/2" for 127.0.0.1 at 2019-11-05 22:51:57 +0900
Processing by UsersController#show as HTML
  Parameters: {"id"=>"2"}
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  Rendering users/show.html.erb within layouts/application
 

フォロー解除
同じく、Rendering users/show.html.erb

Started DELETE "/relationships/88" for 127.0.0.1 at 2019-11-05 22:52:02 +0900
Processing by RelationshipsController#destroy as HTML
  Parameters: {"utf8"=>"", "authenticity_token"=>"n5ZZ1I9/igmIjRJ8losPb4MGgcBztJ5/IN6/2VFF1jfpq2Xr/h5uS39iTzz8o2kRlIHYlJvfQKlUDYpSifoOTw==", "commit"=>"Unfollow", "id"=>"88"}
  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  Relationship Load (0.4ms)  SELECT  "relationships".* FROM "relationships" WHERE "relationships"."id" = ? LIMIT ?  [["id", 88], ["LIMIT", 1]]
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  Relationship Load (0.4ms)  SELECT  "relationships".* FROM "relationships" WHERE "relationships"."follower_id" = ? AND "relationships"."followed_id" = ? LIMIT ?  [["follower_id", 1], ["followed_id", 2], ["LIMIT", 1]]
   (0.1ms)  begin transaction
  SQL (1.1ms)  DELETE FROM "relationships" WHERE "relationships"."id" = ?  [["id", 88]]
   (1.6ms)  commit transaction
Redirected to http://localhost:3000/users/2
Completed 302 Found in 16ms (ActiveRecord: 4.2ms)


Started GET "/users/2" for 127.0.0.1 at 2019-11-05 22:52:02 +0900
Processing by UsersController#show as HTML
  Parameters: {"id"=>"2"}
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  Rendering users/show.html.erb within layouts/application


14.2.5 [ Follow ] ボタン( Ajax編 )

演習問題1

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

解答

ブラウザにて動作確認

演習問題2

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

解答

Started POST "/relationships" for 127.0.0.1 at 2019-11-06 22:37:57 +0900
Processing by RelationshipsController#create as JS
  Parameters: {"utf8"=>"", "authenticity_token"=>"quG2QBvRnG9iE+GV/wn9f9GPcma2L5nT+X07bSNILbLRBfp0B4ladxQgcsHC2CEud7lw73jdpkpv6aD7Nty/Og==", "followed_id"=>"2", "commit"=>"Follow"}
  (省略)
   (0.2ms)  SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = ?  [["followed_id", 2]]
  Rendered relationships/create.js.erb (6.2ms)
Completed 200 OK in 25ms (Views: 7.6ms | ActiveRecord: 5.6ms)


Started DELETE "/relationships/90" for 127.0.0.1 at 2019-11-06 22:38:00 +0900
Processing by RelationshipsController#destroy as JS
  Parameters: {"utf8"=>"", "authenticity_token"=>"jrAkVxicFuNQQ96quS/UlJwg8zTi2mXGYYFtbJp/uHkgeNtoqU7k1jr0XF934QVDIulqxky9kEISQ8d4XCAXfQ==", "commit"=>"Unfollow", "id"=>"90"}
  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1
(省略)
   (0.1ms)  begin transaction
  SQL (1.0ms)  DELETE FROM "relationships" WHERE "relationships"."id" = ?  [["id", 90]]
   (1.5ms)  commit transaction
  Rendering relationships/destroy.js.erb
  Rendered users/_follow.html.erb (3.3ms)
   (0.4ms)  SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = ?  [["followed_id", 2]]
  Rendered relationships/destroy.js.erb (10.7ms)
Completed 200 OK in 29ms (Views: 15.7ms | ActiveRecord: 4.3ms)

jsファイルが表示されている


14.2.6 フォローをテストする

演習問題1

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

解答

まず、現時点でテストがGreenになることを確認する

1行目をコメントアウトする

[app/controllers/relationships_controller.rb]

class RelationshipsController < ApplicationController
  before_action :logged_in_user

  def create
    @user = User.find(params[:followed_id])
    current_user.follow(@user)
    respond_to do |format|
      # format.html { redirect_to @user }
      format.js
    end
  end
 .
 .
 .
end

テスト -> Red

ERROR["test_should_follow_a_user_the_standard_way", FollowingTest, 1.0399349999934202]
 test_should_follow_a_user_the_standard_way#FollowingTest (1.04s)
ActionController::UnknownFormat:         ActionController::UnknownFormat: ActionController::UnknownFormat
            app/controllers/relationships_controller.rb:7:in `create'
            test/integration/following_test.rb:31:in `block (2 levels) in <class:FollowingTest>'
            test/integration/following_test.rb:30:in `block in <class:FollowingTest>'

  73/73: [===========================================================] 100% Time: 00:00:02, Time: 00:00:02

2行目をコメントアウトする

[app/controllers/relationships_controller.rb]

class RelationshipsController < ApplicationController
  before_action :logged_in_user

  def create
    @user = User.find(params[:followed_id])
    current_user.follow(@user)
    respond_to do |format|
      format.html { redirect_to @user }
      # format.js
    end
  end

 .
 .
 .
end

テスト -> Green

1行目の format.html { redirect_to @user }を消すとテストが落ちる

演習問題2

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

解答

[test/integration/following_test.rb]

  test "should follow a user with Ajax" do
    assert_difference '@user.following.count', 1 do
      # post relationships_path, xhr: true, params: { followed_id: @other.id}
    end
  end

エラー内容

FAIL["test_should_follow_a_user_with_Ajax", FollowingTest, 1.2087310000060825]
 test_should_follow_a_user_with_Ajax#FollowingTest (1.21s)
        "@user.following.count" didn't change by 1.
        Expected: 2
          Actual: 1
        test/integration/following_test.rb:36:in `block in <class:FollowingTest>'

postがないのでもちろんテストはエラー。

なぜ先ほどの演習で確認したテストがこの問題を検知できたのか

3日間調べたが理解できなかったので飛ばします。


14.3.1 動機と計画

演習問題1

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

解答

default_scope -> { order(created_at: :desc) } と設定しているので、マイクロポストの投稿時間を基準に降順で表示される。


14.3.2 フィードを初めて実装する

演習問題1

リスト 14.44において、現在のユーザー自身の投稿を含めないようにするにはどうすれば良いでしょうか? また、そのような変更を加えると、リスト 14.42のどのテストが失敗するでしょうか?

解答

("user_id = ?", id)で自分の投稿を表示しているので削除する

  # ユーザーのステータスフェードを返す 
  def feed
    # Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
    Micropost.where("user_id IN (?)", following_ids)
  end

テストがRedになる
paginationが見つかりませんも検出される(paginationはちゃんと表示されているのに)

% rails test
Running via Spring preloader in process 8714
Started with run options --seed 3649

 FAIL["test_micropost_interface", MicropostsInterfaceTest, 1.0278030000044964]
 test_micropost_interface#MicropostsInterfaceTest (1.03s)
        Expected at least 1 element matching "div.pagination", found 0..
        Expected 0 to be >= 1.
        test/integration/microposts_interface_test.rb:12:in `block in <class:MicropostsInterfaceTest>'

 FAIL["test_feed_should_have_the_right_posts", UserTest, 2.1715810000314377]
 test_feed_should_have_the_right_posts#UserTest (2.17s)
        Expected false to be truthy.
        test/models/user_test.rb:110:in `block (2 levels) in <class:UserTest>'
        test/models/user_test.rb:109:in `block in <class:UserTest>'

  74/74: [===========================================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.50530s
74 tests, 339 assertions, 2 failures, 0 errors, 0 skips


演習問題2

リスト 14.44において、フォローしているユーザーの投稿を含めないようにするにはどうすれば良いでしょうか? また、そのような変更を加えると、リスト 14.42のどのテストが失敗するでしょうか?

解答

演習1の逆バージョン

  # ユーザーのステータスフェードを返す 
  def feed
    Micropost.where("user_id = ?", id)
    # Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
    # Micropost.where("user_id IN (?)", following_ids)
  end

テストがRedになる paginationは自身の投稿に紐づいていることになっているので今回はエラーが出ない

% rails test
Running via Spring preloader in process 8827
Started with run options --seed 5233

 FAIL["test_feed_should_have_the_right_posts", UserTest, 1.2993419999838807]
 test_feed_should_have_the_right_posts#UserTest (1.30s)
        Expected false to be truthy.
        test/models/user_test.rb:106:in `block (2 levels) in <class:UserTest>'
        test/models/user_test.rb:105:in `block in <class:UserTest>'

  74/74: [===========================================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.69392s
74 tests, 347 assertions, 1 failures, 0 errors, 0 skips


演習問題3

リスト 14.44において、フォローしていないユーザーの投稿を含めるためにはどうすれば良いでしょうか? また、そのような変更を加えると、リスト 14.42のどのテストが失敗するでしょうか? ヒント: 自分自身とフォローしているユーザー、そしてそれ以外という集合は、いったいどういった集合を表すのか考えてみてください。

解答

全ての投稿を表示すればいいので

  # ユーザーのステータスフェードを返す 
  def feed
    Micropost.all
    # Micropost.where("user_id = ?", id)
    # Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
    # Micropost.where("user_id IN (?)", following_ids)
  end

フォローしていないユーザーの投稿は表示しない。という部分でエラーが出る

% rails test
Running via Spring preloader in process 8864
Started with run options --seed 36471

 FAIL["test_feed_should_have_the_right_posts", UserTest, 1.5234350000391714]
 test_feed_should_have_the_right_posts#UserTest (1.52s)
        Expected true to be nil or false
        test/models/user_test.rb:114:in `block (2 levels) in <class:UserTest>'
        test/models/user_test.rb:113:in `block in <class:UserTest>'

  74/74: [===========================================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.82366s
74 tests, 383 assertions, 1 failures, 0 errors, 0 skips


14.3.3 サブセレクト

演習問題1

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

解答

  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


演習問題2

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

解答

おわり