1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Rails】Active Recordのjoinsメソッドで連結したテーブルから一意なレコードを取得する(Rails Tutorial第14章)

Last updated at Posted at 2020-08-14

はじめに

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メソッドを使用してフォローしたユーザーと自分自身に紐づくマイクロポストをフィードとして表示させることである。
一見いい感じに見えるが、
image.png

マイクロポストを投稿すると、
image.png

どう考えても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

また、各テーブルの構成は以下の通り。
Rails Tutorial14章joinsの関連図.png

##原因となったコード

models/user.rb
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 文を確認する。

rails_console

>>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メソッドにより作成されたテーブルの構造とデータを確認する。

rails_dbconsole
#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により、みどり枠とあか枠の部分が抽出され、あか枠部分がダブりとなっている。
RailsTuorial14章Joinsで作成されるテーブルの模式図.png

models/user.rb
 #みどり枠部分
 relationships.follower_id = :id: 

 #あか枠部分
 microposts.user_id = :id

解決策

原因が分かるまで結構な時間を使ってしまったけれど、解決方法が分かるとたったの1語で解決できる。

##distinctメソッドを使う
重複のない一意性のあるレコードを取得するためには、『distinct』メソッドを使用するとのこと。

早速組み込んでみる。

models/user.rb
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

image.png
OK!!
##テストを書く
今こそテストを書く時。
ということでFeedに自分が投稿したマイクロポストの重複がないことをテストする。

test/integrationtest/microposts_interface_test.rb
 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
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?