1 関連付けを使う理由

Railsの「関連付け(アソシエーション: association)」は、2つのActive Recordモデル同士のつながりを指します。モデルとモデルの間には関連付けを行なう必要がありますが、その理由はおわかりでしょうか。関連付けを行うことで、自分のコードの共通操作がシンプルになって扱いやすくなります。簡単なRailsアプリケーションを例にとって説明しましょう。このアプリケーションにはAuthor(著者)モデルとBook(書籍)モデルがあります。一人の著者は、複数の書籍を持っています。関連付けを設定していない状態では、モデルの宣言は以下のようになります。

class Author < ApplicationRecord
end

class Book < ApplicationRecord
end

ここで、既存の著者が新しい書籍を1件追加したくなったとします。この場合、以下のようなコードを実行する必要があるでしょう。

@book = Book.create(published_at: Time.now, author_id: @author.id)

今度は著者を1人削除する場合を考えてみましょう。著者を削除するときは、その著者の書籍もすべて削除されるようにしておきます。

@books = Book.where(author_id: @author.id)
@books.each do |book|
  book.destroy
end
@author.destroy

Active Recordの関連付け機能を使うと、2つのモデルの間につながりがあることを明示的にRailsに対して宣言でき、それによってモデルの操作を一貫させることができます。著者と書籍を設定するコードを次のように書き直せます。

class Author < ApplicationRecord
  has_many :books, dependent: :destroy
end

class Book < ApplicationRecord
  belongs_to :author
end

上のように関連付けを追加したことで、特定の著者の新しい書籍を1冊追加する作業が以下のように1行で書けるようになりました。

@book = @author.books.create(published_at: Time.now)

著者と、その著者の書籍をまとめて削除する作業はずっと簡単です。

@author.destroy

その他の関連付け方法については、次のセクションをお読みください。その後で、関連付けに関するさまざまなヒントや活用方法、Railsの関連付けメソッドとオプションの完全な参考情報も紹介します。

2 関連付けの種類

Railsでサポートされている関連付けは以下の6種類です。

関連付けは、一種のマクロ的な呼び出しとして実装されており、これによってモデル間の関連付けを宣言的に追加できます。たとえば、あるモデルが他のモデルに従属している(belongs_to)と宣言すると、2つのモデルのそれぞれのインスタンス間で「主キー - 外部キー」情報を保持しておくようにRailsに指示します。同時に、いくつかの便利なメソッドもそのモデルに追加されます。

本ガイドではこの後、それぞれの関連付けの宣言方法と利用方法について詳しく解説します。その前に、それぞれの関連付けが適切となる状況について簡単にご紹介します。

2.1 belongs_to関連付け

あるモデルでbelongs_to関連付けを行なうと、宣言を行ったモデルの各インスタンスは、他方のモデルのインスタンスに文字どおり「従属(belongs to)」します。たとえば、Railsアプリケーションに著者(Author)と書籍(Book)情報が含まれており、書籍1冊につき正確に1人の著者を割り当てたいのであれば、Bookモデルで以下のように宣言します。

class Book < ApplicationRecord
  belongs_to :author
end

belongs_to 関連付けの図

belongs_to関連付けで指定するモデル名は必ず「単数形」にしなければなりません。上記の例で、Bookモデルのauthor関連付けを複数形(authors)にしてからBook.create(authors: @author)でインスタンスを作成しようとすると、uninitialized constant Book::Authorsエラーが発生します。Railsは、関連付けの名前から自動的にモデルのクラス名を推測します。従って、関連付け名が誤って複数形になってしまっていると、そこから推測されるクラス名も誤った形の複数形になってしまいます。

上の関連付けに対応するマイグレーションは以下のような感じになります。

class CreateBooks < ActiveRecord::Migration[7.0]
  def change
    create_table :authors do |t|
      t.string :name
      t.timestamps
    end

    create_table :books do |t|
      t.belongs_to :author
      t.datetime :published_at
      t.timestamps
    end
  end
end

belongs_toを単独で利用すると、一方向のみの「1対1」つながりが生成されます。つまり上の例で言うと、「個別の書籍はその著者を知っている」状態になりますが、「著者は自分の書籍について知らない」状態になります。

双方向関連付けをセットアップするには、belongs_to関連付けを使うときに相手側のモデルにhas_oneまたはhas_many関連付けを指定します。

belongs_toでは「参照の一貫性」が担保されません。そのため、ユースケースによっては以下のように参照カラムでデータベースレベルの外部キー制約(foreign_key: true)を追加する必要があります。

create_table :books do |t|
  t.belongs_to :author, foreign_key: true
  # ...
end

2.2 has_one関連付け

has_one関連付けは、相手側の1つのモデルがこのモデルへの参照を持っていることを示します。相手側のモデルは、この関連付けを経由してフェッチできます。

class Supplier < ApplicationRecord
  has_one :account
end

belongs_toとの主な違いは、リンクカラムsupplier_idが相手側のテーブルにあることです。

has_one関連付けの図

上の関連付けに対応するマイグレーションは以下のような感じになります。

class CreateSuppliers < ActiveRecord::Migration[7.0]
  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

ユースケースによっては、accountsテーブルとの関連付けのために、supplierカラムにuniqueインデックスか外部キー制約を追加する必要が生じることもあります。その場合、カラムの定義は次のようになるでしょう。

create_table :accounts do |t|
  t.belongs_to :supplier, index: { unique: true }, foreign_key: true
  # ...
end

このリレーションは、相手側のモデルでbelongs_to関連付けも設定することで双方向関連付けになります。

2.3 has_many関連付け

has_many関連付けは、has_oneと似ていますが、相手のモデルとの「1対多」のつながりを表す点が異なります。has_many関連付けは、多くの場合belongs_toの反対側で使われます。

has_many関連付けは、そのモデルの各インスタンスが、相手のモデルのインスタンスを0個以上持っていることを示します。たとえば、さまざまな著者や書籍を含むアプリケーションでは、Author(著者)モデルを以下のように宣言できます。

class Author < ApplicationRecord
  has_many :books
end

has_many関連付けを宣言する場合、相手のモデル名は「複数形」で指定する必要があります。

has_many関連付けの図

上の関連付けに対応するマイグレーションは以下のような感じになります。

class CreateAuthors < ActiveRecord::Migration[7.0]
  def change
    create_table :authors do |t|
      t.string :name
      t.timestamps
    end

    create_table :books do |t|
      t.belongs_to :author
      t.datetime :published_at
      t.timestamps
    end
  end
end

ユースケースにもよりますが、通常はこのbooksテーブルのauthorカラムに「一意でない」インデックスを追加し、オプションで外部キー制約を作成するのがよいでしょう。

create_table :books do |t|
  t.belongs_to :author, index: true, foreign_key: true
  # ...
end

2.4 has_many :through関連付け

has_many :through関連付けは、他方のモデルと「多対多」のつながりを設定する場合によく使われます。

この関連付けでは、2つのモデルの間に「第3のモデル」(joinモデル)が介在し、それを経由(through)して相手のモデルの「0個以上」のインスタンスとマッチします。たとえば、患者(patients)が医師(physicians)との診察予約(appointments)を取る医療業務を考えてみます。この場合、関連付けの宣言は次のような感じになるでしょう。

class Physician < ApplicationRecord
  has_many :appointments
  has_many :patients, through: :appointments
end

class Appointment < ApplicationRecord
  belongs_to :physician
  belongs_to :patient
end

class Patient < ApplicationRecord
  has_many :appointments
  has_many :physicians, through: :appointments
end

has_many :through関連付けの図

上の関連付けに対応するマイグレーションは以下のような感じになります。

class CreateAppointments < ActiveRecord::Migration[7.0]
  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

joinモデルのコレクションは、has_many関連付けメソッド経由で管理できます。たとえば、以下のような割り当てを実行したとします。

physician.patients = patients

このとき、新たに関連付けられたオブジェクトについて、新しいjoinモデルが自動的に作成されます。以前あった行がなくなった場合は、その行はjoinモデルから自動的に削除され、joinモデルに含まれなくなります。

joinモデルでは、以前あった行がなくなった場合の自動削除は即座に行われます。しかも、そのときにdestroyコールバックが発生しないので注意が必要です。

has_many :through関連付けは、ネストしたhas_many関連付けを介して「ショートカット」を設定する場合にも便利です。たとえば、1つのドキュメントに多くの節(section)があり、1つの節の下に多くの段落(paragraph)がある状態で、節をスキップしてドキュメントにあるすべての段落のシンプルなコレクションが欲しいとします。その場合、以下の方法で設定できます。

class Document < ApplicationRecord
  has_many :sections
  has_many :paragraphs, through: :sections
end

class Section < ApplicationRecord
  belongs_to :document
  has_many :paragraphs
end

class Paragraph < ApplicationRecord
  belongs_to :section
end

through: :sectionsを指定することにより、Railsは以下の文を理解できるようになります。

@document.paragraphs

2.5 has_one :through関連付け

has_one :through関連付けは、他方のモデルに対して「1対1」のつながりを設定します。この関連付けは、2つのモデルの間に「第3のモデル」(joinモデル)が介在し、それを経由(through)して相手モデルの1個のインスタンスとマッチします。たとえば、個別の供給者(supplier)が1個のアカウント(account)を持ち、さらに1個のアカウントが1個のアカウント履歴に関連付けられる場合、Supplierモデルは以下のような感じになります。

class Supplier < ApplicationRecord
  has_one :account
  has_one :account_history, through: :account
end

class Account < ApplicationRecord
  belongs_to :supplier
  has_one :account_history
end

class AccountHistory < ApplicationRecord
  belongs_to :account
end

has_one :through関連付けの図

上の関連付けに対応するマイグレーションは以下のような感じになります。

class CreateAccountHistories < ActiveRecord::Migration[7.0]
  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関連付けは、他方のモデルと「多対多」のつながりを作成しますが、through:を指定した場合と異なり、第3のモデル(joinモデル)が介在しません(訳注: 後述するようにjoin用のテーブルは必要です)。たとえば、アプリケーションにさまざまな完成品(assemblies)と部品(parts)があり、完成品ごとに多数の部品が対応し、逆に1個の部品も多くの完成品に対応するのであれば、モデルの宣言は以下のようになります。

class Assembly < ApplicationRecord
  has_and_belongs_to_many :parts
end

class Part < ApplicationRecord
  has_and_belongs_to_many :assemblies
end

has_and_belongs_to_many関連付けの図

上の関連付けに対応するマイグレーションは以下のような感じになります。

class CreateAssembliesAndParts < ActiveRecord::Migration[7.0]
  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, id: false do |t|
      t.belongs_to :assembly
      t.belongs_to :part
    end
  end
end

2.7 belongs_tohas_oneのどちらを選ぶか

2つのモデルの間に1対1のリレーションシップを設定したいのであれば、一方のモデルにbelongs_toを追加し、他方のモデルにhas_oneを追加する必要があります。どちらの関連付けをどちらのモデルに置けばよいのでしょうか。

区別の決め手となるのは外部キー(foreign key)をどちらに置くかです(外部キーは、belongs_to関連付けを追加したモデルのテーブルに追加されます)が、もう少しデータの実際の意味についても考えてみる必要があります。has_oneというリレーションシップは、主語となるものが目的語となるものを「所有する」ことを表します。「1人の供給者がアカウントを1つ所有する」と考える方が、「1つのアカウントが1人の供給者を所有する」と考えるよりも自然です。つまり、この場合の正しいリレーションシップは以下のようになります。

class Supplier < ApplicationRecord
  has_one :account
end

class Account < ApplicationRecord
  belongs_to :supplier
end

上の関連付けに対応するマイグレーションは以下のような感じになります。

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

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

    add_index :accounts, :supplier_id
  end
end

マイグレーションでt.bigint :supplier_idのように「小文字のモデル名_id」と書くと、外部キーを明示的に指定できます。現在のバージョンのRailsでは、同じことをt.references :supplierという方法で記述できます。こちらの方が実装の詳細を抽象化して隠蔽できます。

2.8 has_many :throughhas_and_belongs_to_manyのどちらを選ぶか

Railsでは、モデル間の多対多リレーションシップを宣言するのに2とおりの方法が利用できます。簡単なのはhas_and_belongs_to_manyを使う方法です。この方法では関連付けを直接指定できます。

class Assembly < ApplicationRecord
  has_and_belongs_to_many :parts
end

class Part < ApplicationRecord
  has_and_belongs_to_many :assemblies
end

多対多リレーションシップを宣言するもう1つの方法はhas_many :throughです。こちらの場合は、joinモデルを経由する間接的な関連付けが使われます。

class Assembly < ApplicationRecord
  has_many :manifests
  has_many :parts, through: :manifests
end

class Manifest < ApplicationRecord
  belongs_to :assembly
  belongs_to :part
end

class Part < ApplicationRecord
  has_many :manifests
  has_many :assemblies, through: :manifests
end

どちらを使うかを決める最もシンプルな経験則は次のとおりです。 リレーションシップモデル自体を独立したエンティティとして扱いたい場合は、中間にjoinモデルを使うhas_many :throughリレーションシップを設定します。 リレーションシップモデルで何か特別なことをする必要がまったくない場合は、joinモデルの不要なhas_and_belongs_to_manyリレーションシップを使う方がよりシンプルです(ただし、こちらの場合はjoinモデルが不要な代わりに、専用のjoinテーブルを別途データベースに作成しておく必要があることをお忘れなく)。

joinモデルでバリデーション、コールバック、追加の属性が必要な場合は、has_many :throughをお使いください。

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

ポリモーフィック関連付け(polymorphic association)は、関連付けのやや高度な応用です。ポリモーフィック関連付けを使うと、ある1つのモデルが他の複数のモデルに属していることを、1つの関連付けだけで表現できます。たとえば、写真(picture)モデルがあり、このモデルを従業員(employee)モデルと製品(product)モデルの両方に従属させたいとします。この場合は以下のように宣言します。

class Picture < ApplicationRecord
  belongs_to :imageable, polymorphic: true
end

class Employee < ApplicationRecord
  has_many :pictures, as: :imageable
end

class Product < ApplicationRecord
  has_many :pictures, as: :imageable
end

ポリモーフィックなbelongs_toは、他のあらゆるモデルから利用可能なインターフェイスを設定する宣言と考えてもよいでしょう。@employee.picturesとすると、写真のコレクションをEmployeeモデルの1個のインスタンスから取得できます。

同様に、@product.picturesとすれば、写真のコレクションをProductモデルの1個のインスタンスから取得できます。

Pictureモデルのインスタンスがあれば、@picture.imageableとすることでその親を取得できます。これを可能にするには、ポリモーフィックなインターフェイスを宣言するモデルで、外部キーのカラムと型のカラムを両方とも宣言しておく必要があります。

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

    add_index :pictures, [:imageable_type, :imageable_id]
  end
end

t.referencesという書式を使うと、同じことをもっとシンプルに書けます。

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

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

2.10 自己結合

データモデルを設計していると、モデルを自分自身に関連付ける必要が生じることがあります。たとえば、全従業員(employees)を1つのデータベースモデルに格納しておきたいが、マネージャー(manager)と部下(subordinates)のリレーションシップも追えるようにしておきたい場合が考えられます。この状況は、以下のように自己結合(self-joining)関連付けでモデル化できます。

class Employee < ApplicationRecord
  has_many :subordinates, class_name: "Employee",
                          foreign_key: "manager_id"

  belongs_to :manager, class_name: "Employee", optional: true
end

上のように宣言しておくと、@employee.subordinates@employee.managerを取り出せるようになります。

マイグレーションおよびスキーマでは、モデル自身にreferencesカラムを追加します。

class CreateEmployees < ActiveRecord::Migration[7.0]
  def change
    create_table :employees do |t|
      t.references :manager, foreign_key: { to_table: :employees }
      t.timestamps
    end
  end
end

3 ヒントと注意事項

RailsアプリケーションでActive Recordの関連付けを効果的に使うためには、以下について知っておく必要があります。

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

3.1 キャッシュ制御

関連付けのメソッドは、すべてキャッシュを中心に構築されています。最後に実行したクエリの結果はキャッシュに保持され、次回以降の操作で利用できます。このキャッシュは、以下のようにメソッド間でも共有される点にご注意ください。

# データベースからbooksを取得する
author.books.load

# booksのキャッシュコピーが使われる
author.books.size

# booksのキャッシュコピーが使われる
author.books.empty?

データがアプリケーションの他の部分によって更新されている可能性があるのでキャッシュを再読み込みしたい場合は、どうしたらよいでしょうか。その場合は、以下のように関連付けのメソッド呼び出しでreloadを呼び出せば、キャッシュが破棄されてデータが再読み込みされます。

# データベースからbooksを取得する
author.books.load

# booksのキャッシュコピーが使われる
author.books.size

# booksのキャッシュコピーが破棄され、その後データベースから再度読み込まれる
author.books.reload.empty?

3.2 名前衝突の回避

関連付けにはどんな名前でも使えるとは限りません。関連付けを作成すると、モデルにその名前のメソッドが追加されます。従って、ActiveRecord::Baseのインスタンスで既に使われているような名前を関連付けに使うのは禁物です。そのような名前を関連付けに使うと、基底メソッドが上書きされて不具合が生じる可能性があります。attributesconnectionは関連付けに使ってはならない名前の例です。

3.3 スキーマの更新

関連付けはきわめて便利ですが、残念ながら魔法ではありません。関連付けを使うからには、関連付けの設定に合わせてデータベースのスキーマを常に更新しておく責任が生じます。作成した関連付けにもよりますが、具体的には次の2つの作業が必要になります。

  1. belongs_to関連付けを使う場合は、外部キーを作成する必要があります。
  2. has_and_belongs_to_many関連付けを使う場合は、適切なjoinテーブルを作成する必要があります。
3.3.1 belongs_to関連付けに対応する外部キーを作成する

belongs_to関連付けを宣言したら、対応する外部キーを作成する必要があります。以下のモデルを例にとります。

class Book < ApplicationRecord
  belongs_to :author
end

上の宣言は、以下のbooksテーブル上で対応する外部キーカラムと整合している必要があります。作成した直後のテーブルの場合、マイグレーションは次のような感じになります。

class CreateBooks < ActiveRecord::Migration[7.0]
  def change
    create_table :books do |t|
      t.datetime   :published_at
      t.string     :book_number
      t.references :author
    end
  end
end

一方、既存のテーブルの場合、マイグレーションは次のような感じになります。

class AddAuthorToBooks < ActiveRecord::Migration[7.0]
  def change
    add_reference :books, :author
  end
end

データベースレベルでの参照整合性を強制するには、上の‘reference’カラム宣言にforeign_key: trueオプションを追加します。

3.3.2 has_and_belongs_to_many関連付けに対応するjoinテーブルを作成する

has_and_belongs_to_many関連付けを作成した場合は、それに対応するjoinテーブルを明示的に作成する必要があります。joinテーブルの名前が:join_tableオプションで明示的に指定されていない場合、Active Recordは2つのクラス名をABC順に結合して、joinテーブル名を作成します。たとえばAuthorモデルとBookモデルを結合する場合、'a'は辞書で'b'より先に出現するので "authors_books"というデフォルトのjoinテーブル名が使われます。

モデル名の優先順位はStringクラスの<=>演算子を用いて算出されます。つまり、2つの文字列の長さが異なり、短い方が長い方の途中まで完全に一致している場合は、長い方の文字列は短い方よりも優先順位が高くなるということです。たとえば、"paper_boxes" テーブルと "papers" テーブルがある場合、"paper_boxes" の方が長いので、これらのjoinテーブル名は "papers_paper_boxes" になりそうな気がします。しかし実際に生成されるjoinテーブル名は "paper_boxes_papers" になります(これは多くのエンコーディングでアンダースコア '_' の方が 's' よりも並び順が前になるためです)。

生成された名前がどのようなものであれ、適切なマイグレーションを実行してjoinテーブルを生成する必要があります。以下の関連付けを例にとって考えてみましょう。

class Assembly < ApplicationRecord
  has_and_belongs_to_many :parts
end

class Part < ApplicationRecord
  has_and_belongs_to_many :assemblies
end

この関連付けに対応するassemblies_partsテーブルをマイグレーションで作成する必要があります。作成するテーブルには主キーを設定しないでください。

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

    add_index :assemblies_parts, :assembly_id
    add_index :assemblies_parts, :part_id
  end
end

このテーブルはモデルを表現していないので、create_tableid: falseを渡します。こうしておかないとこの関連付けは正常に動作しません。モデルのIDが破損する、IDの競合で例外が発生するなど、has_and_belongs_to_many関連付けの動作が怪しい場合は、この設定を忘れていないかどうか再度確認してみてください。

create_join_tableメソッドを使うことも可能です。

class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[7.0]
  def change
    create_join_table :assemblies, :parts do |t|
      t.index :assembly_id
      t.index :part_id
    end
  end
end

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

デフォルトでは、関連付けによって探索されるのは、現在のモジュールのスコープ内にあるオブジェクトだけです。Active Recordモデルをモジュール内で宣言する場合は、この点に注意する必要があります。

module MyApplication
  module Business
    class Supplier < ApplicationRecord
      has_one :account
    end

    class Account < ApplicationRecord
      belongs_to :supplier
    end
  end
end

上のコードは正常に動作します。これは、SupplierクラスとAccountクラスが同じスコープ内で定義されているためです。 しかし下のコードは動作しません。SupplierクラスとAccountクラスが異なるスコープ内で定義されているためです。

module MyApplication
  module Business
    class Supplier < ApplicationRecord
      has_one :account
    end
  end

  module Billing
    class Account < ApplicationRecord
      belongs_to :supplier
    end
  end
end

あるモデルを、別の名前空間にあるモデルを関連付けるには、関連付けの宣言で以下のように完全なクラス名を指定する必要があります

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

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

3.5 双方向関連付け

通常は、関連付けを双方向に機能させるために、2つのモデルの両方に関連付けを定義する必要があります。

class Author < ApplicationRecord
  has_many :books
end

class Book < ApplicationRecord
  belongs_to :author
end

Active Recordは、これらの関連付けの設定から、2つのモデルが双方向の関連を共有していることを自動的に認識しようとします。以下に示すように、Active RecordはAuthorオブジェクトを1個だけ読み出すことで、アプリケーションの効率を高めるとともにデータの一貫性を維持します。

irb> a = Author.first
irb> b = a.books.first
irb> a.first_name == b.author.first_name
=> true
irb> a.first_name = 'David'
irb> a.first_name == b.author.first_name
=> true

Active Recordでは、ほとんどの標準的な名前同士の関連付けついて自動認識をサポートしています。ただしActive Recordは、:through:foreign_keyオプションを含む双方向関連付けを自動認識しません。 また、関連付け自身でカスタムスコープが使われている場合も、config.active_record.automatic_scope_inversingtrueに設定しない限り自動認識しません(新しいアプリケーションではデフォルトでconfig.active_record.automatic_scope_inversing = trueが設定されます)。

たとえば、次のようなモデルを宣言したケースを考えてみましょう。

class Author < ApplicationRecord
  has_many :books
end

class Book < ApplicationRecord
  belongs_to :writer, class_name: 'Author', foreign_key: 'author_id'
end

この場合、Active Recordは双方向関連付けを自動的に認識しません。

irb> a = Author.first
irb> b = a.books.first
irb> a.first_name == b.writer.first_name
=> true
irb> a.first_name = 'David'
irb> a.first_name == b.writer.first_name
=> false

Active Recordが提供している:inverse_ofオプションを使うと、双方向関連付けを明示的に宣言できます。

class Author < ApplicationRecord
  has_many :books, inverse_of: 'writer'
end

class Book < ApplicationRecord
  belongs_to :writer, class_name: 'Author', foreign_key: 'author_id'
end

has_manyの関連付けを宣言するときに:inverse_ofオプションも追加することで、Active Recordは双方向関連付けを認識するようになります。

irb> a = Author.first
irb> b = a.books.first
irb> a.first_name == b.writer.first_name
=> true
irb> a.first_name = 'David'
irb> a.first_name == b.writer.first_name
=> true

4 関連付けの詳細な参照

本セクションでは、関連付けの種別ごとの詳細を解説します。関連付けの宣言によって追加されるメソッドやオプションについても説明します。

4.1 belongs_to関連付けの詳細な参照

belongs_to関連付けは、データベースの観点では、このモデルのテーブルに別のテーブルへの参照を表すカラムが含まれていることを意味します。belongs_to関連付けは、状況に応じて1対1または1対多のリレーションを設定するのに利用できます。相手側クラスのテーブルが1対1のリレーションで参照を含んでいる場合は、has_oneを使うべきです。

4.1.1 belongs_toで追加されるメソッド

belongs_to関連付けを宣言したクラスでは、以下の8つのメソッドが自動的に利用できるようになります。

  • association
  • association=(associate)
  • build_association(attributes = {})
  • create_association(attributes = {})
  • create_association!(attributes = {})
  • reload_association
  • association_changed?
  • association_previously_changed?

上のメソッド名のassociationの部分はプレースホルダなので、belongs_toの第1引数として渡されるものの名前で読み替えてください。

たとえば以下の宣言があるとします。

class Book < ApplicationRecord
  belongs_to :author
end

このとき、Bookモデルのインスタンスで以下のメソッドが使えるようになります。

author
author=
build_author
create_author
create_author!
reload_author
author_changed?
author_previously_changed?

新しく作成したhas_one関連付けまたはbelongs_to関連付けを初期化するには、association.buildメソッドではなくbuild_で始まるメソッドを使う必要があります(association.buildhas_many関連付けやhas_and_belongs_to_many関連付けで使われます)。関連付けを作成する場合も、create_で始まるメソッドをお使いください。

4.1.1.1 association

associationメソッドは、関連付けられたオブジェクトを返します。関連付けられたオブジェクトがない場合はnilを返します。

@author = @book.author

関連付けられたオブジェクトがデータベースから既に取得されている場合は、キャッシュされたものを返します。キャッシュを読み出さずにデータベースから直接読み込みたい場合は、親オブジェクトが持つ#reload_associationメソッドを呼び出します。

@author = @book.reload_author
4.1.1.2 association=(associate)

association=メソッドは、引数のオブジェクトをそのオブジェクトに関連付けます。その背後では、関連付けられたオブジェクトから主キーを取り出し、そのオブジェクトの外部キーにその同じ値を設定しています。

@book.author = @author
4.1.1.3 build_association(attributes = {})

build_associationメソッドは、関連付けられた型の新しいオブジェクトを返します。返されるオブジェクトは、渡された属性に基いてインスタンス化され、外部キーを経由するリンクが設定されます。関連付けられたオブジェクトはまだ保存されていないことにご注意ください。

@author = @book.build_author(author_number: 123,
                                  author_name: "John Doe")
4.1.1.4 create_association(attributes = {})

create_associationメソッドは、関連付けられた型の新しいオブジェクトを返します。このオブジェクトは、渡された属性を用いてインスタンス化され、そのオブジェクトの外部キーを介してリンクが設定されます。そして、関連付けられたモデルで指定されているバリデーションがすべてパスすると、この関連付けられたオブジェクトは保存されます

@author = @book.create_author(author_number: 123,
                                   author_name: "John Doe")
4.1.1.5 create_association!(attributes = {})

上のcreate_associationと同じですが、レコードが無効な場合にActiveRecord::RecordInvalidがraiseされる点が異なります。

4.1.1.6 association_changed?

association_changed?メソッドは、新しい関連付けオブジェクトが代入された場合にtrueを返します。外部キーは次の保存で更新されます。

@book.author # => #<Author author_number: 123, author_name: "John Doe">
@book.author_changed? # => false

@book.author = Author.second # => #<Author author_number: 456, author_name: "Jane Smith">
@book.author_changed? # => true

@book.save!
@book.author_changed? # => false
4.1.1.7 association_previously_changed?

association_previously_changed?メソッドは、関連付けが前回の保存で更新されて新しい関連付けオブジェクトを参照している場合にtrueを返します。

@book.author # => #<Author author_number: 123, author_name: "John Doe">
@book.author_previously_changed? # => false

@book.author = Author.second # => #<Author author_number: 456, author_name: "Jane Smith">
@book.save!
@book.author_previously_changed? # => true
4.1.2 belongs_toのオプション

Railsのデフォルトのbelongs_to関連付けは優秀なので、ほとんどの場合カスタマイズ不要ですが、関連付けの参照をカスタマイズしたい場合もあります。これは、作成するときに渡すオプションとスコープブロックで簡単にカスタマイズできます。たとえば、以下のようなオプションを関連付けに追加できます。

class Book < ApplicationRecord
  belongs_to :author, touch: :books_updated_at,
    counter_cache: true
end

belongs_to関連付けでは以下のオプションがサポートされています。

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

:autosaveオプションをtrueに設定すると、親オブジェクトが保存されるたびに、読み込まれているすべての関連付けメンバを保存し、destroyフラグが立っているメンバを破棄します。 :autosavefalseに設定することと、:autosaveオプションを未設定のままにしておくことは同じではありません:autosaveオプションを渡さない場合、関連付けられたオブジェクトのうち、新しいオブジェクトは保存されますが、更新されたオブジェクトは保存されません。

4.1.2.2 :class_name

関連付けの相手となるオブジェクト名を関連付け名から生成できない事情がある場合、:class_nameオプションを用いてモデル名を直接指定できます。たとえば、書籍(book)が著者(author)に従属しているが実際の著者のモデル名がPatronである場合には、以下のように指定します。

class Book < ApplicationRecord
  belongs_to :author, class_name: "Patron"
end
4.1.2.3 :counter_cache

:counter_cacheオプションは、従属しているオブジェクトの個数の検索効率を向上させます。以下のモデルで説明します。

class Book < ApplicationRecord
  belongs_to :author
end
class Author < ApplicationRecord
  has_many :books
end

上の宣言のままでは、@author.books.sizeの値を知るためにデータベースに対してCOUNT(*)クエリを実行する必要があります。この呼び出しを避けるために、「従属している側のモデル(belongs_toを宣言している側のモデル)」にカウンタキャッシュを追加できます。

class Book < ApplicationRecord
  belongs_to :author, counter_cache: true
end
class Author < ApplicationRecord
  has_many :books
end

上のように宣言すると、キャッシュ値が最新の状態に保たれ、次にsizeメソッドが呼び出されたときにその値が返されます。

ここで1つ注意が必要です。:counter_cacheオプションはbelongs_to宣言で指定しますが、実際に個数を数えたいカラムは「相手の」モデル(関連付け先のモデル)の側に追加する必要があります。上の場合は、Authorモデルにbooks_countカラムを追加する必要があります。

counter_cacheオプションでtrueの代わりに任意のカラム名を設定すると、デフォルトのカラム名をオーバーライドできます。以下は、books_countの代わりにcount_of_booksを設定した場合の例です。

class Book < ApplicationRecord
  belongs_to :author, counter_cache: :count_of_books
end

class Author < ApplicationRecord
  has_many :books
end

これは、関連付けのbelongs_to側で:counter_cacheオプションを設定するだけでできます。

カウンタキャッシュ用のカラムは、attr_readonlyによってそのモデルの読み出し専用属性リストに追加されます。

4.1.2.4 :dependent

:dependentで指定するオプションの挙動は以下のとおりです。

  • :destroy: オブジェクトが削除されるときに、関連付けられたオブジェクトのdestroyメソッドが実行されます。
  • :delete: オブジェクトが削除されるときに、関連付けられたオブジェクトが直接データベースから削除されます。destroyメソッドは実行されません。
  • :destroy_async: オブジェクトが削除されるときに、ActiveRecord::DestroyAssociationAsyncJobジョブがジョブキューに入り、関連付けられたオブジェクトでdestroyメソッドを呼び出します。このジョブが動作するには、Active Jobをセットアップしておく必要があります。

このオプションは、他のクラスのhas_many関連付けとつながりのあるbelongs_to関連付けに対して使ってはいけません。孤立レコードがデータベースに残ってしまう可能性があります。

4.1.2.5 :foreign_key

Railsの規約では、相手のモデルを指す外部キーを保持しているjoinテーブル上のカラム名については、そのモデル名にサフィックス_idを追加した関連付け名が使われることを前提とします。:foreign_keyオプションを使えば、外部キーの名前を直接指定できます。

class Book < ApplicationRecord
  belongs_to :author, class_name: "Patron",
                        foreign_key: "patron_id"
end

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

4.1.2.6 :primary_key

Railsの規約では、idカラムはそのテーブルの主キーとして使われます。:primary_keyオプションを指定すると、指定された別のカラムを主キーとして設定できます

たとえば、usersテーブルにguidという主キーがあるとします。そのguidカラムに、別のtodosテーブルの外部キーであるuser_idカラムを使いたい場合は、次のようにprimary_keyを設定します。

class User < ApplicationRecord
  self.primary_key = 'guid' # 主キーがguidになる
end

class Todo < ApplicationRecord
  belongs_to :user, primary_key: 'guid'
end

@user.todos.createを実行すると、@todoレコードは@userguidとしてuser_idを持つようになります。

4.1.2.7 :inverse_of

:inverse_ofオプションは、その関連付けの逆関連付けとなるhas_many関連付けまたはhas_one関連付けの名前を指定します。

class Author < ApplicationRecord
  has_many :books, inverse_of: :author
end

class Book < ApplicationRecord
  belongs_to :author, inverse_of: :books
end
4.1.2.8 :polymorphic

:polymorphicオプションにtrueを指定すると、ポリモーフィック関連付けを指定できます。ポリモーフィック関連付けについて詳しくは本ガイドのポリモーフィック関連付けを参照してください。

4.1.2.9 :touch

:touchオプションをtrueに設定すると、そのオブジェクトがsaveまたはdestroyされたときに、関連付けられたオブジェクトのupdated_atタイムスタンプやupdated_onタイムスタンプが常に現在の時刻に設定されます。

class Book < ApplicationRecord
  belongs_to :author, touch: true
end

class Author < ApplicationRecord
  has_many :books
end

上のBookは、関連付けられているAuthorのタイムスタンプをsaveまたはdestroyするときに更新します。更新時に特定のタイムスタンプ属性を指定することも可能です。

class Book < ApplicationRecord
  belongs_to :author, touch: :books_updated_at
end
4.1.2.10 :validate

:validateオプションをtrueに設定すると、新たに関連付けられたオブジェクトを保存するときに必ずバリデーションされます。デフォルトはfalseであり、この場合新たに関連付けられたオブジェクトは保存時にバリデーションされません。

4.1.2.11 :optional

:optionalオプションをtrueに設定すると、関連付けされたオブジェクトの存在バリデーションが実行されなくなります。このオプションはデフォルトではfalseです。

4.1.3 belongs_toのスコープ

belongs_toで使われるクエリをカスタマイズしたい場合があります。スコープブロックを用いてこのようなカスタマイズを行えます。以下に例を示します。

class Book < ApplicationRecord
  belongs_to :author, -> { where active: true }
end

スコープブロック内では標準のクエリメソッドをすべて利用できます。ここでは以下について説明します。

  • where
  • includes
  • readonly
  • select
4.1.3.1 where

whereメソッドは、関連付けられるオブジェクトが満たすべき条件を指定します。

class Book < ApplicationRecord
  belongs_to :author, -> { where active: true }
end
4.1.3.2 includes

includesメソッドを使うと、その関連付けが使われるときにeager loadingすべき第2関連付けを指定できます。以下のモデルを例に考えてみましょう。

class Chapter < ApplicationRecord
  belongs_to :book
end

class Book < ApplicationRecord
  belongs_to :author
  has_many :chapters
end

class Author < ApplicationRecord
  has_many :books
end

章(chapter)から本の著者名(author)を@chapter.book.authorのように直接取り出す頻度が高い場合は、以下のようにChapterからBookへの関連付けでAuthorをあらかじめincludesしておくと、クエリが減って効率が高まります。

class Chapter < ApplicationRecord
  belongs_to :book, -> { includes :author }
end

class Book < ApplicationRecord
  belongs_to :author
  has_many :chapters
end

class Author < ApplicationRecord
  has_many :books
end

直接の関連付けではincludesを使う必要はありません。Book belongs_to :authorのような直接の関連付けでは必要に応じて自動的にeager loadingされます。

4.1.3.3 readonly

readonlyを指定すると、関連付けられたオブジェクトを読み出し専用で取り出します。

4.1.3.4 select

selectメソッドを使うと、関連付けられたオブジェクトのデータ取り出しに使われるSQLのSELECT句をオーバーライドできます。Railsはデフォルトですべてのカラムを取り出します。

selectbelongs_to関連付けで使う場合は、正しい結果を得るために:foreign_keyオプションも設定してください。

4.1.4 関連付けられたオブジェクトが存在するかどうかを確認する

association.nil?メソッドを用いて、関連付けられたオブジェクトが存在するかどうかを確認できます。

if @book.author.nil?
  @msg = "この本の著者が見つかりません"
end
4.1.5 オブジェクトが保存されるタイミング

オブジェクトをbelongs_to関連付けに割り当てても、オブジェクトが自動的に保存されるわけではありません。関連付けられたオブジェクトも保存されません。

4.2 has_one関連付けの詳細な参照

has_one関連付けは相手のモデルと1対1対応します。データベースの観点では、この関連付けは相手のクラスが外部キーを持つことを意味します。相手ではなく自分のクラスが外部キーを持つのであれば、belongs_toを使うべきです。

4.2.1 has_oneで追加されるメソッド

has_one関連付けを宣言したクラスでは、以下の6つのメソッドが自動的に利用できるようになります。

  • association
  • association=(associate)
  • build_association(attributes = {})
  • create_association(attributes = {})
  • create_association!(attributes = {})
  • reload_association

上のメソッド名のassociationの部分はプレースホルダなので、has_oneの第1引数として渡されるものの名前で読み替えてください。

class Supplier < ApplicationRecord
  has_one :account
end

これにより、Supplierモデルのインスタンスで以下のメソッドが使えるようになります。

account
account=
build_account
create_account
create_account!
reload_account

新しく作成したhas_one関連付けまたはbelongs_to関連付けを初期化するには、association.buildメソッドではなくbuild_で始まるメソッドを使う必要があります(association.buildhas_many関連付けやhas_and_belongs_to_many関連付けで使われます)。関連付けを作成する場合も、create_で始まるメソッドをお使いください。

4.2.1.1 association

associationメソッドは、関連付けられたオブジェクトを返します。関連付けられたオブジェクトがない場合はnilを返します。

@account = @supplier.account

関連付けられたオブジェクトがデータベースから既に取得されている場合は、キャッシュされたものを返します。キャッシュを読み出さずにデータベースから直接読み込みたい場合は、親オブジェクトが持つ#reload_associationメソッドを呼び出します。

@account = @supplier.reload_account
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メソッドは、関連付けられた型の新しいオブジェクトを返します。このオブジェクトは、渡された属性を用いてインスタンス化され、そのオブジェクトの外部キーを介してリンクが設定されます。そして、関連付けられたモデルで指定されているバリデーションがすべてパスすると、この関連付けられたオブジェクトは保存されます

@account = @supplier.create_account(terms: "Net 30")
4.2.1.5 create_association!(attributes = {})

上のcreate_associationと同じですが、レコードが無効な場合にActiveRecord::RecordInvalidがraiseされる点が異なります。

4.2.2 has_oneのオプション

Railsのデフォルトのhas_one関連付けは優秀なので、ほとんどの場合カスタマイズ不要ですが、関連付けの参照をカスタマイズしたい場合もあります。これは、作成するときに渡すオプションで簡単にカスタマイズできます。たとえば、以下のようなオプションを関連付けに追加できます。

class Supplier < ApplicationRecord
  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
  • :touch
  • :validate
4.2.2.1 :as

:asオプションを設定すると、ポリモーフィック関連付けを指定できます。ポリモーフィック関連付けについて詳しくは本ガイドのポリモーフィック関連付けを参照してください。

4.2.2.2 :autosave

:autosaveオプションをtrueに設定すると、親オブジェクトが保存されるたびに、読み込まれているすべてのメンバを保存し、destroyフラグが立っているメンバを破棄します。 :autosavefalseに設定することと、:autosaveオプションを未設定のままにしておくことは同じではありません:autosaveオプションを渡さない場合、関連付けられたオブジェクトのうち、新しいオブジェクトは保存されますが、更新されたオブジェクトは保存されません。

4.2.2.3 :class_name

関連付けの相手となるオブジェクト名を関連付け名から生成できない事情がある場合、:class_nameオプションを用いてモデル名を直接指定できます。たとえば、Supplier(供給者)がAccount(アカウント)を1つ持ち、アカウントを含むモデルの実際の名前がAccountではなくBillingになっている場合、以下のようにモデル名を指定できます。

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

オブジェクトのオーナーがdestroyされたときの、それに関連付けられたオブジェクトの扱いを制御します。

  • :destroy: 関連付けられたオブジェクトも同時にdestroyされます。
  • :delete: 関連付けられたオブジェクトはデータベースから直接削除されます(コールバックは実行されません)。
  • :destroy_async: オブジェクトが削除されるときに、ActiveRecord::DestroyAssociationAsyncJobジョブがジョブキューに入り、関連付けられたオブジェクトでdestroyメソッドを呼び出します。このジョブが動作するには、Active Jobをセットアップしておく必要があります。
  • :nullify: 外部キーがNULLに設定されます。ポリモーフィックなtypeカラムもポリモーフィック関連付けでNULLに設定されます。コールバックは実行されません。
  • :restrict_with_exception: 関連付けられたレコードがある場合にActiveRecord::DeleteRestrictionError例外が発生します。
  • :restrict_with_error: 関連付けられたオブジェクトがある場合にエラーがオーナーに追加されます。

NOT NULLデータベース制約のある関連付けでは、:nullifyオプションを与えないようにする必要があります。そのような関連付けをdestroyするときにdependentを設定しなかった場合、関連付けられたオブジェクトを変更できなくなってしまいます。これは、最初に関連付けられたオブジェクトの外部キーがNULL値になってしまい、この値は許されていないためです。

4.2.2.5 :foreign_key

Railsの規約では、相手のモデル上の外部キーを保持しているカラム名については、そのモデル名にサフィックス_idを追加した関連付け名が使われることを前提とします。:foreign_keyオプションを使うと外部キーの名前を直接指定できます。

class Supplier < ApplicationRecord
  has_one :account, foreign_key: "supp_id"
end

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

4.2.2.6 :inverse_of

:inverse_ofオプションは、その関連付けの逆関連付けとなるbelongs_to関連付けの名前を指定します。

class Supplier < ApplicationRecord
  has_one :account, inverse_of: :supplier
end

class Account < ApplicationRecord
  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関連付けで用いる関連付け元の名前を指定します。

4.2.2.9 :source_type

:source_typeオプションは、ポリモーフィック関連付けを介するhas_one :through関連付けで、関連付け元の型を指定します。

class Author < ApplicationRecord
  has_one :book
  has_one :hardback, through: :book, source: :format, source_type: "Hardback"
  has_one :dust_jacket, through: :hardback
end

class Book < ApplicationRecord
  belongs_to :format, polymorphic: true
end

class Paperback < ApplicationRecord; end

class Hardback < ApplicationRecord
  has_one :dust_jacket
end

class DustJacket < ApplicationRecord; end
4.2.2.10 :through

:throughオプションは、このガイドで既に説明したhas_one :through関連付けのクエリを実行する際に経由するjoinモデルを指定します。

4.2.2.11 :touch

:touchオプションをtrueに設定すると、そのオブジェクトがsaveまたはdestroyされたときに、関連付けられたオブジェクトのupdated_atタイムスタンプやupdated_onタイムスタンプが常に現在の時刻に設定されます。

class Supplier < ApplicationRecord
  has_one :account, touch: true
end

class Account < ApplicationRecord
  belongs_to :supplier
end

上のSupplierは、関連付けられているAccountのタイムスタンプをsaveまたはdestroyするときに更新します。更新時に特定のタイムスタンプ属性を指定することも可能です。

class Supplier < ApplicationRecord
  has_one :account, touch: :suppliers_updated_at
end
4.2.2.12 :validate

:validateオプションをtrueに設定すると、新たに関連付けられたオブジェクトが保存時に必ずバリデーションされます。デフォルトはfalseであり、この場合新たに関連付けられたオブジェクトは保存時に バリデーション されません。

4.2.3 has_oneのスコープ

has_oneで使われるクエリをカスタマイズしたい場合があります。スコープブロックを用いてこのようなカスタマイズを行えます。以下に例を示します。

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

スコープブロック内では標準のクエリメソッドをすべて利用できます。ここでは以下について説明します。

  • where
  • includes
  • readonly
  • select
4.2.3.1 where

whereは、関連付けられるオブジェクトが満たすべき条件を指定します。

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

includesメソッドを使うと、その関連付けが使われるときにeager loadingすべき第2関連付けを指定できます。以下のモデルを例に考えてみましょう。

class Supplier < ApplicationRecord
  has_one :account
end

class Account < ApplicationRecord
  belongs_to :supplier
  belongs_to :representative
end

class Representative < ApplicationRecord
  has_many :accounts
end

供給者(supplier)からアカウントの代表(representative)を@supplier.account.representativeのように直接取り出す頻度が高い場合は、SupplierからAccountへの関連付けにRepresentativeをあらかじめincludesしておくと、クエリが減って効率が高まります。

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

class Account < ApplicationRecord
  belongs_to :supplier
  belongs_to :representative
end

class Representative < ApplicationRecord
  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 = "この供給者のアカウントがありません"
end
4.2.5 オブジェクトが保存されるタイミング

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

関連付けられているオブジェクト同士のいずれか一方がバリデーションによって保存に失敗すると、割り当ての状態がfalseになり、割り当てはキャンセルされます。

親オブジェクト(has_one関連付けを宣言している側のオブジェクト)が保存されていない場合(つまりnew_record?trueを返す場合)、子オブジェクトは保存されません。親オブジェクトが保存されると、子オブジェクトは自動的に保存されます。

has_one関連付けにオブジェクトを割り当てて、しかもそのオブジェクトを保存したくない場合は、build_associationメソッドをお使いください。

4.3 has_many関連付けの詳細な参照

has_many関連付けは、他のモデルとの間に「1対多」のつながりを作成します。データベースの観点では、この関連付けは、他のクラスがこのクラスのインスタンスを参照する外部キーを持っていることを意味します。

4.3.1 has_manyで追加されるメソッド

has_many関連付けを宣言したクラスでは、以下の17のメソッドが自動的に利用できるようになります。

上のメソッド名のcollectionの部分はプレースホルダなので、has_manyの第1引数として渡されるものの名前で読み替えてください。また、collection_singularの部分は名前を単数形にして読み替えてください。たとえば以下の宣言があるとします。

class Author < ApplicationRecord
  has_many :books
end

これにより、Authorモデルのインスタンスで以下のメソッドが使えるようになります。

books
books<<(object, ...)
books.delete(object, ...)
books.destroy(object, ...)
books=(objects)
book_ids
book_ids=(ids)
books.clear
books.empty?
books.size
books.find(...)
books.where(...)
books.exists?(...)
books.build(attributes = {}, ...)
books.create(attributes = {})
books.create!(attributes = {})
books.reload
4.3.1.1 collection

collectionメソッドは、関連付けられたすべてのオブジェクトのリレーションを返します。関連付けられたオブジェクトがない場合は、空のリレーションを1つ返します。

@books = @author.books
4.3.1.2 collection<<(object, ...)

collection<<メソッドは、1つ以上のオブジェクトをコレクションに追加します。このとき、追加されるオブジェクトの外部キーは、呼び出し側モデルの主キーに設定されます。

@author.books << @book1
4.3.1.3 collection.delete(object, ...)

collection.deleteメソッドは、外部キーをNULLに設定することで、コレクションから1個以上のオブジェクトを削除します。

@author.books.delete(@book1)

削除の方法はこれだけではありません。オブジェクト同士がdependent: :destroyで関連付けられている場合はdestroyされますが、オブジェクト同士がdependent: :delete_allで関連付けられている場合はdeleteされるのでご注意ください。

4.3.1.4 collection.destroy(object, ...)

collection.destroyは、コレクションに関連付けられているオブジェクトに対してdestroyを実行することで、コレクションから1つまたは複数のオブジェクトを削除します。

@author.books.destroy(@book1)

この場合オブジェクトは無条件にデータベースから削除されます。このとき:dependentオプションはすべて無視されます。

4.3.1.5 collection=(objects)

collection=メソッドは、削除や追加を適宜実行することで、コレクションに渡されたオブジェクトだけが含まれるようにします。変更の結果はデータベースで永続化されます。

4.3.1.6 collection_singular_ids

collection_singular_idsメソッドは、そのコレクションに含まれるオブジェクトのidを配列にしたものを返します。

@book_ids = @author.book_ids
4.3.1.7 collection_singular_ids=(ids)

collection_singular_ids=メソッドは、削除や追加を適宜実行することで、指定された主キーのidを持つオブジェクトだけが含まれるようにします。変更の結果はデータベースで永続化されます。

4.3.1.8 collection.clear

collection.clearメソッドは、dependentオプションで指定された戦略に沿って、コレクションからすべてのオブジェクトを削除します。オプションが渡されなかった場合は、デフォルトの戦略に従います。デフォルトの戦略は、has_many :throughの関連付けの場合はdelete_allが指定され、has_manyの関連付けの場合は外部キーがNULLに設定されます。

@author.books.clear

dependent: :delete_allの場合と同様に、オブジェクトがdependent: :destroyまたはdependent: :destroy_asyncで関連付けされていた場合、それらのオブジェクトは削除されます。

4.3.1.9 collection.empty?

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

<% if @author.books.empty? %>
  No Books Found
<% end %>
4.3.1.10 collection.size

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

@book_count = @author.books.size
4.3.1.11 collection.find(...)

collection.findメソッドは、コレクションに含まれるオブジェクトを検索します。

@available_book = @author.books.find(1)
4.3.1.12 collection.where(...)

collection.whereメソッドは、コレクションに含まれているオブジェクトを指定された条件に基いて検索します。このメソッドではオブジェクトは遅延読み込み(lazy load)されるので、オブジェクトに実際にアクセスするときだけデータベースへのクエリが発生します。

@available_books = @author.books.where(available: true) # クエリはまだ発生しない
@available_book = @available_books.first                # ここでクエリが発生する
4.3.1.13 collection.exists?(...)

collection.exists?メソッドは、指定された条件に合うオブジェクトがコレクションの中に存在するかどうかをチェックします。

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

collection.buildメソッドは、関連付けされた型のオブジェクトまたはオブジェクトの配列を返します。返されるオブジェクトは、渡された属性に基いてインスタンス化され、外部キーを経由するリンクが作成されます。関連付けられたオブジェクトはまだ保存されていないことにご注意ください。

@book = @author.books.build(published_at: Time.now,
                            book_number: "A12345")

@books = @author.books.build([
  { published_at: Time.now, book_number: "A12346" },
  { published_at: Time.now, book_number: "A12347" }
])
4.3.1.15 collection.create(attributes = {})

collection.createメソッドは、関連付けされた型の新しいオブジェクトまたはオブジェクトの配列を返します。このオブジェクトは、渡された属性を用いてインスタンス化され、そのオブジェクトの外部キーを介してリンクが作成されます。そして、関連付けられたモデルで指定されているバリデーションがすべてパスすると、この関連付けられたオブジェクトは保存されます

@book = @author.books.create(published_at: Time.now,
                                 book_number: "A12345")

@books = @author.books.create([
  { published_at: Time.now, book_number: "A12346" },
  { published_at: Time.now, book_number: "A12347" }
    ...
4.3.1.16 collection.create!(attributes = {})

上のcollection.createと同じですが、レコードが無効な場合にActiveRecord::RecordInvalidがraiseされる点が異なります。

4.3.1.17 collection.reload

collection.reloadメソッドは、関連付けられたすべてのオブジェクトのリレーションを1つ返し、データベースを強制的に読み出します。関連付けられたオブジェクトがない場合は、空のリレーションを1つ返します。

@books = @author.books.reload
4.3.2 has_manyのオプション

Railsのデフォルトのhas_many関連付けは優秀なので、ほとんどの場合カスタマイズ不要ですが、関連付けの参照をカスタマイズしたい場合もあります。これは、作成するときにオプションを渡すことで簡単にカスタマイズできます。たとえば、以下のようなオプションを関連付けに追加できます。

class Author < ApplicationRecord
  has_many :books, dependent: :delete_all, validate: false
end

has_many関連付けでは以下のオプションがサポートされます。

  • :as
  • :autosave
  • :class_name
  • :counter_cache
  • :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に設定すると、親オブジェクトが保存されるたびに、読み込まれているすべてのメンバを保存し、destroyフラグが立っているメンバを破棄します。 :autosavefalseに設定することと、:autosaveオプションを未設定のままにしておくことは同じではありません:autosaveオプションを渡さない場合、関連付けられたオブジェクトのうち、新しいオブジェクトは保存されますが、更新されたオブジェクトは保存されません。

4.3.2.3 :class_name

関連付けの相手となるオブジェクト名を関連付け名から生成できない事情がある場合、:class_nameオプションを用いてモデル名を直接指定できます。たとえば、1人の著者(author)が複数の書籍(books)を持っているが、実際の書籍モデル名がTransactionである場合には以下のように指定します。

class Author < ApplicationRecord
  has_many :books, class_name: "Transaction"
end
4.3.2.4 :counter_cache

このオプションは、:counter_cacheオプションを任意の名前に変更したい場合に使います。このオプションは、belongs_toの関連付け:counter_cacheの名前を変更したときにのみ必要になります。

4.3.2.5 :dependent

オーナーオブジェクトがdestroyされたときに、オーナーに関連付けられたオブジェクトの扱いを制御します。

  • :destroy: 関連付けられたオブジェクトもすべて同時にdestroyされます。
  • :delete_all: 関連付けられたオブジェクトはすべてデータベースから直接削除されます(コールバックは実行されません)。
  • :destroy_async: オブジェクトが削除されるときに、ActiveRecord::DestroyAssociationAsyncJobジョブがジョブキューに入り、関連付けられたオブジェクトでdestroyメソッドを呼び出します。このジョブが動作するには、Active Jobをセットアップしておく必要があります。
  • :nullify: 外部キーはNULLに設定されます。ポリモーフィックなtypeカラムもポリモーフィック関連付けでNULLに設定されます。コールバックは実行されません。
  • :restrict_with_exception: 関連付けられたレコードがある場合にActiveRecord::DeleteRestrictionError例外が発生します。
  • :restrict_with_error: 関連付けられたオブジェクトがある場合にエラーがオーナーに追加されます。

:destroyオプションや:delete_allオプションは、collection.deleteメソッドやcollection=メソッドのセマンティクス(意味)にも影響します(コレクションから削除されると、関連付けられたオブジェクトもdestroyされます)。

4.3.2.6 :foreign_key

Railsの規約では、相手のモデル上の外部キーを保持しているカラム名については、そのモデル名にサフィックス _id を追加した関連付け名が使われることを前提とします。:foreign_keyオプションを使うと外部キーの名前を直接指定できます。

class Author < ApplicationRecord
  has_many :books, foreign_key: "cust_id"
end

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

4.3.2.7 :inverse_of

:inverse_ofオプションは、その関連付けの逆関連付けとなるbelongs_to関連付けの名前を指定します。

class Author < ApplicationRecord
  has_many :books, inverse_of: :author
end

class Book < ApplicationRecord
  belongs_to :author, inverse_of: :books
end
4.3.2.8 :primary_key

Railsの規約では、関連付けの主キーはidカラムに保存されていることを前提とします。:primary_keyオプションで主キーを明示的に指定することでこれを上書きできます。

usersテーブルに主キーとしてidカラムがあり、その他にguidカラムもあるとします。要件として、todosテーブルで(idではなく)guidカラムの値を外部キーとして使いたいとします。これは以下のように実現できます。

class User < ApplicationRecord
  has_many :todos, primary_key: :guid
end

@todo = @user.todos.createを実行すると、@todoレコードのuser_idの値は @userguidになります。

4.3.2.9 :source

:sourceオプションは、has_many :through関連付けで用いる関連付け元の名前を指定します。このオプションが必要になるのは、関連付け名から関連付け元の名前を自動的に推論できない場合のみです。

4.3.2.10 :source_type

:source_typeオプションは、ポリモーフィック関連付けを介するhas_many :through関連付けで、関連付け元の型を指定します。

class Author < ApplicationRecord
  has_many :books
  has_many :paperbacks, through: :books, source: :format, source_type: "Paperback"
end

class Book < ApplicationRecord
  belongs_to :format, polymorphic: true
end

class Hardback < ApplicationRecord; end
class Paperback < ApplicationRecord; end
4.3.2.11 :through

:throughオプションは、本ガイドで既に説明したhas_one :through関連付けのクエリを実行する際に経由するjoinモデルを指定します。

4.3.2.12 :validate

:validateオプションをfalseに設定すると、新たに関連付けられたオブジェクトは保存時にバリデーションされません。デフォルトはtrueであり、この場合新たに関連付けられたオブジェクトは保存時にバリデーションされます。

4.3.3 has_manyのスコープ

has_manyで使われるクエリをカスタマイズしたい場合があります。スコープブロックを用いてこのようなカスタマイズを行えます。以下に例を示します。

class Author < ApplicationRecord
  has_many :books, -> { where processed: true }
end

スコープブロック内では標準のクエリメソッドをすべて利用できます。ここでは以下について説明します。

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

whereは、関連付けられるオブジェクトが満たすべき条件を指定します。

class Author < ApplicationRecord
  has_many :confirmed_books, -> { where "confirmed = 1" },
    class_name: "Book"
end

条件はハッシュで指定することもできます。

class Author < ApplicationRecord
  has_many :confirmed_books, -> { where confirmed: true },
    class_name: "Book"
end

whereオプションでハッシュを用いた場合、この関連付けで作成されたレコードは自動的にこのハッシュを使うスコープに含まれるようになります。この例の場合、@author.confirmed_books.createまたは@author.confirmed_books.buildを実行すると、confirmedカラムの値がtrueの書籍(book)が常に作成されます。

4.3.3.2 extending

extendingメソッドは、関連付けプロキシを拡張する名前付きモジュールを指定します。関連付けの拡張については後述します

4.3.3.3 group

groupメソッドは、結果をグループ化する属性名を1つ指定します。内部的にはSQLのGROUP BY句が使われます。

class Author < ApplicationRecord
  has_many :chapters, -> { group 'books.id' },
                      through: :books
end
4.3.3.4 includes

includesメソッドを使うと、その関連付けが使われるときにeager loadingすべき第2関連付けを指定できます。以下のモデルを例に考えてみましょう。

class Author < ApplicationRecord
  has_many :books
end

class Book < ApplicationRecord
  belongs_to :author
  has_many :chapters
end

class Chapter < ApplicationRecord
  belongs_to :book
end

著者名(author)から本の章(chapter)を@author.books.chaptersのように直接取り出す頻度が高い場合は、AuthorからBookへの関連付けを行なう時にChaptersをあらかじめincludesしておくと、クエリが減って効率が高まります。

class Author < ApplicationRecord
  has_many :books, -> { includes :chapters }
end

class Book < ApplicationRecord
  belongs_to :author
  has_many :chapters
end

class Chapter < ApplicationRecord
  belongs_to :book
end
4.3.3.5 limit

limitメソッドは、関連付けを用いて取得できるオブジェクトの総数の上限を指定するのに使います。

class Author < ApplicationRecord
  has_many :recent_books,
    -> { order('published_at desc').limit(100) },
    class_name: "Book"
end
4.3.3.6 offset

offsetメソッドは、関連付けを用いてオブジェクトを取得する際の開始オフセットを指定します。たとえば、-> { offset(11) }と指定すると、最初の11レコードはスキップされ、12レコード目以降が返されるようになります。

4.3.3.7 order

orderメソッドは、関連付けられたオブジェクトの並び順を指定します。内部的にはSQLのORDER BY句が使われます。

class Author < ApplicationRecord
  has_many :books, -> { 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 < ApplicationRecord
  has_many :readings
  has_many :articles, through: :readings
end
irb> person = Person.create(name: 'John')
irb> article = Article.create(name: 'a1')
irb> person.articles << article
irb> person.articles << article
irb> person.articles.to_a
=> [#<Article id: 5, name: "a1">, #<Article id: 5, name: "a1">]
irb> Reading.all.to_a
=> [#<Reading id: 12, person_id: 5, article_id: 5>, #<Reading id: 13, person_id: 5, article_id: 5>]

上の例の場合、2つのreadingがどちらも同じ記事を指しており、person.articlesを実行すると同じ記事が2件取り出されてしまいます。

今度はdistinctを設定してみましょう。

class Person
  has_many :readings
  has_many :articles, -> { distinct }, through: :readings
end
irb> person = Person.create(name: 'Honda')
irb> article = Article.create(name: 'a1')
irb> person.articles << article
irb> person.articles << article
irb> person.articles.to_a
=> [#<Article id: 7, name: "a1">]
irb> Reading.all.to_a
=> [#<Reading id: 16, person_id: 7, article_id: 7>, #<Reading id: 17, person_id: 7, article_id: 7>]

上の例でも2つのreadingは重複していますが、person.articlesを実行すると1件の記事だけを表示します。これはコレクションが一意のレコードのみを読み出しているからです。

挿入時にも同様に、永続化済みのレコードをすべて一意にする(関連付けを検査したときに重複レコードが決して発生しないようにする)には、テーブル自体にuniqueインデックスを追加する必要があります。たとえばreadingsというテーブルがあるとすると、1人のpersonに記事を1回しか追加できないようにするには、マイグレーションに以下を追加します。

add_index :readings, [:person_id, :article_id], unique: true

インデックスが一意になると、同じ記事をpersonに2回追加しようとすると ActiveRecord::RecordNotUniqueエラーが発生するようになります

irb> person = Person.create(name: 'Honda')
irb> article = Article.create(name: 'a1')
irb> person.articles << article
irb> person.articles << article
ActiveRecord::RecordNotUnique

なお、include?などのRubyメソッドで一意性をチェックすると競合が発生しやすいので注意が必要です。関連付けで強制的に一意にする目的でinclude?を使わないでください。たとえば上のarticleの例では、以下のコードで競合が発生しやすくなります。これは、複数のユーザーが同時にこのコードを実行する可能性があるためです。

person.articles << article unless person.articles.include?(article)
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つのクラスは中間でjoinテーブルを介して関連付けられます。このjoinテーブルには、両方のクラスを参照する外部キーがそれぞれ含まれます。

4.4.1 has_and_belongs_to_manyで追加されるメソッド

has_and_belongs_to_many関連付けを宣言したクラスでは、以下の17のメソッドが自動的に利用できるようになります。

上のメソッド名のcollectionの部分はプレースホルダなので、has_and_belongs_to_manyの第1引数として渡されるものの名前で読み替えてください。また、collection_singularの部分は名前を単数形にして読み替えてください。たとえば以下の宣言があるとします。

class Part < ApplicationRecord
  has_and_belongs_to_many :assemblies
end

これにより、Partモデルのインスタンスで以下のメソッドが使えるようになります。

assemblies
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 = {})
assemblies.create!(attributes = {})
assemblies.reload
4.4.1.1 追加のカラムメソッド

has_and_belongs_to_many関連付けで利用している中間のjoinテーブルが、2つの外部キー以外のカラムを含んでいる場合、これらのカラムは関連付けを介して取り出されるレコードに属性として追加されます。属性が追加されたレコードは常に読み出し専用になります。このようにして読み出された属性に対する変更は保存できないためです。

has_and_belongs_to_many関連付けで使うjoinテーブルにこのような余分なカラムを追加することは非推奨化されています。2つのモデルを多対多リレーションシップで結合するjoinテーブルでこのような複雑な振る舞いが必要な場合は、has_and_belongs_to_manyではなくhas_many :throughをお使いください。

4.4.1.2 collection

collectionメソッドは、関連付けられたすべてのオブジェクトのリレーションを1つ返します。関連付けられたオブジェクトがない場合は、空のリレーションを1つ返します。

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

collection<<メソッドは、joinテーブル上でレコードを作成し、それによって1個以上のオブジェクトをコレクションに追加します。

@part.assemblies << @assembly1

このメソッドはcollection.concatおよびcollection.pushのエイリアスです。

4.4.1.4 collection.delete(object, ...)

collection.deleteメソッドは、joinテーブル上のレコードを削除し、それによって1個以上のオブジェクトをコレクションから削除します。オブジェクトはdestroyされません。

@part.assemblies.delete(@assembly1)
4.4.1.5 collection.destroy(object, ...)

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

@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=メソッドは、指定された主キーidを持つオブジェクトでコレクションの内容を置き換えます。元からあったオブジェクトは削除されます。この変更はデータベースで永続化されます。

4.4.1.9 collection.clear

collection.clearメソッドは、joinテーブル上のレコードを削除し、それによってすべてのオブジェクトをコレクションから削除します。このメソッドを実行しても、関連付けられたオブジェクトはdestroyされません。

4.4.1.10 collection.empty?

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

<% if @part.assemblies.empty? %>
  ※この部品はどの完成品でも使われていません。
<% end %>
4.4.1.11 collection.size

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

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

collection.findメソッドは、コレクションに含まれるオブジェクトを検索します。

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

collection.whereメソッドは、コレクションに含まれているオブジェクトを指定された条件に基いて検索します。このメソッドではオブジェクトは遅延読み込み(lazy load)されるので、オブジェクトに実際にアクセスするときだけデータベースへのクエリが発生します。

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

collection.exists?メソッドは、指定された条件に合うオブジェクトがコレクションの中に存在するかどうかをチェックします。

4.4.1.15 collection.build(attributes = {})

collection.buildメソッドは、関連付けられた型の新しいオブジェクトを1つ返します。このオブジェクトは、渡された属性でインスタンス化され、そのjoinテーブルを介してリンクが作成されます。ただし、関連付けられたオブジェクトはこの時点では保存されていないことにご注意ください。

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

collection.createメソッドは、関連付けられた型の新しいオブジェクトを1つ返します。このオブジェクトは、渡された属性を用いてインスタンス化され、joinテーブルを介してリンクが作成されます。そして、関連付けられたモデルで指定されているバリデーションがすべてパスすると、この関連付けられたオブジェクトは保存されます。

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

上のcollection.createと同じですが、レコードが無効な場合にActiveRecord::RecordInvalidがraiseされる点が異なります。

4.4.1.18 collection.reload

collection.reloadメソッドは、関連付けられたすべてのオブジェクトのリレーションを1つ返し、データベースを強制的に読み出します。関連付けられたオブジェクトがない場合は、空のリレーションを1つ返します。

@assemblies = @part.assemblies.reload
4.4.2 has_and_belongs_to_manyのオプション

Railsのデフォルトのhas_and_belongs_to_many関連付けは優秀なので、ほとんどの場合カスタマイズ不要ですが、関連付けの参照をカスタマイズしたい場合もあります。これは、作成するときにオプションを渡すことで簡単にカスタマイズできます。たとえば、以下のようなオプションを関連付けに追加できます。

class Parts < ApplicationRecord
  has_and_belongs_to_many :assemblies, -> { readonly },
                                       autosave: true
end

has_and_belongs_to_many関連付けでは以下のオプションがサポートされます。

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

Railsの慣例では、相手のモデルを指す外部キーを保持しているjoinテーブル上のカラム名については、そのモデル名にサフィックス _id を追加した名前が使われることを前提とします。:association_foreign_keyオプションを使うと外部キーの名前を直接指定できます。

:foreign_keyオプションおよび:association_foreign_keyオプションは、以下のような多対多の自己結合を行いたいときに便利です。

class User < ApplicationRecord
  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に設定すると、親オブジェクトが保存されるたびに、読み込まれているすべての関連付けられたメンバを保存し、destroyフラグが立っているメンバを破棄します。 :autosavefalseに設定することと、:autosaveオプションを未設定のままにしておくことは同じではありません:autosaveオプションを渡さない場合、関連付けられたオブジェクトのうち、新しいオブジェクトは保存されますが、更新されたオブジェクトは保存されません。

4.4.2.3 :class_name

関連付けの相手となるオブジェクト名を関連付け名から生成できない事情がある場合、:class_nameオプションを用いてモデル名を直接指定できます。たとえば、1つの部品(Part)が複数の組み立て(Assembly)で使われ、組み立てを含む実際のモデル名がGadgetである場合、次のように設定します。

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

Railsの規約では、そのモデルを指す外部キーを保持しているjoinテーブル上のカラム名については、そのモデル名にサフィックス _id を追加した名前が使われることを前提とします。:foreign_keyオプションを使うと外部キーの名前を直接指定できます。

class User < ApplicationRecord
  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テーブルのデフォルト名が気に入らない場合、: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 < ApplicationRecord
  has_and_belongs_to_many :assemblies, -> { where active: true }
end

スコープブロック内では標準のクエリメソッドをすべて利用できます。ここでは以下について説明します。

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

whereは、関連付けられるオブジェクトが満たすべき条件を指定します。

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

条件はハッシュで指定することもできます。

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

whereオプションでハッシュを用いた場合、この関連付けで作成されたレコードは自動的にこのハッシュを使うスコープに含まれるようになります。この例の場合、@parts.assemblies.createまたは@parts.assemblies.buildを実行すると、factoryカラムにSeattleを持つオブジェクトが作成されます。

4.4.3.2 extending

extendingメソッドは、関連付けプロキシを拡張する名前付きモジュールを指定します。関連付けの拡張については後述します

4.4.3.3 group

groupメソッドは、結果をグループ化する属性名を1つ指定します。内部的にはSQLのGROUP BY句が使われます。

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

includesメソッドを使うと、その関連付けが使われるときにeager loadingすべき第2関連付けを指定できます。

4.4.3.5 limit

limitメソッドは、関連付けを用いて取得できるオブジェクトの総数の上限を指定するのに使います。

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

offsetメソッドは、関連付けを用いてオブジェクトを取得する際の開始オフセットを指定します。たとえばoffset(11)と指定すると、最初の11レコードはスキップされ、12レコード以降が返されるようになります。

4.4.3.7 order

orderメソッドは、関連付けられたオブジェクトの並び順を指定します。内部的にはSQLのORDER BY句が使われます。

class Parts < ApplicationRecord
  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 distinct

distinctメソッドは、コレクション内の重複を削除します。

4.4.4 オブジェクトが保存されるタイミング

has_and_belongs_to_many関連付けにオブジェクトを割り当てると、joinテーブルを更新するためにそのオブジェクトは自動的に保存されます。1つの文で複数のオブジェクトを割り当てると、それらはすべて保存されます。

関連付けられているオブジェクト同士のどれかがバリデーションエラーで保存に失敗すると、割り当ての状態がfalseになり、割り当てはキャンセルされます。

親オブジェクト(has_and_belongs_to_many関連付けを宣言している側のオブジェクト)が保存されない場合(つまりnew_record?trueを返す場合)、子オブジェクトは追加時に保存されません。親オブジェクトが保存されると、関連付けられていたオブジェクトのうち保存されていなかったメンバはすべて保存されます。

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

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

通常のコールバックは、Active Recordオブジェクトのライフサイクルの中でフックされます。これにより、オブジェクトのさまざまな場所でコールバックを実行できます。たとえば、:before_saveコールバックを使って、オブジェクトが保存される直前に何かを実行できます。

関連付けのコールバックも、上のような通常のコールバックと似ていますが、(Active Recordオブジェクトではなく)コレクションのライフサイクルによってイベントがトリガされる点が異なります。以下の4つの関連付けコールバックを利用できます。

  • before_add
  • after_add
  • before_remove
  • after_remove

これらのオプションを関連付けの宣言に追加することで、関連付けコールバックを定義できます。以下に例を示します。

class Author < ApplicationRecord
  has_many :books, before_add: :check_credit_limit

  def check_credit_limit(book)
    # ...
  end
end

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

1つのイベントで複数のコールバックを使いたい場合には、配列で渡します。

class Author < ApplicationRecord
  has_many :books,
    before_add: [:check_credit_limit, :calculate_shipping_charges]

  def check_credit_limit(book)
    # ...
  end

  def calculate_shipping_charges(book)
    # ...
  end
end

before_addコールバックがthrow(:abort)した場合、オブジェクトはコレクションに追加されません。同様に、before_removethrow(:abort)した場合も、オブジェクトはコレクションから削除されません。

# 本が上限に達した場合は追加されない
def check_credit_limit(book)
  throw(:abort) if limit_reached?
end

これらのコールバックは、関連付けられたオブジェクトが関連付けコレクションを介して追加または削除された場合にのみ呼び出されます。

# `before_add`コールバックはトリガーされる
author.books << book
author.books = [book, book2]

# `before_add`コールバックはトリガーされない
book.update(author_id: 1)

4.6 関連付けの拡張

Railsは自動的に機能を関連付けのプロキシオブジェクトにビルドしますが、開発者はこれをカスタマイズできます。無名モジュール(anonymous module)を用いてこれらのオブジェクトを拡張(検索、作成などのメソッドを追加)できます。以下に例を示します。

class Author < ApplicationRecord
  has_many :books do
    def find_by_book_prefix(book_number)
      find_by(category_id: book_number[0..2])
    end
  end
end

拡張をさまざまな関連付けで共有したい場合は、名前付きの拡張モジュールを使うことも可能です。以下に例を示します。

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

class Author < ApplicationRecord
  has_many :books, -> { extending FindRecentExtension }
end

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

関連付けプロキシの内部を参照するには、proxy_associationアクセサにある以下の3つの属性を使います。

  • proxy_association.owner: 関連付けを所有するオブジェクトを返します。
  • proxy_association.reflection: 関連付けを記述するリフレクションオブジェクトを返します。
  • proxy_association.target: belongs_toまたはhas_one関連付けのオブジェクトを返すか、has_manyまたはhas_and_belongs_to_many関連付けオブジェクトのコレクションを返します。

5 単一テーブル継承 (STI)

異なるモデル間でフィールドや振る舞いを共有したい場合があります。 たとえば、Carモデル、Motorcycleモデル、Bicycleモデルがあり、colorpriceなどのフィールドや、いくつかの関連メソッドを共有したいが、モデルごとに振る舞いやコントローラーが異なっているとしましょう。

まず、各モデルのベースとなるVehicleモデルを生成します。

$ bin/rails generate model vehicle type:string color:string price:decimal{10.2}

"type"フィールドを追加している点にご注目ください。すべてのモデルはデータベース上のテーブルに保存されるため、Railsはこのカラムに該当するモデル名を保存します。この例では "Car"、"Motorcycle"または"Bicycle"になります。この例の単一テーブル継承(STI: Single Table Inheritance)では、テーブルに"type"フィールドがないとうまく動きません。

次に、Vehicleモデルを継承して3つのモデルをそれぞれ生成します。このとき、--parent=親モデルオプションを使って特定の親モデルを継承している点にご注目ください。このオプションを使うと、同様のマイグレーションファイルを生成せずにモデルを生成できます(該当するテーブルが既に存在しているため)。

たとえばCarモデルの場合は以下のようになります。

$ bin/rails generate model car --parent=Vehicle

生成されたモデルは次のようになります。

class Car < Vehicle
end

これによってVehicleモデルに追加されたすべての振る舞いがCarモデルにも追加されるようになります。関連付けやpublicメソッドなども同様に追加されます。

この状態で新しく作成したCarを保存すると、typeフィールドに"Car"が代入されたデータがvehiclesテーブルに追加されます。

Car.create(color: 'Red', price: 10000)

実際に生成されるSQLは次のようになります。

INSERT INTO "vehicles" ("type", "color", "price") VALUES ('Car', 'Red', 10000)

Carのレコードを取得するクエリを送信すると、vehiclesテーブル中のCarが検索されるようになります。

Car.all

実際のクエリは次のようになります。

SELECT "vehicles".* FROM "vehicles" WHERE "vehicles"."type" IN ('Car')

フィードバックについて

Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。

原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨

本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。

Railsガイド運営チーム (@RailsGuidesJP)

支援・協賛

Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。

  1. Star
  2. このエントリーをはてなブックマークに追加