Activeレコードの関連付け

このガイドでは、Activeレコードの関連付けについて説明します。 このガイドを読むことで、次の事が学べるはずです。

  • Activeレコードのモデル間で関連付けを定義する方法
  • Activeレコードの関連付けの様々なタイプについて
  • 関連付けを作ることによってモデルに追加されるメソッドの使用方法

1. 何故、関連付けを行うのか?

何故、モデル間で関連付けを行う必要があるのでしょうか? それは、コード内での共通のオペレーションをシンプルに、また簡単にするためです。 例えば、顧客(customers)と注文(orders)のモデルを持つシンプルなRailsアプリケーションがあるとします。 それぞれの顧客は多くの注文を持ちます。 関連付けが無ければ、モデルの定義は次のようになります。

class Customer < ActiveRecord::Base
end

class Order < ActiveRecord::Base
end

ここで、存在する顧客に新しい注文を追加したいとします。 すると、次のようなことを行う必要があります。

@order = Order.create(order_date: Time.now, customer_id: @customer.id)

また、顧客を削除することを考えると、同様にその顧客の注文データが全て削除されることを確認する必要があります。

@orders = Order.where(customer_id: @customer.id)
@orders.each do |order|
  order.destroy
end
@customer.destroy

Activeレコードの関連付けは、Railsに2つのモデル間に繋がりがあることを定義することで伝え、 こういったオペレーションを合理化します。 ここで顧客と注文にその設定を施した修正されたコードを確認してみましょう。

class Customer < ActiveRecord::Base
  has_many :orders, dependent: :destroy
end

class Order < ActiveRecord::Base
  belongs_to :customer
end

この変更で、特定の顧客に対して新しい注文を作成するのが容易になりました。

@order = @customer.orders.create(order_date: Time.now)

顧客の削除とその全ての注文の削除は、非常に簡単になります。

@customer.destroy

関連付けの異なるタイプについて更に知りたければ、このガイドの次のセクションを読み進めてください。 以降は関連付けによるチップスやテクニック、そしてRailsの関連付けのためのメソッドとオプションが揃ったリファレンスになります。

2. 関連付けのタイプ

Railsでは、関連付けによって2つのActiveレコードモデルの間が繋がれます。 関連付けは、宣言によってモデルに機能を追加できるように、マクロスタイルの呼び出しを使用して実装されます。 例えばbelongs_toの宣言によって、 Railsに2つのモデルのインスタンスの間の主キーと外部キー情報を保持するように指示します。 また、他にもモデルに追加出来る汎用的なメソッドが揃っています。 Railsは、6種の関連付けをサポートします。

  • belongs_to
  • has_one
  • has_many
  • has_many :through
  • has_one :through
  • has_and_belongs_to_many

以降のこのガイドでは、様々な形式の関連付けの宣言と使用方法について学んでいきます。 ただし、その前に関連付けが適切に行われる状況について紹介しておきます。

2.1 belongs_toによる関連付け

belongs_to関連付けは、宣言されたモデルが別のモデルに"属する"という意味になり、 1対1の関係でモデル同士を繋げます。 例えば、もしアプリケーションに顧客(customers)と注文(orders)があり、 各注文は1人の個客に割り当てられるような場合、 注文(order)モデルに次のように宣言を行います。

class Order < ActiveRecord::Base
  belongs_to :customer
end
belongs_to

belongs_toへの関連付けには、単数形の用語を使用しなければいけません。 もし、上記の例で顧客への関連付けで複数形をOrderモデル内で使用すると、 "uninitialized constant Order::Customers"というメッセージを受け取ることになります。 これはRailsが関連付けの名前から、自動的にクラス名を判定するためです。 関連付け名を誤って複数形にしてしまうと、同様に複数形の名前のクラスだと判定されてしまいます。

対応するマイグレーションは下記のようになります。

class CreateOrders < ActiveRecord::Migration
  def change
    create_table :customers do |t|
      t.string :name
      t.timestamps
    end

    create_table :orders do |t|
      t.belongs_to :customer
      t.datetime :order_date
      t.timestamps
    end
  end
end

2.2 has_oneによる関連付け

has_one関連付けも、1対1でモデルを繋げるように設定するものですが、 意味(と結論)が多少異なります。 この関連付けは、モデルの各インスタンスが他のモデルのインスタンスを含み、または所有することを指し示します。 例えば、もし各サプライヤー(supplier)がアプリケーションで1つのアカウント(account)を持つとすると、 サプライヤーモデルに次のように宣言します。

class Supplier < ActiveRecord::Base
  has_one :account
end
has_one

対応するマイグレーションは下記のとおりです。

class CreateSuppliers < ActiveRecord::Migration
  def change
    create_table :suppliers do |t|
      t.string :name
      t.timestamps
    end

    create_table :accounts do |t|
      t.belongs_to :supplier
      t.string :account_number
      t.timestamps
    end
  end
end

2.3 has_manyによる関連付け

has_many関連付けは、1対多でモデル同士を繋げるように指し示すものです。 この関連付けは、belongs_toによる関連付けの"反対側"でよく使われるものです。 この関連付けは、モデルの各インスタンスが他のモデルのインスタンスを複数(またはゼロ)持つという事を指し示します。 例えば、アプリケーションに顧客(customers)と注文(orders)があるとすると、 顧客モデルは下記のように宣言されます。

class Customer < ActiveRecord::Base
  has_many :orders
end

has_many関連付けの宣言をする際には、指定するモデルの名前は複数形にします。

has_many

対応するマイグレーションは下記のようになります。

class CreateCustomers < ActiveRecord::Migration
  def change
    create_table :customers do |t|
      t.string :name
      t.timestamps
    end

    create_table :orders do |t|
      t.belongs_to :customer
      t.datetime :order_date
      t.timestamps
    end
  end
end

2.4 has_many :throughによる関連付け

has_many :through関連付けは、多対多の関係でモデルと関連付けるのに使用されます。 この関連付けが宣言されたモデルが、他の0個以上のインスタンスと、第3のモデルを通して(through)紐付けられることを指し示します。 例えば、患者(patients)が医師(physicians)を確認して予約(appointments)を行う医療の現場のシステムについて考えてみます。 この場合、関連付けの宣言は下記のようになります。

#医師クラス
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
has_many_through

対応するマイグレーションは下記のようになります。

class CreateAppointments < ActiveRecord::Migration
  def change
    create_table :physicians do |t|
      t.string :name
      t.timestamps
    end

    create_table :patients do |t|
      t.string :name
      t.timestamps
    end

    create_table :appointments do |t|
      t.belongs_to :physician
      t.belongs_to :patient
      t.datetime :appointment_date
      t.timestamps
    end
  end
end

結合モデルのコレクションは、APIを通して管理することが出来るようになります。 例えば、下記のような割り当てを行うと、

physician.patients = patients

新しい結合モデルは、新たに関連付けられたオブジェクトのために作成され、 無くなっている行があれば、そのレコードは削除されます。

結合モデルの自動的に行われる削除は直接行われ、destroyのコールバックはトリガされません。

has_many :through関連付けは、入れ子のhas_many関連付けを通した"ショートカット"の設定にも便利です。 例えば、ドキュメントが多くのセクションを持ち、セクションが多くの段落を持つ場合、 ドキュメント内の全てのセクションのシンプルなコレクションの取得が必要となることがあるかもしれません。 これは、下記の設定によって取得出来るようになります。

class Document < ActiveRecord::Base
  has_many :sections
  has_many :paragraphs, through: :sections
end

class Section < ActiveRecord::Base
  belongs_to :document
  has_many :paragraphs
end

class Paragraph < ActiveRecord::Base
  belongs_to :section
end

through: :sectionsを指定することで、Railsは関係性を把握します。

@document.paragraphs

2.5 has_one :throughによる関連付け

has_one :throughの関連付けは、1対1の関係で他のモデルを関連付けるのに使用されます。 この関連付けが宣言されたモデルが、他のモデルの1つのインスタンスと、第3のモデルを通して(through)紐付けられることを指し示します。 例えば、もし各サプライヤー(提供者)が1つのアカウントを持ち、各アカウントが1つのアカウント履歴に関連付けられている場合、 サプライヤーのモデルは次のようにすることが出来ます。

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
has_one_through

このためのマイグレーションは、次のようになります。

class CreateAccountHistories < ActiveRecord::Migration
  def change
    create_table :suppliers do |t|
      t.string :name
      t.timestamps
    end

    create_table :accounts do |t|
      t.belongs_to :supplier
      t.string :account_number
      t.timestamps
    end

    create_table :account_histories do |t|
      t.belongs_to :account
      t.integer :credit_rating
      t.timestamps
    end
  end
end

2.6 has_and_belongs_to_many関連付け

has_and_belongs_to_manyの関連付けは直接、介在するモデル無しで、他のモデルとの多対多の繋がりを作成します。 例えば、もしあなたのアプリケーションが、"組み立て部品"(assemblies)と"パーツ"(parts)を持ち、 各組み立て部品は多くのパーツを持ち、各パーツは多くの組み立て部品で使用されるとする場合、 これを次のように宣言することが可能です。

class Assembly < ActiveRecord::Base
  has_and_belongs_to_many :parts
end

class Part < ActiveRecord::Base
  has_and_belongs_to_many :assemblies
end
habtm

これに対応するマイグレーションは、次のようになります。

class CreateAssembliesAndParts < ActiveRecord::Migration
  def change
    create_table :assemblies do |t|
      t.string :name
      t.timestamps
    end

    create_table :parts do |t|
      t.string :part_number
      t.timestamps
    end

    create_table :assemblies_parts do |t|
      t.belongs_to :assembly
      t.belongs_to :part
    end
  end
end

2.7 belongs_toとhas_oneのどちらを選択すべきか

もし2つのモデルを1対1の関連性で設定したい場合、 片方にはbelongs_toを、もう片方にはhas_oneを追加する必要があります。 どのようにして、どちらがどちらになるかを決めればいいのでしょうか?

この識別は、外部キーがどこに配置されているかに依りますが、 (これはbelongs_to関連付けの宣言クラスのテーブルに対して行われます) 実際のデータの意味付けを考えて指定されるべきです。 has_one関連付けは、「いずれかの1つがあなたの物です」という意味になり、 そのいずれかの1つは、あなたへの指定を返す事になります。 例えば、「アカウントがサプライヤー(提供者)を所有している」というよりは、 「サプライヤー(提供者)がアカウントを所有している」と言ったほうが、より自然な意味付けと言えます。 この考え方により、下記のような正しい関連付けを作ることが出来ます。

class Supplier < ActiveRecord::Base
  has_one :account
end

class Account < ActiveRecord::Base
  belongs_to :supplier
end

これに対応するマイグレーションは次のようになります。

class CreateSuppliers < ActiveRecord::Migration
  def change
    create_table :suppliers do |t|
      t.string  :name
      t.timestamps
    end

    create_table :accounts do |t|
      t.integer :supplier_id
      t.string  :account_number
      t.timestamps
    end
  end
end

t.integer :supplier_idを使用して、外部キーの名前付けを明確に指定しています。 現状のRailsのバージョンでは、代わりにt.references :supplierを使用することで、 この実装の詳細を抽象化することが出来ます。

2.8 has_many :throughとhas_and_belongs_to_manyのどちらを選択すべきか

Railsはモデル間の多対多の関連付けの宣言に対して、異なる2つの方法を提供します。 シンプルなのは、直接関連付けを定義するhas_and_belongs_to_manyを使用する方法です。

class Assembly < ActiveRecord::Base
  has_and_belongs_to_many :parts
end

class Part < ActiveRecord::Base
  has_and_belongs_to_many :assemblies
end

2つ目の多対多の関連付けの宣言方法は、has_many :throughを使用する方法です。 これは結合モデルを通して、間接的に関連付けを作ります。

class Assembly < ActiveRecord::Base
  has_many :manifests
  has_many :parts, through: :manifests
end

class Manifest < ActiveRecord::Base
  belongs_to :assembly
  belongs_to :part
end

class Part < ActiveRecord::Base
  has_many :manifests
  has_many :assemblies, through: :manifests
end

関連付けされるモデルが独立したものとして動作する必要があるのであれば、 単純にhas_many :through関連付けを設定すべきです。 もし関連付けのモデルが何かしら必要とされる事が無いのであれば、 単純にhas_and_belongs_to_many関連付けを設定するばよいでしょう。 (ただし、結合テーブルがデータベース内に作られることを覚えておく必要があります)

もし、検証(Validation)やコールバック(callback)、結合モデル上の他の属性が必要なのであれば、 has_many :throughを使用すべきです。

2.9 ポリモーフィック関連付け

関連付けにもう少し踏み込んだ絡み付を行うのが、ポリモーフィック関連付けです。 ポリモーフィック関連付けを使用することで、モデルは1つの関連付けで、1つ以上の他のモデルに属する事が可能になります。 例えば、従業員(employee)モデルまたは商品(product)モデルのどちらかに属する写真(picture)モデルを持っているとします。 これは、下記のように宣言することが出来ます。

class Picture < ActiveRecord::Base
  belongs_to :imageable, polymorphic: true
end

class Employee < ActiveRecord::Base
  has_many :pictures, as: :imageable
end

class Product < ActiveRecord::Base
  has_many :pictures, as: :imageable
end

belongs_toのポリモーフィックな宣言は、他のモデルが使用することが出来るインターフェースとして設定すると考えることが出来ます。 従業員(Employee)モデルのインスタンスから、@employee.picturesとすることで、 写真のコレクションを取得することが出来ます。

同様に、@product.picturesも取得することが出来ます。

Pictureモデルのインスタンスを持つ場合、 @picture.imageableを通して、その親を取得することが可能です。 これを動作させるには、ポリモーフィックのインターフェースとして宣言されるモデル内に、 外部キーカラムとtypeカラムの両方を宣言する必要があります。

class CreatePictures < ActiveRecord::Migration
  def change
    create_table :pictures do |t|
      t.string  :name
      t.integer :imageable_id
      t.string  :imageable_type
      t.timestamps
    end
  end
end

マイグレーションはt.references形式を使用することで、これをシンプルにすることが出来ます。

class CreatePictures < ActiveRecord::Migration
  def change
    create_table :pictures do |t|
      t.string :name
      t.references :imageable, polymorphic: true
      t.timestamps
    end
  end
end
polymorphic

2.10 自己結合

データモデルを設計する際に、時にモデルは自分自身に関連付けを持たせるべきである、という事に気づく事があります。 例えば、全ての従業員(employees)を1つのデータベースモデルに格納し、 管理者(manager)と部下(subordinates)のような関連付けが追えるようにしたいと考えたとします。 このような場合、自己結合関連付けを使用してモデル化をすることが可能です。

class Employee < ActiveRecord::Base
  has_many :subordinates, class_name: "Employee",
                          foreign_key: "manager_id"

  belongs_to :manager, class_name: "Employee"
end

この設定により、@employee.subordinates@employee.managerを取得出来るようになります。

3 TIPS、トリップ、警告

ここでは、Activeレコード関連付けの知っておくべき幾つかの有効なテクニックについて、説明します。

  • キャッシュの制御
  • 名前衝突の回避
  • スキーマの更新
  • 関連付けスコープの制御
  • 双方向関連付け

3.1 キャッシュの制御

関連付けのメソッドの全てはキャッシュを中心に構築され、 以降の操作のために直近の有効なクエリーの結果を保持します。 キャッシュはメソッドを跨いで共有されます。 例えば、

customer.orders         # データベースからordersを取得
customer.orders.size    # ordersのキャッシュの複製を使用
customer.orders.empty?  # ordersのキャッシュの複製を使用

ただし、もしアプリケーションの他の部分によるデータの変更があるかもしれないため、キャッシュをリロードしたい場合は、 どうすれば良いでしょうか? その場合は関連付けの呼び出しにtrueを渡すだけです。

customer.orders               # データベースからordersを取得します
customer.orders.size          # ordersのキャッシュの複製を使用
customer.orders(true).empty?  # ordersのキャッシュの複製は破棄され、
                              # データベースから取得されます

3.2 名前衝突の回避

関連付けのための名付けを、自由に行うことは出来ません。 何故なら、関連付けを作成すると、モデルにその名前を使用したメソッドが追加されるため、 ActiveRecord::Baseのインスタンメソッドとして既に使用されている名前を、 関連付けの名前として与えられると問題が発生するからです。 関連付けのメソッドが基盤となるメソッドを上書きし、破綻してしまいます。 例えば、"attributes"または"connection"は、関連付けには不適切な名前です。

3.3 スキーマの更新

関連付けは非常に便利ではありますが、万能な魔法というわけではありません。 あなたは、あなたの関連付けにマッチするデータベースのスキーマに対して、 継続してメンテナンスをしていく責任があります。 実際には、あなたが作成した関連付けがどのような物なのかに依って2つの事を意味します。 belongs_to関連付けでは外部キーを作成する必要があり、 has_and_belongs_to_many関連付けでは適切な結合テーブルを作成する必要があります。

3.3.1 belongs_to関連付けのための外部キーの作成

belongs_to関連付けを定義する際に、適切な外部キーを作成する必要があります。 例えば、次のモデルを考えるとすると、

class Order < ActiveRecord::Base
  belongs_to :customer
end

この宣言は、適切なorders(注文)テーブル上での外部キー宣言によって、後方支援される必要があります。

class CreateOrders < ActiveRecord::Migration
  def change
    create_table :orders do |t|
      t.datetime :order_date
      t.string   :order_number
      t.integer  :customer_id
    end
  end
end

もし、基板となるモデルを構築した後に関連付けを作成することになった場合は、 必須となる外部キーを提供するadd_columnマイグレーションを作成する必要がある事を覚えておいてください。

3.3.2 has_and_belongs_to_many関連付けのための結合テーブルの作成

has_and_belongs_to_manyを作成する場合、 その結合テーブルを明示的に作成する必要があります。 :join_tableオプションを使用した明示的な結合テーブルの名前の指定が無い限り、 Activeレコードはクラス名の辞書順によって名前を作成します。 そのため、customer(顧客)とorder(注文)モデルの結合は、デフォルトで辞書順により"c"が"o"より優先されて、 "customers_orders"のテーブル名が与えられます。

モデル名間の優先順位は、文字列用に<演算子を使用して計算されます。 これは、もし文字列の長さが異なり、且つ始まりから最も短い単語(?)部分で同じ文字列が使用されている場合、 長い文字列は辞書順としては短いものより優先順位が高いとみなされます。 例えば、"paper_boxes"のテーブル名と"papers"のテーブルは、 "paper_boxes"の名前の長さから、"papers_paper_boxes"の名前で結合テーブルが生成されることが予想されますが、 実際には"paper_boxes_papers"という名前の結合テーブルが生成されます。 (何故なら、アンダースコア('_')は一般的なエンコーディングの辞書順では、's'より優先度順位は低いからです。)

名前が何であれ、適切なマイグレーションを使用して結合テーブルを手動で生成しなければいけません。 例えば、下記の関連付けがあったとします。

class Assembly < ActiveRecord::Base
  has_and_belongs_to_many :parts
end

class Part < ActiveRecord::Base
  has_and_belongs_to_many :assemblies
end

これらは、マイグレーションによって作成されるassemblies_partsテーブルの後方支援を必要とします。 このテーブルは主キー無しで作成されるべきです。

class CreateAssembliesPartsJoinTable < ActiveRecord::Migration
  def change
    create_table :assemblies_parts, id: false do |t|
      t.integer :assembly_id
      t.integer :part_id
    end
  end
end

create_tableid: falseを渡しているのは、テーブルがモデルを表さないからです。 これは適切に関連付けが動作するために必要なことです。 もし、has_and_belongs_to_many関連付けの中で、 モデルIDの破損、ID衝突による例外といった不審な挙動を見かける場合は、 あなたがこの事を忘れている可能性があります。

3.4 関連付けスコープの制御

デフォルトでは、関連付けはオブジェクトを現在のモジュールのスコープ内でしか探しません。 これはActiveレコードモデルをモジュール内に定義する際に、重要になる可能性があります。 例えば、

module MyApplication
  module Business
    class Supplier < ActiveRecord::Base
       has_one :account
    end

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

これは正常に動作します。 何故なら、Supplier(供給者)とAccountのクラスは同じスコープ内で定義されているからです。 ただし、下記の例は動作しません。 何故なら、Supplier(供給者)とAccountのクラスが異なるスコープで定義されているからです。

module MyApplication
  module Business
    class Supplier < ActiveRecord::Base
       has_one :account
    end
  end

  module Billing
    class Account < ActiveRecord::Base
       belongs_to :supplier
    end
  end
end

異なる名前空間内のモデルに、モデルを関連付けるには、 関連付けの定義で完全クラス名を指定しなければいけません。

module MyApplication
  module Business
    class Supplier < ActiveRecord::Base
       has_one :account,
        class_name: "MyApplication::Billing::Account"
    end
  end

  module Billing
    class Account < ActiveRecord::Base
       belongs_to :supplier,
        class_name: "MyApplication::Business::Supplier"
    end
  end
end

3.5 双方向の関連付け

関連付けが双方向に動作するのは通常のことで、 異なる2つのモデルそれぞれに定義が必要になります。

class Customer < ActiveRecord::Base
  has_many :orders
end

class Order < ActiveRecord::Base
  belongs_to :customer
end

デフォルトでは、Activeレコードはこれらの関連付けの間の繋がりを把握していません。 これは、2つのオブジェクトの複製を作ることで、同期が外れることを知ることが出来ます。

c = Customer.first
o = c.orders.first
c.first_name == o.customer.first_name # => true
c.first_name = 'Manny'
c.first_name == o.customer.first_name # => false

これはcとo.customerは同じ値のデータですが、異なるメモリ上に置かれ、 片方が変更されることで自動的にもう片方も更新されるという事が無いためです。 Activeレコードは:inverse_ofオプションを提供することで、 これらの関係への通知を可能にしてくれます。

class Customer < ActiveRecord::Base
  has_many :orders, inverse_of: :customer
end

class Order < ActiveRecord::Base
  belongs_to :customer, inverse_of: :orders
end

この変更により、Activeレコードはcutomerオブジェクトの服せうを1つだけ読み込むことでデータの矛盾を防ぎ、 アプリケーションがより効率的になります。

c = Customer.first
o = c.orders.first
c.first_name == o.customer.first_name # => true
c.first_name = 'Manny'
c.first_name == o.customer.first_name # => true

inverse_ofのサポートが制限されるケースが幾つか存在します。

  • :throughの関連付けでは動作しません。
  • :polymorphicの関連付けでは動作しません。
  • :asの関連付けでは動作しません。
  • belongs_toへのhas_manyのinverse_ofは無視されます。(翻訳に自信なし)

3. チップス、テクニック、注意点

以降のセクションでは、追加されるメソッドと関連付けを定義する際に使用することが出来るオプションを含む、 各関連付けの詳細について説明していきます。

4.1 belongs_to関連付けについて

belongs_toは、他のモデルとの1対1の関連付けを作ります。 データベースの観点から言えば、この関連付けは、このクラスが外部キーを含むという事になります。 もし、そのクラスでは無い別のクラスが外部キーを含む場合は、 belongs_toの代わりにhas_oneを使用するべきです。

4.1.1 belongs_toによって追加されるメソッド

belongs_to関連付けを定義すると、 定義されたクラスには、関連付けに関する自動的に4つのメソッドが与えられます。

  • association(force_reload = false)
  • association=(associate)
  • build_association(attributes = {})
  • create_association(attributes = {})

これらのメソッドの全ては、belongs_toの第1引数として渡されたシンボルの関連付けを置き換えます。 例えば、次のように定義された場合、

class Order < ActiveRecord::Base
  belongs_to :customer
end

各order(注文)モデルのインスタンスは、下記のメソッドを持つ事になります。

customer
customer=
build_customer
create_customer

新しいhas_oneまたはbelongs_toの関連付けを初期化する際には、 has_manyまたはhas_and_belongs_to_manyに使用されるassociation.buildメソッドでは無く、 build_接頭辞を関連付けのbuildに使用しなければいけません。 createであれば、create_接頭辞を使用します。

4.1.1.1 association(force_reload = false)

associationメソッドは、もし既に存在すれば、その関連付けのオブジェクトを返します。 もし関連付けのオブジェクトが見つからなければ、nilを返します。

@customer = @order.customer

もし、関連付けられたオブジェクトが、このオブジェクトのために既にデータベースから取得されているのであれば、 キャッシュされているものが返されます。 この振る舞いを上書きする(または、強制的にデータベースを読みに行く)には、 force_reload引数にtrueを渡してください。

4.1.1.2 association=(associate)

association=メソッドはそのオブジェクトに対して、 関連付けのオブジェクトを割り当てます。 これは裏で、関連するオブジェクトの主キーの値を、 このオブジェクトの外部キーに割り当てている事を意味します。

@order.customer = @customer
4.1.1.3 build_association(attributes = {})

build_associationメソッドは、関連する型の新しいオブジェクトを返します。 このオブジェクトは渡された属性からインスタンス化が行われ、 このオブジェクトの外部キーを通してリンクが設定されますが、 関連付けられたオブジェクトはまだ保存されません。

@customer = @order.build_customer(customer_number: 123,
                                  customer_name: "John Doe")
4.1.1.4 create_association(attributes = {})

create_associationメソッドは、関連付する型の新しいオブジェクトを返します。 このオブジェクトは引数として渡された属性(attributes)からインスタンス化され、 このオブジェクトの外部キーを通してリンクが設定され、 関連付けのモデル上で指定された全ての検証をパスすると、関連付けられたオブジェクトは保存されます。

@customer = @order.create_customer(customer_number: 123,
                                   customer_name: "John Doe")
4.1.2 belongs_toのオプション

Railsが、ほとんどの状況に適した気の利いたデフォルト設定を使用してくれる一方、 belongs_to関連付け参照の振る舞いを、カスタマイズしたいという事があるかもしれません。 そのようなカスタマイズは、関連付けを作成する際に、オプションとスコープブロックを渡すことで簡単に行うことが可能です。 例えば、この関連付けは、そのような2つのオプションを使用します。

class Order < ActiveRecord::Base
  belongs_to :customer, dependent: :destroy,
    counter_cache: true
end

belongs_to関連付けは、下記のオプションをサポートします。

  • :autosave
  • :class_name
  • :counter_cache
  • :dependent
  • :foreign_key
  • :inverse_of
  • :polymorphic
  • :touch
  • :validate
4.1.2.1 :autosave

:autosaveオプションをtrueに設定すると、 親のオブジェクトを保存する度に、読み込まれたメンバーを保存し、 削除するものとしてマークされたメンバーを削除します。

4.1.2.2 :class_name

他のモデルの名前が、関連付くものの名前から引き出すことが出来ない場合、 :class_nameオプションを使用して、モデル名を抵抗することが可能です。 例えば、order(注文)がcustomer(顧客)に属しているが、 customer(顧客)が含まれる実際のモデルの名前がPatronである場合、 次のように設定することが出来ます。

class Order < ActiveRecord::Base
  belongs_to :customer, class_name: "Patron"
end
4.1.2.3 :counter_cache

:counter_cache(カウンターキャッシュ)オプションは、より効率的に従属するオブジェクトの数を調べるために使用されます。 下記の2つのモデルがあると想定します。

class Order < ActiveRecord::Base
  belongs_to :customer
end

class Customer < ActiveRecord::Base
  has_many :orders
end

これらの定義では、@customer.orders.sizeの値を調べるには、 データベースに対してCOUNT(*)クエリーを発行する必要があります。 従属するモデルにカウンターキャッシュを追加することで、この発行を避ける事が出来ます。

class Order < ActiveRecord::Base
  belongs_to :customer, counter_cache: true
end
class Customer < ActiveRecord::Base
  has_many :orders
end

この宣言により、Railsは最新の値のキャッシュを保持するようになり、 sizeメソッドとして応答して、この値を返します。

ただし、:counter_cacheオプションはbelongs_to宣言を含むモデル上に指定されますが、 実際のデータベースのカラムは関連付けられるモデルに追加されなければいけません。 上記のケースではれば、Customerモデルに対してorders_countという名前が追加される必要があります。 必要に応じて、デフォルトのカラム名を上書きすることが可能です。

class Order < ActiveRecord::Base
  belongs_to :customer, counter_cache: :count_of_orders
end
class Customer < ActiveRecord::Base
  has_many :orders
end

カウンターキャッシュのカラムはattr_readonlyを通じて、 読み込み専用のモデルのリストに追加されます。

4.1.2.4 :dependent

:destroy:dependentオプションを設定すると、 このオブジェクトの削除の際に、関連付けられたオブジェクト上でそのオブジェクトを削除するためのdestroyメソッドが呼び出されます。 もし:delete:dependentオプションを設定すると、 このオブジェクトの削除の際に、destroyメソッドの呼び出し無しで、関連付けられたオブジェクトは削除されます。 もし:restrict:dependentオプションを設定すると、 このオブジェクトを削除しようとした際に、もし関連付けられたオブジェクトが存在すると、 ActiveRecord::DeleteRestrictionErrorが発生します。

このオプションを他のクラスへのhas_manyに繋がる、 belongs_toの関連付け上で指定すべきではありません。 これはデータベース内で、いわゆる親が居ないレコードを存在させてしまう事に繋がります。

4.1.2.5 :foreign_key

便利な事にRailsは、このモデル上の、関連付け先のモデルの名前に_id接頭辞を追加したカラムを、 外部キーを保持するためのカラムとみなしてくれます。 :foreign_keyオプションは、直接外部キーの名前を設定することを可能にしてくれます。

class Order < ActiveRecord::Base
  belongs_to :customer, class_name: "Patron",
                        foreign_key: "patron_id"
end

あるケースでは、Railsは外部キーのカラムを作成しないことがあります。 その場合は、マイグレーションの一部で明示的に定義する必要があります。

4.1.2.6 :inverse_of

:inverse_ofオプションは、この関連付けを逆さまにする、 has_manyまたはhas_one関連付けの名前を指定します。 :polymorphicオプションと組み合わせて、動作させることは出来ません。

class Customer < ActiveRecord::Base
  has_many :orders, inverse_of: :customer
end

class Order < ActiveRecord::Base
  belongs_to :customer, inverse_of: :orders
end
4.1.2.7 :polymorphic

:polymorphicオプションへtrueを渡すと、それはポリモーフィック関連付けであることを指し示す事になります。 ポリモーフィック関連付けについては、既にこのガイド内で詳細な説明がされています。

4.1.2.8 :touch

:touchオプションに:trueを設定すると、このオブジェクトが保存または削除される度に、 関連付けられたオブジェクトのupdated_at、またはupdated_onタイムスタンプが、 その時の時刻に設定されます。

class Order < ActiveRecord::Base
  belongs_to :customer, touch: true
end

class Customer < ActiveRecord::Base
  has_many :orders
end

上記のケースでは、order(注文)を保存または削除すると、関連付くcustomer(顧客)上のタイムスタンプが更新されます。 また、特定のタイムスタンプを更新するように指定することも可能です。

class Order < ActiveRecord::Base
  belongs_to :customer, touch: :orders_updated_at
end
4.1.2.9 :validate

もし:validateオプションをtrueに設定すると、 関連付けられたオブジェクトは、このオブジェクトが保存される度に検証されることになります。 デフォルトではfalse:で、関連付けられたオブジェクトは、このオブジェクトが保存される際に検証されません。

4.1.3 belongs_toのスコープ

belongs_toを使用する際に、クエリーをカスタマイズしたいというケースがあるかもしれません。 そのようなカスタマイズは、スコープのブロックを通して行うことが可能です。 例えば、

class Order < ActiveRecord::Base
  belongs_to :customer, -> { where active: true },
                        dependent: :destroy
end

スコープブロック内では、標準のクエリーメソッド を幾つか使用することが可能です。 該当するものを下記に示します。

  • where
  • includes
  • readonly
  • select
4.1.3.1 where

whereメソッドは、関連付くオブジェクトが満たすべき条件を指定することを可能にしてくれます。

class Order < ActiveRecord::Base
  belongs_to :customer, -> { where active: true }
end
4.1.3.2 includes

includesメソッドを使用して、2番目として要求されるeager-load(熱心な読み込み)されるべき、 関連付けを指定する事が可能です。 例として、次のモデルがあるとします。

class LineItem < ActiveRecord::Base
  belongs_to :order
end

class Order < ActiveRecord::Base
  belongs_to :customer
  has_many :line_items
end

class Customer < ActiveRecord::Base
  has_many :orders
end

もし、line items(品目)から、@line_item.order.customerとして、 しきりにcustomers(顧客)を直接取得するのであれば、 customersをline itemsのorderへ含めることで、コードをより効率的にすることが出来ます。

class LineItem < ActiveRecord::Base
  belongs_to :order, -> { includes :customer }
end

class Order < ActiveRecord::Base
  belongs_to :customer
  has_many :line_items
end

class Customer < ActiveRecord::Base
  has_many :orders
end

直に接している関連付けのためにincludesを使用する必要はありません。 もし、Order belongs_to :customerが指定されていれば、 customerはそれが必要な時に、自動的にeager-loadされます。

4.1.3.3 readonly

readonlyを使用すると、関連付けを通して取得された関連オブジェクトは読み込み専用になります。

4.1.3.4 select

selectメソッドは、関連付けオブジェクトのデータ取得時に使用するSQLのSELECT句の上書きをさせてくれます。 デフォルトでは、Railsは全てのカラムを取得します。

もしselectメソッドをbelongs_to関連付けの上で使用するのであれば、 :foreign_keyオプションも正しい結果を得るための保険として設定しておくべきです。

4.1.4 関連付けオブジェクトは存在するか?

association.nil?メソッドを使用することで、関連付けオブジェクトが存在するかを確認することが出来ます。

if @order.customer.nil?
  @msg = "No customer found for this order"
end
4.1.5 オブジェクトはいつ保存されるか?

オブジェクトへのbelongs_to関連付けの割り当ては、 自動的にオブジェクトの保存を行いません。 同様に関連付けられたオブジェクトも、保存を行いません。

4.2 has_one関連付けについて

has_one関連付けは、他のモデルとの1対1の関連付けを作ります。 データベースの観点から言えば、この関連付けは、他のクラスが外部キーを含むとも言えます。 もし、このクラスが外部キーを含む場合は、 has_oneの代わりにbelongs_toを使用すべきです。

4.2.1 has_oneによって追加されるメソッド

has_one関連付けを定義すると、 定義されたクラスには、関連付けに関する4つのメソッドが自動的に与えられます。

  • association(force_reload = false)
  • association=(associate)
  • build_association(attributes = {})
  • create_association(attributes = {})

これらのメソッドの全ては、has_oneの第1引数として渡されたシンボルの関連付けを置き換えます。 例えば、次のように定義された場合、

class Supplier < ActiveRecord::Base
  has_one :account
end

Supplier(提供者)モデルのインスタンスは、下記のメソッドを持つ事になります。

account
account=
build_account
create_account

新しいhas_oneまたはbelongs_toの関連付けを初期化する際には、 has_manyまたはhas_and_belongs_to_manyに使用されるassociation.buildメソッドでは無く、 build_接頭辞を関連付けのbuildに使用しなければいけません。 createであれば、create_接頭辞を使用します。

4.2.1.1 association(force_reload = false)

associationメソッドは、もし既に存在すれば、その関連付けのオブジェクトを返します。 もし関連付けのオブジェクトが見つからなければ、nilを返します。

@account = @supplier.account

もし、関連付けられたオブジェクトが、このオブジェクトのために既にデータベースから取得されているのであれば、 キャッシュされているものが返されます。 この振る舞いを上書きする(または、強制的にデータベースを読みに行く)には、 force_reload引数にtrueを渡してください。

4.2.1.2 association=(associate)

association=メソッドはそのオブジェクトに対して、 関連付けのオブジェクトを割り当てます。 これは裏で、関連するオブジェクトの主キーの値を、 このオブジェクトの外部キーに割り当てている事を意味します。

@supplier.account = @account
4.2.1.3 build_association(attributes = {})

build_associationメソッドは、関連する型の新しいオブジェクトを返します。 このオブジェクトは渡された属性からインスタンス化され、 その外部キーを通してリンクが設定されますが、 関連付けられたオブジェクトはまだ保存されません。

@account = @supplier.build_account(terms: "Net 30")
4.2.1.4 create_association(attributes = {})

create_associationメソッドは、関連付する型の新しいオブジェクトを返します。 このオブジェクトは引数として渡された属性(attributes)からインスタンス化が行われ、 リンクはその外部キーを通して設定され、 関連付けのモデル上で指定された全ての検証をパスすると、関連付けられたオブジェクトは保存されます。

@account = @supplier.create_account(terms: "Net 30")
4.2.2 has_oneのオプション

Railsが、ほとんどの状況に適した気の利いたデフォルト設定を使用してくれる一方、 has_one関連付け参照の振る舞いを、カスタマイズしたいという事があるかもしれません。 そのようなカスタマイズは、関連付けを作成する際に、オプションを渡すことで簡単に行うことが可能です。 例えば、この関連付けは、そのような2つのオプションを使用します。

class Supplier < ActiveRecord::Base
  has_one :account, class_name: "Billing", dependent: :nullify
end

has_one関連付けは、下記のオプションをサポートします。

  • :as
  • :autosave
  • :class_name
  • :dependent
  • :foreign_key
  • :inverse_of
  • :primary_key
  • :source
  • :source_type
  • :through
  • :validate
4.2.2.1 :as

:asオプションの設定は、ポリモーフィズムの関連付けであることを指し示します。 ポリモーフィック関連付けについては、既にこのガイド内で詳細な説明がされています。

4.2.2.2 :autosave

:autosaveオプションをtrueに設定すると、 親のオブジェクトを保存する度に、読み込まれたメンバーを保存し、 削除するものとしてマークされたメンバーを削除します。

4.2.2.3 :class_name

他のモデルの名前が、関連付くものの名前から引き出すことが出来ない場合、 :class_nameオプションを使用して、モデル名を抵抗することが可能です。 例えば、supplier(提供者)がaccount(アカウント)を持つが、 accounts(アカウント)が含まれる実際のモデルの名前がBilling(請求書)である場合、 次のように設定することが出来ます。

class Supplier < ActiveRecord::Base
  has_one :account, class_name: "Billing"
end
4.2.2.4 :dependent

オーナー(持ち主)が削除された際の、関連付けられたオブジェクトの制御を行います。

  • :destroyを指定されると、関連付けられたオブジェクトも削除します。
  • :deleteが指定されると、データベースから直接、関連付けられたオブジェクトが削除されます。 (そのため、コールバックが実行されません。)
  • :nullifyが指定されると、外部キーにNULLが設定されます。 コールバックは実行されません。
  • :restrict_with_exceptionが指定されると、関連付けられたレコードが存在する場合、例外が発生します。
  • :restrict_with_errorが指定されると、関連付けられたオブジェクトが存在する場合、オーナー(持ち主)に対してエラーが追加されます。
4.2.2.5 :foreign_key

便利な事にRailsは、もう1つのモデル上で、このモデルの名前に_id接頭辞を追加したカラムを、 外部キーを保持するためのカラムとみなしてくれます。 :foreign_keyオプションは、直接外部キーの名前を設定することを可能にしてくれます。

class Supplier < ActiveRecord::Base
  has_one :account, foreign_key: "supp_id"
end

あるケースでは、Railsは外部キーのカラムを作成しないことがあります。 その場合は、マイグレーションの一部で明示的に定義する必要があります。

4.2.2.6 :inverse_of

:inverse_ofオプションは、この関連付けを逆さまにする、 belongs_to関連付けの名前を指定します。 :throughまたは:asオプションと組み合わせて動作させることは出来ません。

class Supplier < ActiveRecord::Base
  has_one :account, inverse_of: :supplier
end

class Account < ActiveRecord::Base
  belongs_to :supplier, inverse_of: :account
end
4.2.2.7 :primary_key

便利な事に、Railsはこのモデルのidを、主キーを保持するのに使用するカラムとみなしてくれます。 :primary_keyオプションを使用して、これを上書きし、明示的に主キーを指定することが可能です。

4.2.2.8 :source

:sourceオプションは、has_one :through関連付けのために、 sourceの関連付け名を指定します。

4.2.2.9 :source_type

:source_typeオプションは、ポリモーフィック関連付けを通して処理されるhas_one :through関連付けのために、 source関連付けの型を指定します。(翻訳に自信なし)

4.2.2.10 :through

:throughオプションは、クエリーの実行を通す結合テーブルを指定します。 has_one :through関連付けは、既にこのガイド内で詳細な説明がされています。

4.2.2.11 :validate

もし:validateオプションをtrueに設定すると、 関連付けられたオブジェクトは、このオブジェクトが保存される度に検証されることになります。 デフォルトではfalse:で、関連付けられたオブジェクトは、このオブジェクトが保存される際に検証されません。

4.2.3 has_oneのスコープ

has_oneを使用する際に、クエリーをカスタマイズしたいというケースがあるかもしれません。 そのようなカスタマイズは、スコープのブロックを通して行うことが可能です。 例えば、

class Supplier < ActiveRecord::Base
  has_one :account, -> { where active: true }
end

スコープブロック内では、標準のクエリーメソッドを幾つか使用することが可能です。 該当するものを下記に示します。

  • where
  • includes
  • readonly
  • select
4.2.3.1 where

whereメソッドは、関連付くオブジェクトが満たすべき条件を指定することを可能にしてくれます。

class Supplier < ActiveRecord::Base
  has_one :account, -> { where "confirmed = 1" }
end
4.2.3.2 includes

この関連付けが使用される際に、テーブルの繋がりが2階層目として要求されるeager-load(熱心な読み込み)されるべきテーブルを、 includesメソッドを使用することで、その関連付けを指定する事が可能です。 例として、次のモデルがあるとします。

class Supplier < ActiveRecord::Base
  has_one :account
end

class Account < ActiveRecord::Base
  belongs_to :supplier
  belongs_to :representative
end

class Representative < ActiveRecord::Base
  has_many :accounts
end

もし、suppliers(提供者)から、@supplier.account.representativeとして、 しきりにrepresentatives(代理人)を直接取得するのであれば、 representativesをsuppliersのaccountsに含めることで、コードをより効率的にすることが出来ます。

class Supplier < ActiveRecord::Base
  has_one :account, -> { includes :representative }
end

class Account < ActiveRecord::Base
  belongs_to :supplier
  belongs_to :representative
end

class Representative < ActiveRecord::Base
  has_many :accounts
end
4.2.3.3 readonly

readonlyメソッドを使用すると、関連付けを通して取得された関連オブジェクトは読み込み専用になります。

4.2.3.4 select

selectメソッドは、関連付けオブジェクトのデータ取得時に使用するSQLのSELECT句の上書きをさせてくれます。 デフォルトでは、Railsは全てのカラムを取得します。

4.2.4 関連付けオブジェクトは存在するか?

association.nil?メソッドを使用することで、関連付けオブジェクトが存在するかを確認することが出来ます。

if @supplier.account.nil?
  @msg = "No account found for this supplier"
end
4.2.5 オブジェクトはいつ保存されるか?

has_one関連付けにオブジェクトに割り当てる際に、 オブジェクトは自動的に保存されます(それの外部キーを更新するために)。 また、その外部キーも変更されるため、置き換えられるオブジェクトも自動的に保存されます。

これらのいずれかの保存が検証エラーによって失敗した場合、割り当て(代入)文はfalseを返し、 割り当て自体はキャンセルされます。

もし、オブジェクトを保存すること無く、has_one関連付けにオブジェクトを割り当てたいのであれば、 association.buildメソッドを使用してください。

4.3 has_many関連付けについて

has_many関連付けは、他のモデルに対して1対多の関係性を構築します。 データベースの観点からはこの関連付けは、他のクラスがこのクラスのインスタンスを参照する外部キーを持つと言えます。

4.3.1 has_manyによって追加されるメソッド

has_many関連付けを定義すると、 定義されたクラスには自動的に、関連付けに関係する13個のメソッドが自動的に与えられます。

  • collection(force_reload = false)
  • collection<<(object, ...)
  • collection.delete(object, ...)
  • collection.destroy(object, ...)
  • collection=objects
  • collection_singular_ids
  • collection_singular_ids=ids
  • collection.clear
  • collection.empty?
  • collection.size
  • collection.find(...)
  • collection.where(...)
  • collection.exists?(...)
  • collection.build(attributes = {}, ...)
  • collection.create(attributes = {})

これらのメソッドの全ては、collectionの部分はhas_manyに1つ目の引数として渡されたシンボルと置き換えられ、 collection_singularの部分は、そのシンボルの単数形と置き換えられます。 例えば、下記のように宣言された場合、

class Customer < ActiveRecord::Base
  has_many :orders
end

各顧客(customer)モデルのインスタンスは、下記のメソッドを持つ事になります。

orders(force_reload = false)
orders<<(object, ...)
orders.delete(object, ...)
orders.destroy(object, ...)
orders=objects
order_ids
order_ids=ids
orders.clear
orders.empty?
orders.size
orders.find(...)
orders.where(...)
orders.exists?(...)
orders.build(attributes = {}, ...)
orders.create(attributes = {})
4.3.1.1 collection(force_reload = false)

collectionメソッドは、関連付けられた全てのオブジェクトの配列を返します。 もし関連付けられたオブジェクトが無ければ、空の配列が返されます。

@orders = @customer.orders
4.3.1.2 collection<<(object, ...)

collection<<メソッドは、1つまたは複数のオブジェクトを、 それらの外部キーを呼び出したモデルの主キーを設定することでcollectionに加えます。

@customer.orders << @order1
4.3.1.3 collection.delete(object, ...)

collection.deleteメソッドは、外部キーにNULLを設定することで、 collectionから1つまたは複数のオブジェクトを削除します。

@customer.orders.delete(@order1)

加えて、オブジェクトがdependent: :destroyに関連付けられているとdestroyされ、 ¥dependent: :delete_allに関連付けられているとdeleteされます。

4.3.1.4 collection.destroy(object, ...)

collection.destroyメソッドは、各オブジェクト上でdestroyすることで、 collectionから1つまたは複数のオブジェクトを削除します。

@customer.orders.destroy(@order1)

オブジェクトは常にデータベースから削除され、:dependentオプションは無視されます。

4.3.1.5 collection=objects

collection=メソッドは、適切な追加と削除を行うことで、 collectionに提供されたオブジェクト(objects)のみを含めるようにします。

4.3.1.6 collection_singular_ids

collection_singular_idsメソッドは、collection内のオブジェクトのidの配列を返します。

@order_ids = @customer.order_ids
4.3.1.7 collection_singular_ids=ids

collection_singular_ids=メソッドは、適切に追加と削除を行うことによって、 提供された主キーの値のIDのオブジェクトだけをcollectionに含めるようにします。

4.3.1.8 collection.clear

collection.clearメソッドは、collectionから全てのオブジェクトを削除します。 dependent: :destroyが関連付けられていれば、関連するオブジェクトをdestroyし、 dependent: :delete_allが関連付けられていれば、データベースから直接deleteし、 このいずれかで無ければ、それらの外部キーにNULLを設定します。

4.3.1.9 collection.empty?

collection.empty?メソッドは、collectionが関連付けられたオブジェクトを1つも含まない場合にtrueを返します。

<% if @customer.orders.empty? %>
  No Orders Found
<% end %>
4.3.1.10 collection.size

collection.sizeメソッドは、コレクション内のオブジェクトの数を返します。

@order_count = @customer.orders.size
4.3.1.11 collection.find(...)

collection.findメソッドは、コレクション内のオブジェクトを探します。 ActiveRecord::Base.findと同じ文法とオプションを使用します。

@open_orders = @customer.orders.find(1)
4.3.1.12 collection.where(...)

collection.whereメソッドは、与えられた条件に基づいて、コレクション内からオブジェクトを探しますが、 オブジェクトの読み込みは遅くなります。 これは、データベースがオブジェクトがアクセスされる時にのみ、データベースにクエリーが発行されるためです。

@open_orders = @customer.orders.where(open: true) # まだクエリーは未発行
@open_order = @open_orders.first # ここでデータベースにクエリーを発行
4.3.1.13 collection.exists?(...)

collection.exists?メソッドは、コレクション内に与えられた条件に合うオブジェクトが存在するか否かを調べます。 ActiveRecord::Base.exists?と同じ文法とオプションを使用します。

4.3.1.14 collection.build(attributes = {}, ...)

collection.buildメソッドは、1つ以上の関連付けられた型の、新しいオブジェクトを返します。 これらのオブジェクトは、渡された属性からインスタンス化され、 外部キーが作成されることでリンクされますが、これらの関連付けられたオブジェクトはまだ保存されません。

@order = @customer.orders.build(order_date: Time.now,
                                order_number: "A12345")
4.3.1.15 collection.create(attributes = {})

collection.createは、関連付けられた型の新しいオブジェクトを返します。 このオブジェクトは、渡された属性からインスタンス化され、 その外部キーが作成されることでリンクされ、 関連付けられたモデル上で指定されている検証に全てパスすれば、そのオブジェクトは保存されます。

@order = @customer.orders.create(order_date: Time.now,
                                 order_number: "A12345")
4.3.2 has_manyのオプション

Railsが、ほとんどの状況に適した気の利いたデフォルト設定を使用してくれる一方、 has_many関連付け参照の振る舞いを、カスタマイズしたいという事があるかもしれません。 そのようなカスタマイズは、関連付けを作成する際に、オプションを渡すことで簡単に行うことが可能です。 例えば、この関連付けは、そのような2つのオプションを使用します。

class Customer < ActiveRecord::Base
  has_many :orders, dependent: :delete_all, validate: :false
end

has_many関連付けは、下記のオプションをサポートします。

  • :as
  • :autosave
  • :class_name
  • :dependent
  • :foreign_key
  • :inverse_of
  • :primary_key
  • :source
  • :source_type
  • :through
  • :validate
4.3.2.1 :as

:asオプションの設定は、既にこのガイド内で説明された、ポリモーフィック関連付けを指定します。

4.3.2.2 :autosave

:autosaveオプションをtrueに設定すると、 親のオブジェクトを保存する度に、読み込まれたメンバーを保存し、 削除するものとしてマークされたメンバーを削除します。

4.3.2.3 :class_name

他のモデルの名前が、関連付くものの名前から引き出すことが出来ない場合、 :class_nameオプションを使用して、モデル名を抵抗することが可能です。 例えば、customer(顧客)がorders(注文)を持つが、 orders(アカウント)が含まれる実際のモデルの名前がTransaction(処理)である場合、 次のように設定することが出来ます。

class Customer < ActiveRecord::Base
  has_many :orders, class_name: "Transaction"
end
4.3.2.4 :dependent

関連付けられたオブジェクトのオーナー(持ち主)がdestroyされた際に、何を発生させるかを制御します。

  • :destroyが指定されると、全ての関連付けられた全てのオブジェクトも削除します。
  • :delete_allが指定されると、関連付けられた全てのオブジェクトはデータベースから直接削除されます。 (そのため、コールバックは実行されません。)
  • :nullifyが指定されると、外部キーにNULLが設定されます。 コールバックは実行されません。
  • :restrict_with_exceptionが指定されると、関連付けられたレコードが存在すると、例外が発生します。
  • :restrict_with_errorが指定されると、関連付けられたオブジェクトが存在する場合、オーナー(持ち主)に対してエラーが追加します。

関連付けに:throughが使用されていると、このオプションは無視されます。

4.3.2.5 :foreign_key

便利な事にRailsは、もう1つのモデル上で、このモデルの名前に_id接頭辞を追加したカラムを、 外部キーを保持するためのカラムとみなしてくれます。 :foreign_keyオプションは、直接外部キーの名前を設定することを可能にしてくれます。

class Customer < ActiveRecord::Base
  has_many :orders, foreign_key: "cust_id"
end

あるケースでは、Railsは外部キーカラムを作成しません。 マイグレーションの一部として、明示的に定義する必要があります。

4.3.2.6 :inverse_of

:inverse_ofオプションは、この関連付けを逆さまにする、 belongs_to関連付けの名前を指定します。 :throughまたは:asオプションと組み合わせて動作させることは出来ません。

class Customer < ActiveRecord::Base
  has_many :orders, inverse_of: :customer
end

class Order < ActiveRecord::Base
  belongs_to :customer, inverse_of: :orders
end
4.3.2.7 :primary_key

便利な事に、Railsはこの関連付けのidを、主キーを保持するのに使用するカラムとみなしてくれます。 :primary_keyオプションを使用して、これを上書きし、明示的に主キーを指定することが可能です。

4.3.2.8 :source

:sourceオプションは、has_many :through関連付けのために、 sourceの関連付け名を指定します。 関連付けの名前から、source関連付けの名前が自動的に推測されて指定されない場合にのみ、このオプションを使用する必要があります。

4.3.2.9 :source_type

:source_typeオプションは、ポリモーフィック関連付けを通して処理されるhas_many :through関連付けのために、 source関連付けの型を指定します。(翻訳に自信なし)

4.3.2.10 :through

:throughオプションは、クエリーの実行を通す結合テーブルを指定します。 The :through option specifies a join model through which to perform the query. has_many :through関連付けが提供する多対多の実装は、既にこのガイド内で詳細な説明がされています。

4.3.2.11 :validate

もし:validateオプションにfalseが設定されると、 関連付けられたオブジェクトは、そのオブジェクトが保存される際に検証がされなくなります。 デフォルトではこの値はtrueで、そのオブジェクトが保存される際は検証が行われます。

4.3.3 has_manyのスコープ

has_manyを使用する際に、クエリーをカスタマイズしたいというケースがあるかもしれません。 そのようなカスタマイズは、スコープのブロックを通して行うことが可能です。 例えば、

class Customer < ActiveRecord::Base
  has_many :orders, -> { where processed: true }
end

スコープブロック内では、標準のクエリーメソッドを幾つか使用することが可能です。 該当するものを下記に示します。

  • where
  • extending
  • group
  • includes
  • limit
  • offset
  • order
  • readonly
  • select
  • uniq
4.3.3.1 where

whereメソッドは、関連付くオブジェクトが満たすべき条件を指定することを可能にしてくれます。

class Customer < ActiveRecord::Base
  has_many :confirmed_orders, -> { where "confirmed = 1" },
    class_name: "Order"
end

またハッシュを通じて、条件を設定する事も可能です。

class Customer < ActiveRecord::Base
  has_many :confirmed_orders, -> { where confirmed: true },
                              class_name: "Order"
end

もしハッシュスタイルのwhereオプションを使用する場合、 この関連付けを通したれコード作成は、ハッシュを使用して自動的にスコープ化されます。 このケースでは、@customer.confirmed_orders.create、または、@customer.confirmed_orders.buildを使用すると、 confirmedカラムの値がtrueであるordersを作成します。

4.3.3.2 extending

extendingメソッドは、関連プロキシを拡張する名付けモジュールを指定します。 関連付けの拡張の詳細については、後ほどこのガイド内で説明します。

4.3.3.3 group

groupメソッドは、結果的にSQLでGROUP BYの対象となる、 属性名を指定するのに使用されます。

class Customer < ActiveRecord::Base
  has_many :line_items, -> { group 'orders.id' },
                        through: :orders
end
4.3.3.4 includes

この関連付けが使用される際に、テーブルの繋がりが2階層目として要求されるeager-load(熱心な読み込み)されるべきテーブルを、 includesメソッドを使用することで、その関連付けを指定する事が可能です。 例えば、下記のようなモデルがあったとします。

class Customer < ActiveRecord::Base
  has_many :orders
end

class Order < ActiveRecord::Base
  belongs_to :customer
  has_many :line_items
end

class LineItem < ActiveRecord::Base
  belongs_to :order
end

もし、customers(顧客)から、@customer.orders.line_itemsとして、 しきりにline items(品目)を直接取得するのであれば、 line itemsをcustomersのordersに含めることで、コードをより効率的にすることが出来ます。

class Customer < ActiveRecord::Base
  has_many :orders, -> { includes :line_items }
end

class Order < ActiveRecord::Base
  belongs_to :customer
  has_many :line_items
end

class LineItem < ActiveRecord::Base
  belongs_to :order
end
4.3.3.5 limit

limitメソッドは、関連付けを通して取得されるオブジェクトの総数を制限してくれます。

class Customer < ActiveRecord::Base
  has_many :recent_orders,
    -> { order('order_date desc').limit(100) },
    class_name: "Order",
end
4.3.3.6 offset

offsetメソッドは、関連付けを通じて取得するオブジェクトの開始(オフセット)位置の指定を可能にしてくれます。 例えば、-> { offset(11) }は最初の11レコードをスキップします。

4.3.3.7 order

orderメソッドは、関連付けられて取得するオブジェクトの順序を指定します。 (ORDER BY句で使用されるSQL文)

class Customer < ActiveRecord::Base
  has_many :orders, -> { order "date_confirmed DESC" }
end
4.3.3.8 readonly

readonlyメソッドを使用すると、 関連付けを通じて取得する際に、関連付けられたオブジェクトは読み込み専用になります。

4.3.3.9 select

selectメソッドは、関連付けられたオブジェクトを取得する際の、SQL文のSELECT句の上書きを可能にしてくれます。 デフォルトでは、Railsは全てのカラムを取得します。

もし自分でselectを指定するのであれば、関連付けられたモデルの主キーと外部キーのカラムが、含まれているかを確認してください。 これを行わないと、Railsはエラーをスローします。

4.3.3.10 distinct

重複の無いコレクションを保持するために、distinctメソッドを使用します。 これは:throughオプションと使用する際に、非常に便利です。

class Person < ActiveRecord::Base
  has_many :readings
  has_many :posts, through: :readings
end

person = Person.create(name: 'John')
post   = Post.create(name: 'a1')
person.posts << post
person.posts << post
person.posts.inspect # => [#<Post id: 5, name: "a1">, #<Post id: 5, name: "a1">]
Reading.all.inspect  # => [#<Reading id: 12, person_id: 5, post_id: 5>, #<Reading id: 13, person_id: 5, post_id: 5>]

上記のケースでは、2つのreadingsが存在し、 person.postsは例えばそれらが同じpostを指し示していても、 両方を取り出してしまいます。

それでは、distinctを設定してみましょう。

class Person
  has_many :readings
  has_many :posts, -> { distinct }, through: :readings
end

person = Person.create(name: 'Honda')
post   = Post.create(name: 'a1')
person.posts << post
person.posts << post
person.posts.inspect # => [#<Post id: 7, name: "a1">]
Reading.all.inspect  # => [#<Reading id: 16, person_id: 7, post_id: 7>, #<Reading id: 17, person_id: 7, post_id: 7>]

上記のケースも、2つのreadingsが存在します。 ただし、person.postsは、コレクションが一意のレコードしか読み込まないため、 1つのpostのみを表示します。

もしレコードの挿入時に、指定されている関連付け内で、全てのレコードをdistinct(重複無し)にしたいのであれば (そのため、関連付けを調べる際に重複レコードが見つからなくなります。)、 自身でテーブルに一意(unique)のインデックス(index)を追加するべきです。 例えば、もしテーブル名をperson_postsとし、全てのpostを一意にしたいのであれば、 下記のマイグレーションを追加します。

add_index :person_posts, :post, :unique => true

一意であることを確約する事にinclude?を使用するような方法を取ってしまうと、 レースコンディションになることに注意してください。 関連付けで、distinct(重複が無いこと)を確約するのに、include?を使用しないでください。 例えば、上述でのpostでは下記のコードは、 複数のユーザーが同時にこの処理の実行を試みる可能性があるため、 レースコンデションになります。 (一意を確約しているはずが、複数ユーザーによる同時追加が発生すると、一意制約が破られてしまう可能性があります。)

person.posts << post unless person.posts.include?(post)
4.3.4 オブジェクトはいつ保存されるか?

has_many関連付けへのオブジェクトの割り当てをする際に、そのオブジェクトは自動的に保存されます。 (そのオブジェクト自身の外部キーを更新することで) もし複数のオブジェクトを1文の中で割り当てたのであれば、それらの全てが保存されます。

もし、それらの内のいずれかのオブジェクトが、検証エラーのため保存に失敗したのであれば、 その割り当て文はfalseを返し、その割り当て処理自身がキャンセルされます。

もし親となるオブジェクト(has_many宣言を宣言しているオブジェクト)が保存されない(new_record?trueを返すという事)と、 子オブジェクトは追加された際に保存されません。 全ての保存されていない関連付けの項目は、親が保存された際に自動的に保存されます。

オブジェクトの保存をせずに、has_many関連付けにオブジェクトを割り当てたい場合は、 collection.buildメソッドを使用してください。

4.4 has_and_belongs_to_many関連付けに付いて

has_and_belongs_to_many関連付けは、他のオブジェクトとの多対多の関連付けを作ります。 データベースの観点から言うと、それぞれのクラスを参照する外部キーを含む中間結合テーブルが、 この2つのクラスを関連付けます。

4.4.1 has_and_belongs_to_manyによって追加されるメソッド

has_and_belongs_to_many関連付けを宣言すると、 宣言したクラスには自動的に関連付けに関係する13個のメソッドが与えられます。

  • collection(force_reload = false)
  • collection<<(object, ...)
  • collection.delete(object, ...)
  • collection.destroy(object, ...)
  • collection=objects
  • collection_singular_ids
  • collection_singular_ids=ids
  • collection.clear
  • collection.empty?
  • collection.size
  • collection.find(...)
  • collection.where(...)
  • collection.exists?(...)
  • collection.build(attributes = {})
  • collection.create(attributes = {})

これらのメソッドの全ては、collectionの部分はhas_and_belongs_to_manyに1つ目の引数として渡されたシンボルと置き換えられ、 collection_singularの部分は、そのシンボルの単数形と置き換えられます。 例えば、下記のように宣言された場合、

class Part < ActiveRecord::Base
  has_and_belongs_to_many :assemblies
end

モデル部分の各インスタンスは、これらのメソッドを持つことになります。

assemblies(force_reload = false)
assemblies<<(object, ...)
assemblies.delete(object, ...)
assemblies.destroy(object, ...)
assemblies=objects
assembly_ids
assembly_ids=ids
assemblies.clear
assemblies.empty?
assemblies.size
assemblies.find(...)
assemblies.where(...)
assemblies.exists?(...)
assemblies.build(attributes = {}, ...)
assemblies.create(attributes = {})
4.4.1.1 追加カラムのメソッド

もし、has_and_belongs_to_many関連付けの結合テーブルが、2つの外部キーの他に追加カラムを持つ場合、 これらのカラムは関連付けを通して取得されるレコードへの属性として追加されます。 追加属性を使用して返されたレコードは、Railsはそれらの属性を変更して保存することは出来ないため、 常に読み込み専用になります。

has_and_belongs_to_many関連付け内の結合テーブル上で、追加属性を使用することは非推奨となっています。 もし多対多の関連付けによる2つのモデルの結合テーブル上で、この種の複雑な振る舞いが必要なのであれば、 代わりにhas_many :throughの関連付けを使用すべきです。

4.4.1.2 collection(force_reload = false)

collectionメソッドは、関連付けられたオブジェクトの全ての配列を返します。 もし関連付けられたオブジェクトが無ければ、空の配列が返されます。

@assemblies = @part.assemblies
4.4.1.3 collection<<(object, ...)

collection<<メソッドは、結合テーブル内にレコードを作成することで、 1つ以上のオブジェクトをコレクションに追加します。

@part.assemblies << @assembly1

このメソッドは、collection.concatcollection.pushとしてエイリアスされます。

4.4.1.4 collection.delete(object, ...)

collection.deleteメソッドは、結合テーブル内からレコードを削除することで、 コレクションから1つ以上のオブジェクトを削除します。 これは、オブジェクトを削除しません。

@part.assemblies.delete(@assembly1)

結合テーブル上のコールバックをトリガしません。

4.4.1.5 collection.destroy(object, ...)

collection.destroyメソッドは、コールバック実行を含む結合テーブル内の各レコードの削除を実行することで コレクションから1つ以上のオブジェクトを削除します。 これはオブジェクトを削除しません。

@part.assemblies.destroy(@assembly1)
4.4.1.6 collection=objects

collection=メソッドは、適切に追加と削除を行うことによって、 提供されたオブジェクトだけを含むコレクションを作成します。

4.4.1.7 collection_singular_ids

collection_singular_idsメソッドは、コレクション内のオブジェクトのIDの配列を返します。

@assembly_ids = @part.assembly_ids
4.4.1.8 collection_singular_ids=ids

collection_singular_ids=メソッドは、適切に追加と削除を行うことによって、 提供された主キーの値によって識別されたオブジェクトだけを含むコレクションを作成します。

4.4.1.9 collection.clear

collection.clearメソッドは、結合テーブルから行を削除することによって、 各オブジェクトを削除します。 これは関連付けられたオブジェクトを削除しません。

4.4.1.10 collection.empty?

もしコレクションが関連付けられたオブジェクトを含まない場合、 collection.empty?メソッドはtrueを返します。

<% if @part.assemblies.empty? %>
  This part is not used in any assemblies
<% end %>
4.4.1.11 collection.size

collection.sizeメソッドは、コレクション内のオブジェクト数を返します。

@assembly_count = @part.assemblies.size
4.4.1.12 collection.find(...)

collection.findメソッドは、コレクション内のオブジェクトを見つけます。 ActiveRecord::Base.findと同じ文法とオプションを使用します。 また、コレクション内にオブジェクトが存在するべき付加条件の追加も行います。(翻訳に自信なし)

@assembly = @part.assemblies.find(1)
4.4.1.13 collection.where(...)

collection.whereメソッドは、提供された条件を元にコレクション内からオブジェクトを見つけますが、 オブジェクトの読み込みは遅くなります。 これは、データベースがオブジェクトがアクセスされる時にのみ、データベースにクエリーが発行されるためです。 また、コレクション内にオブジェクトが存在するべき付加条件の追加も行います。(翻訳に自信なし)

@new_assemblies = @part.assemblies.where("created_at > ?", 2.days.ago)
4.4.1.14 collection.exists?(...)

collection.exists?メソッドは、 collection.exists?メソッドは、コレクション内に与えられた条件に合うオブジェクトが存在するか否かを調べます。 ActiveRecord::Base.exists?と同じ文法とオプションを使用します。

4.4.1.15 collection.build(attributes = {})

collection.buildメソッドは、関連付けられた型の新しいオブジェクトを返します。 このオブジェクトは、渡された属性からインスタンス化され、 結合テーブルを通してリンクが作成されますが、 関連付けられたこのオブジェクトは、まだ保存されません。

@assembly = @part.assemblies.build({assembly_name: "Transmission housing"})
4.4.1.16 collection.create(attributes = {})

collection.createメソッドは、関連付けられた型の新しいオブジェクトを返します。 このオブジェクトは、渡された属性からインスタンス化され、 結合テーブルを通してリンクが作成され、 関連付けれたモデルで指定されている全ての検証にパスすると保存されます。

@assembly = @part.assemblies.create({assembly_name: "Transmission housing"})
4.4.1.17 collection.create!(attributes = {})

collection.createと同じことを行いますが、もし不正なレコードであれば、 ActiveRecord::RecordInvalidが発生します。

4.4.2 has_and_belongs_to_manyのオプション

Railsが、ほとんどの状況に適した気の利いたデフォルト設定を使用してくれる一方、 has_and_belongs_to_many関連付け参照の振る舞いを、カスタマイズしたいという事があるかもしれません。 そのようなカスタマイズは、関連付けを作成する際に、オプションを渡すことで簡単に行うことが可能です。 例えば、この関連付けは、そのような2つのオプションを使用します。

class Parts < ActiveRecord::Base
  has_and_belongs_to_many :assemblies, autosave: true,
                                       readonly: true
end

has_and_belongs_to_many関連付けは、下記のオプションをサポートします。

  • :association_foreign_key
  • :autosave
  • :class_name
  • :foreign_key
  • :join_table
  • :validate
  • :readonly
4.4.2.1 :association_foreign_key

便利なことにRailsは、他のモデル名に接尾辞として_idを追加したものを、 他のモデルの外部キーへの参照を保持するのに使用される結合テーブル内のカラムとみなしてくれます。 :association_foreign_keyオプションは、直接外部キーの名前を設定することを可能にしてくれます。

:foreign_key:association_foreign_keyオプションは、 多対多の自己結合を設定する際に便利です。 下記は、その一例になります。

class User < ActiveRecord::Base
  has_and_belongs_to_many :friends,
      class_name: "User",
      foreign_key: "this_user_id",
      association_foreign_key: "other_user_id"
end
4.4.2.2 :autosave

:autosaveオプションをtrueに設定すると、 親のオブジェクトを保存する度に、読み込まれたメンバーを保存し、 削除するものとしてマークされたメンバーを削除します。

4.4.2.3 :class_name

他のモデルの名前が、関連付くものの名前から引き出すことが出来ない場合、 :class_nameオプションを使用して、モデル名を抵抗することが可能です。 例えば、パーツ(part)が多くの組み立て部品(assemblies)を持つが、 assembliesが含まれるモデルの実際の名前がGadget(ガジェット)である場合、 次のように設定することが出来ます。

class Parts < ActiveRecord::Base
  has_and_belongs_to_many :assemblies, class_name: "Gadget"
end
4.4.2.4 :foreign_key

便利なことにRailsは、このモデル名に接尾辞として_idを追加したものを、 このモデルの外部キーへの参照を保持するのに使用される結合テーブル内のカラムとみなしてくれます。 :foreign_keyオプションは、外部キーの名前を直接設定することを可能にしてくれます。

class User < ActiveRecord::Base
  has_and_belongs_to_many :friends,
      class_name: "User",
      foreign_key: "this_user_id",
      association_foreign_key: "other_user_id"
end
4.4.2.5 :join_table

もし結合テーブルの辞書順に基づいたデフォルトの名前が、指定したい名前と異なる場合、 :join_tableオプションを使用して、デフォルトの名前を上書きすることが出来ます。

4.4.2.6 :validate

:validateオプションをfalseに設定すると、 このオブジェクトを保存する際に関連付けオブジェクトの検証が行われなくなります。 デフォルトではこの値はtrueで、関連付けられたオブジェクトは、このオブジェクトが保存される際に検証が行われます。

4.4.3 has_and_belongs_to_manyのスコープ

has_and_belongs_to_manyを使用する際に、クエリーをカスタマイズしたいというケースがあるかもしれません。 そのようなカスタマイズは、スコープのブロックを通して行うことが可能です。 例えば、

class Parts < ActiveRecord::Base
  has_and_belongs_to_many :assemblies, -> { where active: true }
end

スコープブロック内では、標準のクエリーメソッドを幾つか使用することが可能です。 該当するものを下記に示します。

  • where
  • extending
  • group
  • includes
  • limit
  • offset
  • order
  • readonly
  • select
  • uniq
4.4.3.1 where

whereメソッドは、関連付くオブジェクトが満たすべき条件を指定することを可能にしてくれます。

class Parts < ActiveRecord::Base
  has_and_belongs_to_many :assemblies,
    -> { where "factory = 'Seattle'" }
end

またハッシュを通じて、条件を設定する事も可能です。

class Parts < ActiveRecord::Base
  has_and_belongs_to_many :assemblies,
    -> { where factory: 'Seattle' }
end

ハッシュ形式のwhereを使用する場合、この関連付けを通したレコードの作成は、 自動的にそのハッシュを使用したスコープの対象になります。 このケースでは、@parts.assemblies.createまたは@parts.assemblies.buildを使用すると、 factoryカラムに"Seattle"の値を持つordersが作成されます。

4.4.3.2 extending

extendingメソッドは、関連プロキシを拡張する名付けモジュールを指定します。 関連付けの拡張の詳細については、後ほどこのガイド内で説明します。

4.4.3.3 group

groupメソッドは、結果的にSQLでGROUP BYの対象となる、 属性名を指定するのに使用されます。

class Parts < ActiveRecord::Base
  has_and_belongs_to_many :assemblies, -> { group "factory" }
end
4.4.3.4 includes

この関連付けが使用される際に、テーブルの繋がりが2階層目として要求されるeager-load(熱心な読み込み)されるべきテーブルを、 includesメソッドを使用することで、その関連付けを指定する事が可能です。

4.4.3.5 limit

limitメソッドは、関連付けを通して取得されるオブジェクトの総数を制限してくれます。

class Parts < ActiveRecord::Base
  has_and_belongs_to_many :assemblies,
    -> { order("created_at DESC").limit(50) }
end
4.4.3.6 offset

offsetメソッドは、関連付けを通じて取得するオブジェクトの開始(オフセット)位置の指定を可能にしてくれます。 例えば、offset(11)を設定すると、最初の11レコードをスキップします。

4.4.3.7 order

orderメソッドは、関連付けられて取得するオブジェクトの順序を指定します。 (ORDER BY句で使用されるSQL文)

class Parts < ActiveRecord::Base
  has_and_belongs_to_many :assemblies,
    -> { order "assembly_name ASC" }
end
4.4.3.8 readonly

readonlyメソッドを使用すると、 関連付けを通じて取得する際に、関連付けられたオブジェクトは読み込み専用になります。

4.4.3.9 select

selectメソッドは、関連付けられたオブジェクトを取得する際の、SQL文のSELECT句の上書きを可能にしてくれます。 デフォルトでは、Railsは全てのカラムを取得します。

4.4.3.10 uniq

コレクションから重複を削除するには、uniqを使用してください。

4.4.4 When are Objects Saved?

オブジェクトへhas_and_belongs_to_many関連付けを割り当てる際に、 オブジェクトは自動的に保存されます(結合テーブルを更新することによって)。 もし複数のオブジェクトを1文の中で割り当てたのであれば、それらの全てが保存されます。

もし、それらの内のいずれかのオブジェクトが、検証エラーのため保存に失敗したのであれば、 その割り当て文はfalseを返し、その割り当て処理自身がキャンセルされます。

もし親となるオブジェクト(has_and_belongs_to_many宣言を宣言しているオブジェクト)が保存されない(new_record?trueを返すという事)と、 子オブジェクトは追加された際に保存されません。 全ての保存されていない関連付けの項目は、親が保存された際に自動的に保存されます。

オブジェクトの保存をせずに、has_and_belongs_to_many関連付けにオブジェクトを割り当てたい場合は、 collection.buildメソッドを使用してください。

4.5 関連付けのコールバック

通常のコールバックは、Activeレコードオブジェクトのライフサイクル内にフックされ、 それらのオブジェクトが様々なタイミングで動作させることを可能にします。 例えば、:before_saveコールバックは、オブジェクトが保存される直前に、 何かを発生させるのに使用する事が可能です。

関連付けのコールバックは通常のコールバックと似ていますが、 それらはコレクショのライフサイクル内のイベントによってトリガされます。 関連付けコールバックには、下記の4つのコールバックが存在します。

  • before_add
  • after_add
  • before_remove
  • after_remove

関連付けの宣言へオプションを追加することで、関連付けコールバックの定義を行います。 例えば、

class Customer < ActiveRecord::Base
  has_many :orders, before_add: :check_credit_limit

  def check_credit_limit(order)
    ...
  end
end

Railsはコールバックに追加、または削除されるオブジェクトを渡します。

配列としてそれらを渡すことで、1つのイベント上に複数のコールバックを積み上げることが出来ます。

class Customer < ActiveRecord::Base
  has_many :orders,
    before_add: [:check_credit_limit, :calculate_shipping_charges]

  def check_credit_limit(order)
    ...
  end

  def calculate_shipping_charges(order)
    ...
  end
end

もし、before_addコールバックが例外をスローすると、 そのオブジェクトはコレクションへ追加されません。 同様に、before_removeコールバックが例外をスローすると、 オブジェクトはコレクションから削除されません。

4.6 関連付けの拡張

関連付け代理オブジェクト内に、Railsが自動的に構築する機能において、制限を受けることはありません。 また、これらのオブジェクトを、匿名のモジュールを通して、新しいファインダー(finder)、クリエーター(creater)、その他のメソッドを追加することで、 拡張することも可能です。 例えば、

class Customer < ActiveRecord::Base
  has_many :orders do
    def find_by_order_prefix(order_number)
      find_by(region_id: order_number[0..2])
    end
  end
end

多くの関連付けから、共有されるべき拡張を持つ場合、 名前を付けた拡張モジュールを使用することが出来ます。 下記は、その一例になります。

module FindRecentExtension
  def find_recent
    where("created_at > ?", 5.days.ago)
  end
end

class Customer < ActiveRecord::Base
  has_many :orders, -> { extending FindRecentExtension }
end

class Supplier < ActiveRecord::Base
  has_many :deliveries, -> { extending FindRecentExtension }
end

下記のproxy_associationアクセサーの3つの属性を使用することで、 拡張は関連付け代理オブジェクトの内部を参照することが出来ます。

  • proxy_association.ownerは、関連付けの一部であるオブジェクトを返します。
  • proxy_association.reflectionは、関連付けを説明するリフレクションオブジェクトを返します。
  • proxy_association.targetは、belongs_tohas_oneのために関連付けられたオブジェクト、 またはhas_many,has_and_belongs_to_manyのための関連付けられた複数のオブジェクトを返します。

 Back to top

© 2010 - 2017 STUDIO KINGDOM