34
40

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 5 years have passed since last update.

Railsの多対多モデルの触り方、三種

Last updated at Posted at 2014-03-27

はじめに

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を組み立てるのでnotorも自由自在
    • arelのクエリを返すメソッド同士を組み合わせて新しいクエリを作れる
  • デメリット
    • 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でガンガンつなぐというのが一番書きやすかったです。

34
40
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
34
40

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?