はじめに
RDBを使っていると当然、多対多のモデルは出てきます。
触り方にはいくつかあるので投稿することにしました。
サンプル
例として、User
クラス、UserGroup
クラスがあり、中間テーブルとしてUserGrouping
を作成したという前提で話を続けます。
AR = ActiveRecord::Baes
class User < AR
has_many :user_groups
end
class UserGroup < AR
has_many :user_groups
end
class UserGrouping < AR
belongs_to :user
belongs_to :user_group
end
ではUserGroupに属するUser一覧を取得するにはどうすればよいでしょうか。
has_many though
この方法がRailsの推奨だと思われます。
- メリット
- 資料が多い
- ActiveRecord::Relationが返るので、再利用しやすい
- デメリット
- リレーションが深くなると has_many though が中継箇所全てに出てきて邪魔
- リレーションの途中で条件文などを挟めない
class UserGroup
has_many :users, through: :user_groupings
end
user_group = UserGroup.first
users = user_group.users
ActiveRecordのサブクエリ
ActiveRecordでSQLのサブクエリをシミュレートします。
発行されるSQLは他と違います(後述)
- メリット
- ActiveRecord::Relationが返るので、再利用しやすい
- デメリット
- SQLを意識して組み立てる必要がある
user_group = UserGroup.first
user_ids = UserGrouping.where(user_group_id: user_group.id).select(:user_id)
users = User.where(id: user_ids)
SQLビルダー
ActiveRecordが内部で使っているというArelTableを使います。
SQLを書くのとほぼ変わらないのでそれなりの知識が必要です。
- メリット
- SQLを組み立てるので
not
もor
も自由自在 - arelのクエリを返すメソッド同士を組み合わせて新しいクエリを作れる
- SQLを組み立てるので
- デメリット
- ActiveRecord::Relationが返らない
user_group = UserGroup.first
t_u = User.arel_table
t_ugg = UserGrouping.arel_table
query =
t_u.project(Arel.star)
.join(t_ugg).on(t_u[:id].eq t_ugg[:user_id])
.where(t_ugg[:user_group_id].eq user_group.id)
users = User.find_by_sql(query)
おまけ
(1), (3)の方法ではJOIN
のSQLが発行され、(2)の方法では IN
が使われます。
SELECT "users".*
FROM "users"
INNER JOIN "user_groupings"
ON "users"."id" = "user_groupings"."user_id"
WHERE "user_groupings"."user_group_id" = 1
SELECT "users".*
FROM "users"
WHERE "users"."id" IN (
SELECT user_id
FROM "user_groupings"
WHERE "user_groupings"."user_group_id" = 1
)
JOINを用いる場合、今回は中間テーブルがひとつだけど、2つ以上になると 同じIDを持つ行が複数行抽出されるので注意しないといけません。
私の経験では、外側はIN
を用いたサブクエリ、サブクエリ内ではJOINでガンガンつなぐというのが一番書きやすかったです。