Rails の has_many through で scope を使う
Active Record の関連付け(アソシエーション = associtaion)は色んな機能があります。 今回はタイトル通り、has_many :through
と scope
を併用する方法を紹介します。
has_one
, has_many
, belongs_to
辺りの違いは理解していることを前提とします。
has_many :through の説明
最初に、 has_many :through
について説明します。Rails ガイドを見た方が早いと思うので、まずはリンクを貼っておきます。
Active Record の関連付け – Railsガイド
患者(patient)、医師(physician)、診察予約(appointment)の3つのモデルがそれぞれ
- patient:appointment = 1:n
- physician:appointment = 1:n
- patient:physician = m:n
という関係になっています。最後の patient:physician の関係は、appointment を通して(through)の関係のため、
- A physician has many patients through appointments
- A patient has many physicians through appointments
という意味で has_many :through
association と呼ばれます。
本題
やりたいこと
さて、先ほどの Rails ガイドの例を少し発展させてみます。
例えば、ある医師 (physician) の患者 (patient) のうち、現在時刻〜今日の終わりまでの appointment をもっている患者だけを取り出したいと思います。
where 句を使うと以下の通りです。
phy = Physician.find 1
now = Time.current
phy.patients.where(appointments: {appointment_date: now..now.end_of_day})
中間テーブルに scope を設定
現在時刻〜今日の終わりまでの appointment というのはよく使う条件なので、scope を設定する事にします。 Appointment
モデルには、以下のような scope を追加します。scope 名は :today
とでも付けておきましょう。
scope :today, -> { now = Time.current; where(appointment_date: now..now.end_of_day) }
ここまでは問題無いと思います。
scope を使って書き換え
それでは先ほどのクエリーを、新たに設定した scope を使って書き直してみましょう。
まずはダメな例から。
phy = Physician.find 1
phy.patients.today # ダメ
何がダメかというと、today
という scope は、patients ではなく中間テーブルである appointments に設定されているからです。
これを解決する方法はいくつかあるようですが、私は以下のようにしました。
class Physician < ApplicationRecord
has_many :appointments
has_many :patients, through: :appointments
# 新規に以下の2行を追加
has_many :todays_appointments, -> { today }, class_name: 'Appointment'
has_many :todays_patients, through: :todays_appointments
end
ポイントは、中間テーブルに対する has_many
で scope を使う事です。
その上で以下のようにします。
phy = Physician.find 1
phy.todays_patients # OK
まとめ
中間テーブルを使った m:n の関係を持たせる方法の1つとして has_many :through
association があります。その際に、中間テーブルの特定の条件に合致したものだけを子レコードとして取り出したい場合があります。
実現方法としては、
- 中間テーブルに
scope
を設定し - 中間テーブルに対して scope 付きの
has_many
を定義し - 2で定義した
has_many
を使って、has_many :through
で子テーブルを取得する
という方法が簡単です。
分かってしまえば簡単なのですが、Rails ガイドにも Stack Overflow にもそのものズバリの情報が見つからなかったので、色々試行錯誤してしまいました。参考になれば幸いです。
コメントを残す