はじめに
Railsチュートリアルに取り込む中で教材で触れられていないバグに遭遇した。
バグの原因追及と解決方法をまとめる。
この記事で分かること
・Active Recordの『joins』メソッドによる連結されたテーブルのカラム名の確認方法
・重複したレコードを一意にして取得する『distinct』メソッドの使用方法
参考資料
・Railsチュートリアル
https://railstutorial.jp/
・Railsガイド Active Record の関連付け
https://railsguides.jp/association_basics.html
・【Rails】データベースの中身を確認する方法【Cloud9】
https://shuheitakada.com/rails-database-check
・SELECT文の結果を表示する時にカラム名をヘッダーとして表示(.headersコマンド)
https://www.dbonline.jp/sqlite/sqlite_command/index5.html
・Rails distinctメソッドについて
https://qiita.com/toda-axiaworks/items/ad5a0e2322ac6a2ea0f4
###環境について
Railsチュートリアル第6版に従う
・cloud9使用
・ruby 2.6.3p62
・Rails 6.0.3
学習動機
Rails Tutorial第14章 14.3.3サブセレクト 演習3にてバグが生じる。
演習の目的はjoinsメソッドを使用してフォローしたユーザーと自分自身に紐づくマイクロポストをフィードとして表示させることである。
一見いい感じに見えるが、
どう考えてもenjoyしすぎである。
明らかにenjoyしてない気持ちを抑えつつ原因の究明をする。
モデルの関連付
各モデルの関連付は以下の通り。
class User < ApplicationRecord
has_many :microposts
has_many :passive_relationships, class_name: "Relationship", foreign_key: "followed_id"
has_many :followers, through: :passive_relationships, source: :follower
end
class Relationship < ApplicationRecord
belongs_to :follower, class_name: "User"
end
class Micropost < ApplicationRecord
belongs_to :user
end
# Copyright (c) 2016 Michael Hartl
##原因となったコード
def feed
part_of_feed = "relationships.follower_id = :id or microposts.user_id = :id"
Micropost.joins(user: :followers).where(part_of_feed, { id: id })
end
# Copyright (c) 2016 Michael Hartl
まずは、コンソールに入り、Railsにより作成されるSQL 文を確認する。
>>user.feed
SELECT * FROM "microposts" INNER JOIN "users" ON "users"."id" = "microposts"."user_id"
INNER JOIN "relationships" ON "relationships"."followed_id" = "users"."id"
INNER JOIN "users" "followers_users" ON "followers_users"."id" = "relationships"."follower_id"
WHERE (relationships.follower_id = 1 or microposts.user_id = 1)
ORDER BY "microposts"."created_at" DESC;
次に、データベースのコンソールにてjoinsメソッドにより作成されたテーブルの構造とデータを確認する。
#SELECTの結果でカラム名を表示させる
>>.header on
>>SELECT * FROM "microposts" INNER
JOIN "users" ON "users"."id" = "microposts"."user_id"
INNER JOIN "relationships" ON "relationships"."followed_id" = "users"."id"
INNER JOIN "users" "followers_users" ON "followers_users"."id" = "relationships"."follower_id"
WHERE (relationships.follower_id = 1 or microposts.user_id = 1)
ORDER BY "microposts"."created_at" DESC LIMIT 5;
#検索結果 |~|はカラムの省略の意
id|content|user_id|~|id|name|~|id|follower_id|followed_id|~|id|name|~|
308|Enjoy Coding !!|1|~|1|Example User|~|50|4|1|~|4|Mr. Rey Lemke|~|
308|Enjoy Coding !!|1|~|1|Example User|~|51|5|1|~|5|Dr. Louisa Price|~|
308|Enjoy Coding !!|1|~|1|Example User|~|52|6|1|~|6|Charisse Stamm|~|
308|Enjoy Coding !!|1|~|1|Example User|~|53|7|1|~|7|Sang Metz IV|~|
308|Enjoy Coding !!|1|~|1|Example User|~|54|8|1|~|8|Robt Hamill|~|
どうやら、follower_idによりMicropostsにダブりが生じていることが分かる。
ちなみにMicropostのダブりの数と自身のFollowersの数が一致していることからも、
Followersがダブりの原因になっていると推測できる。
つまり、下の様なフォロー関係の場合、
id:1 田中 → id:2 鈴木
id:2 鈴木 → id:1 田中
id:3 佐藤 → id:1 田中
id:3 佐藤 → id:2 鈴木
下記図の様なテーブルが作成される。
Micopost.whereにより、みどり枠とあか枠の部分が抽出され、あか枠部分がダブりとなっている。
#みどり枠部分
relationships.follower_id = :id:
#あか枠部分
microposts.user_id = :id
解決策
原因が分かるまで結構な時間を使ってしまったけれど、解決方法が分かるとたったの1語で解決できる。
##distinctメソッドを使う
重複のない一意性のあるレコードを取得するためには、『distinct』メソッドを使用するとのこと。
早速組み込んでみる。
def feed
part_of_feed = "relationships.follower_id = :id or microposts.user_id = :id"
Micropost.joins(user: :followers).where(part_of_feed, { id: id }).distinct
end
#Copyright (c) 2016 Michael Hartl
OK!!
##テストを書く
今こそテストを書く時。
ということでFeedに自分が投稿したマイクロポストの重複がないことをテストする。
def setup
@user = users(:michael)
end
test "should feed have microposts with uniqueness" do
log_in_as(@user)
get root_path
# マイクロポストの投稿
content = "This micropost is only one!"
post microposts_path, params: { micropost: { content: content }}
follow_redirect!
# feedのダブりの確認
assert_select 'span.content', { :count=>1, :text=> "#{content}" }
end
#Copyright (c) 2016 Michael Hartl