StoneDot の Ruby on Rails 講座

ActiveRecordの関連のすべて

今回は ActiveRecord ついて説明していきたいと思います。 A Guide to Active Record Associationsと、 Active Record Query Interface を参考にしました。 また、図もこちらから拝借させていただきました。

Rails での関連

Rails では関連を設定することでモデルオブジェクトを接続し構造を作ることが出来ます。

関連の設定はモデルクラスに宣言的にメソッドを書き入れることによって行います。 使えるメソッドとしては大きく分けて以下の6種類があります。

ここからはこれらのメソッドをどのように使うのかを説明していきたいと思います。

一対一関連

一対一関連では belongs_tohas_one を利用します。英語をそのまま解釈すると belongs_to は何かしらのオブジェクトに所有されるようなモデルに設定し、 has_one は何かしらのオブジェクトを子に持つようなモデルに設定すべきということが分かります。

例えば、サプライヤーが一つだけ口座を持つ場合、サプライヤーが口座を持っていると考えて、Supplier が has_one を使用し、 Account が belongs_to を使うべきだと言うことが分かります。Account が Supplier に所有されていることを表すためには以下のように記述します。

class Account < ActiveRecord::Base
  belongs_to :supplier
end

同様に、Supplier が Account を所有していることを表すためには以下のように記述します。

class Supplier < ActiveRecord::Base
  has_one :account
end

テーブルの構造としては belongs_to 側の Account が Supplier の id を supplier_id として持つ必要があります。

多対一関連

多対一関連では belongs_to と has_many を利用します。

belongs_to を使うのが所有される立場のオブジェクト、 has_many を使うのが幾つかオブジェクトを持つ側になります。

例えば、顧客が注文をすることを考えると、顧客一人あたり幾つも注文する可能性があるため、顧客が複数の注文を所有していると考えることが出来ます。

このような場合、 Customer が Order を複数所有していることを表すためには以下のように記述します。

class Customer < ActiveRecord::Base
  has_many :orders
end

次に Order が Customer に所有されていることを表すために以下のように記述します。

class Order < ActiveRecord::Base
  belongs_to :customer
end

この時、テーブルの構造としては belongs_to を利用する Order が Customer の id を customer_id に格納する必要があります。

has_one :through

has_one :through は別のオブジェクトを通して一つのオブジェクトを参照する場合に利用します。たとえば、サプライヤーは一つの口座を持っていて、口座は一つの口座履歴を持っているとすると、サプライヤーは口座を通して口座履歴を持っていると考えることが出来ます。このような場合に has_one :through を利用します。

上記のような場合は以下のような形でモデルを記述します。

class Supplier < ActiveRecord::Base
  has_one :account
  has_one :account_history, :through => :account
end

class Account < ActiveRecord::Base
  belongs_to :supplier
  has_one :account_history
end

class AccountHistory < ActiveRecord::Base
  belongs_to :account
end

このように設定することで、 @supplier.account_history で口座履歴を取得することが出来るようになります。

has_many :through

has_many :through は別のオブジェクトを通して複数のオブジェクトの参照をしたい場合に利用します。例えば、病院で患者が医師の予約を取りますが、患者は複数の予約をすることが出来ますし、一人の医者は複数の予約を受け付けることが出来ます。この時、医者は複数の予約を持ち(予約は医者に所有されている)、患者は複数の予約を持つ(予約は患者に所有されている)と考えることが出来ます。この時、医者が自分の担当する患者を調べるときに、予約を通して複数の患者を持つと考えることが出来ます。このような場合に、 has_many :through を利用します。

上記のような状況では、以下のようにモデルを記述します。

class Physician < ActiveRecord::Base
  has_many :appointments
  has_many :patients, :through => :appointments
end
 
class Appointment < ActiveRecord::Base
  belongs_to :physician
  belongs_to :patient
end
 
class Patient < ActiveRecord::Base
  has_many :appointments
  has_many :physicians, :through => :appointments
end

上記のようにすることによって、 Physician は間接的に Patient を持つということを表現することが出来ます。たとえば、 @physician.patients とすることで @physician が対応する患者をすべて列挙することが出来ますし、 @patient.physicians とすることで、 @patient がお世話になる医者をすべて列挙することが出来ます。

多対多関連(その1)

多対多関連をRailsで表現する方法は二つありますが、ここでは has_and_belongs_to_many を使った方法を説明します。

has_and_belongs_to は関連に何も情報がないときに利用します。もし、関連についての情報を持たせたい場合はもう一つの方法である、 has_many :through を利用した手法を使う必要があります。

例えば、組立機器は複数のパーツで構成されていますが、パーツは複数の機器にまたがって利用される可能性があるとします。このときのモデルの構造としては、組立機器が複数のパーツを持ち、パーツが複数の組立機器を持つということになります。これを実現するためには以下のように記述します。

class Assembly < ActiveRecord::Base
  has_and_belongs_to_many :parts
end

class Part < ActiveRecord::Base
  has_and_belongs_to_many :assemblies
end

これによって、 @assembly.parts@part.assemblies といった書き方が出来るようになります。

多対多関連(その2)

ここでは多対多関連を実現するためのもう一つの方法である、 has_many :through を利用した方法を説明します。

実は、これは has_many :through がその例になっています。というのも、医者は複数の患者を持っていると考えることが出来ますし、患者が複数の医者を持っていると見ることもできるからです。

つまり、多対多を実現したい二つのオブジェクトに対しての参照を持つ列に加えて、関連に関する情報を持つテーブルを作成すればよいことになります。

ActiveRecord のメソッド

この下に ActiveRecord で利用することが出来るメソッドの概要を紹介します。各メソッドの利用方法は別途調べてください。

  • find: プライマリキーを利用してレコードを取得する
  • first: 指定された条件で最初のレコードを取得する
  • first!: first と同じ機能を持つが、レコードが見つからない場合は例外を出す
  • last: 指定された条件で最後のレコードを取得する
  • last!: last と同じ機能を持つが、レコードが見つからない場合は例外を出す
  • all: すべてのレコードを取得する
  • find_each: 各々のレコードに個別に処理をする
  • find_in_batches: 各々のレコードを配列にまとめて処理をする
  • where: 取得するレコードの条件を指定する
  • order: 取得するレコードの順番を指定する
  • select: データベースから取得してくる列を限定する
  • limit: 取得するレコードの数を制限する
  • offset: limit で取得するレコードをずらす
  • group: グループを作る方法を指定する
  • having: group by の列に対しての条件を指定する
  • except: 指定メソッドで追加した条件を無効化する
  • only: 指定したメソッド以外で追加した条件を無効化する
  • reorder: デフォルトで指定されている並び替えの設定を上書きする
  • reverse_order: 取得するレコードの順番を逆にする
  • joins: テーブルの結合を行う
  • includes: クエリの発行回数を抑制します
  • lock: 排他制御を行う
  • scope: よく使うクエリをメソッドとして定義する
  • find_by_*: *の部分に指定した列名を条件に検索し最初の要素を返す
  • find_all_by_*: *の部分に指定した列名を条件に検索しすべての要素を返す
  • find_last_by_*: *の部分に指定した列名を条件に検索し最後の要素を返す
  • find_by_*!: find_by_* と同じ機能を持つが、レコードが見つからない場合は例外を出す
  • find_all_by_*!: find_all_by_* と同じ機能を持つが、レコードが見つからない場合は例外を出す
  • find_last_by_*!: find_last_by_* と同じ機能を持つが、レコードが見つからない場合は例外を出す
  • first_or_create: 最初の要素があればそれを返し、無ければ create が呼ばれる
  • first_or_create!: 機能は first_or_create と同じだが、新しいレコードが不正ならば例外を出す
  • first_or_create: first_or_create では create を呼ぶところで new を呼ぶ
  • first_or_create!: first_or_create と同じ機能を持つが、新しいレコードが不正ならば例外を出す
  • pluck: 一列の情報だけを取り出す
  • exist?: レコードが存在するのかを確認する
  • count: レコードの数を数える
  • average: 指定した列の平均値を求める
  • minimum: 指定した列の最小値を求める
  • maximum: 指定した列の最大値を求める
  • sum: 指定した列の値の和を求める