【Rails】Active Recordのjoinsメソッドで連結したテーブルから一意なレコードを取得する(Rails Tutorial第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チュートリアル第６版に従う
・cloud9使用
・ruby 2.6.3p62
・Rails 6.0.3

# 学習動機
Rails Tutorial第14章　14.3.3サブセレクト　演習３にてバグが生じる。
演習の目的はjoinsメソッドを使用してフォローしたユーザーと自分自身に紐づくマイクロポストをフィードとして表示させることである。
一見いい感じに見えるが、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/657027/6ea64f26-786b-2df8-57e5-a876e0e8b985.png)

マイクロポストを投稿すると、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/657027/ba0718bf-fbc5-8abd-5f59-bacc08882faf.png)

どう考えてもenjoyしすぎである。
明らかにenjoyしてない気持ちを抑えつつ原因の究明をする。

## モデルの関連付
各モデルの関連付は以下の通り。

```ruby:
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/657027/23e6b807-369d-0e42-19fb-af8219f54bd2.png)


##原因となったコード
```ruby: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 文を確認する。

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

```ruby: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/657027/3d40b06a-c632-ba8b-6066-d8fcdbcd9642.png)

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

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

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

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

早速組み込んでみる。

```ruby: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/657027/b6a6f190-4aa2-11c4-7bd0-3bbd4ea047b3.png)
OK!!
##テストを書く
今こそテストを書く時。
ということでFeedに自分が投稿したマイクロポストの重複がないことをテストする。

```ruby: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
```