LoginSignup
0
0

More than 1 year has passed since last update.

ActiveRecordでの条件付きのリレーションについて

Posted at

概要

ActiveRecordでは「has_many, has_one, belongs_to」をモデルに定義することでテーブル間同士の関連性を表すことができます。
この関連性ですが、単純なテーブル同士の関係だけではなく、条件を定義することで任意の関連性を表現出来るので、そのことについてまとめます。

基本

開発チームとチームメンバーをDBで管理する場合を考えてみます。
開発チームには複数のチームメンバーがいるので、開発チームとチームメンバーの関係は1対nになります。
名前をteams, membersとします。
名称未設定2.png

モデルで定義すると以下のようになります。

class Team < ApplicationRecord
  has_many :members
end

class Member < ApplicationRecord
  belongs_to :team
end

条件付きの関連性

突然ですが、開発チームはグローバルな多国籍チームでした。チームメンバーの国籍をnationalityというカラムで新しく定義することにします。
この例において、以下のように日本国籍のチームメンバー、アメリカ国籍のチームメンバーなどを開発チームのモデルに関連性として定義することができます。モデルのスコープに近いでしょうか。

class Team < ApplicationRecord
  has_many :members
  has_many :japanese_members, -> { merge(Members.where(nationality: 'japanese')) }, class_name: 'Member', inverse_of: :team, 
end

応用

またまた突然ですが、開発メンバーの中には複数のチームに所属するメンバーがいることが分かりました。
そのため開発チームとチームメンバーの関連性は多対多です。team_membersという中間テーブルを定義してこれを表現することにします。
名称未設定.png

モデル定義は以下のようになります。

class Team < ApplicationRecord
  has_many :team_members
  has_many :members, through: team_members
end

class TeamMember < ApplicationRecord
  belongs_to :team
  belongs_to :member
end

class Member < ApplicationRecord
  has_many :team_members
  has_many :teams, through: team_members
end

この場合で、先ほどの日本国籍のチームメンバーをモデルに関連性として定義すると以下のようになります。

class Team < ApplicationRecord
  has_many :team_members
  has_many :members, through: team_members
  has_many :japanese_members, -> { merge(Members.where(nationality: 'japanese')) }, through: team_members, source: 'team'
end

おまけ

チームにはリーダーが1人だけいることとします。
リーダーかどうかはis_leaderというbool型のカラムで定義することにします。
teamにhas_oneでリーダーのメンバーを定義しようとすると以下のようになります。

class Team < ApplicationRecord
  has_many :team_members
  has_many :members, through: team_members
  has_one :lead_team_member, -> { merge(TeamMembers.joins(:team).merge(Members.where(is_leader: true))) }, class_name: 'TeamMember', inverse_of: :team
  has_one :lead_member, source: 'member', through: lead_team_member
end

has_manyで定義した中間テーブルを通して(throughして)、直接has_oneを定義することはできないので、まずteam_membersに対してhas_oneを定義します。
次に上記で定義したhas_oneの関連を通して(throughして)、最終的にmemberテーブルに対してhas_oneの関連性を持たせることができます。

mergeやwhereがネストしたりして可読性が落ちるので(かつテストも書きづらくなるので)、一つ一つスコープで定義するとよりわかりやすく簡潔に書けるかと思います。

0
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
0
0