edge
詳細はrubyonrails.orgで: もっとRuby on Rails

Active Recordの関連付け

このガイドでは、Active Recordの関連付け機能について説明します。

このガイドを読み終えると、以下のことがわかるようになります。

1 なぜ関連付けが必要なのか?

Railsでは、関連付けは2つのActive Recordモデル間の接続です。なぜモデル間に関連付けが必要なのでしょうか?それは、コード内で一般的な操作をよりシンプルかつ簡単にするためです。

例えば、著者のモデルと本のモデルを含むシンプルなRailsアプリケーションを考えてみましょう。各著者は多くの本を持つことができます。

関連付けがない場合、モデルの宣言は次のようになります。

class Author < ApplicationRecord
end

class Book < ApplicationRecord
end

さて、既存の著者に新しい本を追加したいとしましょう。次のようなことを行う必要があります。

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

また、著者を削除し、その著者のすべての本も削除する必要がある場合を考えてみましょう。

@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

この変更により、特定の著者の新しい本を作成することが簡単になります。

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

著者とそのすべての本を削除することもはるかに簡単になります。

author.destroy

さまざまな種類の関連付けについて詳しく学ぶには、このガイドの次のセクションを読んでください。それに続いて、関連付けを使用するためのヒントやトリック、そしてRailsでの関連付けのメソッドとオプションの完全なリファレンスがあります。

2 関連付けの種類

Railsは、特定のユースケースを考慮した6つの関連付けをサポートしています。

以下は、すべてのサポートされている種類のリストです。詳細な情報や使用方法、メソッドのパラメータなどについては、それぞれのAPIドキュメントへのリンクを参照してください。

関連付けはマクロスタイルの呼び出しを使用して実装されているため、モデルに機能を宣言的に追加することができます。例えば、あるモデルが別のモデルにbelongs_toすることを宣言することで、Railsに2つのモデルのインスタンス間の主キー-外部キー情報を維持するように指示し、モデルにいくつかのユーティリティメソッドが追加されます。

このガイドの残りの部分では、さまざまな形式の関連付けの宣言と使用方法について学びます。しかし、まずは、各関連付けタイプが適切な状況で使用される場面について簡単に紹介します。

2.1 belongs_to関連付け

belongs_to関連付けは、他のモデルとの接続を設定し、宣言するモデルの各インスタンスが他のモデルの1つのインスタンスに「所属する」という関係を構築します。例えば、アプリケーションに著者と本が含まれており、各本が正確に1人の著者に割り当てられる場合、次のように本のモデルを宣言します。

class Book < ApplicationRecord
  belongs_to :author
end

belongs_to関連付けダイアグラム

注意:belongs_to関連付けは、単数形の用語を使用する必要があります。上記の例でBookモデルのauthor関連付けで複数形の形を使用し、Book.create(authors: author)というインスタンスを作成しようとすると、「未初期化定数Book::Authorsがあります」というエラーが表示されます。これは、Railsが自動的に関連付け名からクラス名を推測するためです。関連付け名が誤って複数形になっている場合、推測されるクラス名も誤って複数形になります。

対応するマイグレーションは次のようになるでしょう。

class CreateBooks < ActiveRecord::Migration[7.1]
  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単体では、一方向の一対一の接続を生成します。したがって、上記の例では各本が著者を「知っている」一方、著者は自分の本については知りません。 双方向の関連付けを設定するには、この場合はAuthorモデルで他のモデルに対してhas_oneまたはhas_manyを組み合わせてbelongs_toを使用します。

optionalがtrueに設定されている場合、belongs_toは参照の一貫性を保証しません。したがって、使用ケースに応じて、参照カラムにデータベースレベルの外部キー制約を追加する必要があるかもしれません。 ruby 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.1]
  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

使用ケースによっては、アカウントテーブルのサプライヤーカラムに一意のインデックスと/または外部キー制約を作成する必要がある場合があります。この場合、カラムの定義は次のようになります。

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対多の関連付けを示します。通常、belongs_to関連付けの「反対側」にこの関連付けが見つかります。この関連付けは、モデルの各インスタンスが他のモデルの0個以上のインスタンスを持つことを示します。例えば、著者と書籍を含むアプリケーションでは、著者モデルは次のように宣言されます。

class Author < ApplicationRecord
  has_many :books
end

注意:has_many関連付けの場合、他のモデルの名前は複数形にする必要があります。

has_many関連付けのダイアグラム

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

class CreateAuthors < ActiveRecord::Migration[7.1]
  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関連付けは、他のモデルと多対多の関連付けを設定するためによく使用されます。この関連付けは、宣言モデルが3番目のモデルを介して他のモデルの0個以上のインスタンスと一致することを示します。例えば、患者が医師を診るために予約をする医療機関を考えてみましょう。関連する宣言は次のようになります。

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.1]
  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

結合モデルのコレクションは、has_many関連付けメソッドを介して管理できます。 例えば、次のように割り当てる場合:

physician.patients = patients

新しい結合モデルが自動的に新しく関連付けられたオブジェクトのために作成されます。 以前存在していたものの一部が欠落している場合、それらの結合行は自動的に削除されます。

警告:結合モデルの自動削除は直接的なものであり、削除コールバックはトリガされません。

has_many :through関連付けは、ネストされたhas_many関連付けを介した「ショートカット」の設定にも便利です。例えば、ドキュメントにはセクションが多くあり、セクションには段落が多くあります。ドキュメントのすべての段落の単純なコレクションを取得したい場合があります。次のように設定することができます。

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の関連付けを設定します。この関連付けは、宣言モデルが3番目のモデルを介して別のモデルの1つのインスタンスと一致することを示します。 例えば、各サプライヤーには1つのアカウントがあり、各アカウントには1つのアカウント履歴が関連付けられている場合、サプライヤーモデルは次のようになります: ```ruby 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 Association Diagram

対応するマイグレーションは次のようになるでしょう:

class CreateAccountHistories < ActiveRecord::Migration[7.1]
  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 関連は、他のモデルと直接的な多対多の関係を作成します。この関連は、宣言するモデルの各インスタンスが他のモデルの0個以上のインスタンスを参照することを示します。例えば、アセンブリとパーツを含むアプリケーションの場合、各アセンブリは多くのパーツを持ち、各パーツは多くのアセンブリに現れることがあります。以下のようにモデルを宣言することができます:

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.1]
  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の関係を設定する場合、1つに belongs_to を追加し、もう1つに has_one を追加する必要があります。どちらがどちらかをどのように知ることができますか?

外部キーをどこに配置するか(belongs_to 関連を宣言するクラスのテーブルに配置する)という違いがありますが、データの実際の意味にも注意を払う必要があります。has_one 関連は、何かの1つがあなたのものであることを示します。つまり、何かがあなたを指し示すということです。例えば、サプライヤーがアカウントを所有していると言う方が、アカウントがサプライヤーを所有していると言うよりも意味があります。これは、正しい関係が次のようになることを示唆しています:

class Supplier < ApplicationRecord
  has_one :account
end

class Account < ApplicationRecord
  belongs_to :supplier
end

対応するマイグレーションは次のようになるでしょう:

class CreateSuppliers < ActiveRecord::Migration[7.1]
  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 を使用することで、外部キーの命名が明確で明示的になります。Railsの現在のバージョンでは、t.references :supplier を使用することで、この実装の詳細を抽象化することができます。

2.8 has_many :throughhas_and_belongs_to_many の選択

Railsでは、モデル間の多対多の関係を宣言するために2つの異なる方法が提供されています。1つ目の方法は 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

2つ目の方法は has_many :through を使用する方法で、関連を結合モデルを介して間接的に作成します:

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

簡単なルールは、関連モデルを独立したエンティティとして扱う必要がある場合は has_many :through 関連を設定することです。関連モデルで何か特別な操作を行う必要がない場合は、has_and_belongs_to_many 関連を設定する方が簡単です(ただし、データベースに結合テーブルを作成する必要があることを忘れないでください)。

has_many :through は、結合モデルにバリデーション、コールバック、または追加の属性が必要な場合に使用します。

2.9 ポリモーフィック関連

関連に関して少し高度なトピックとして、ポリモーフィック関連 があります。ポリモーフィック関連では、モデルは複数の他のモデルに所属することができます。例えば、従業員モデルまたは製品モデルに所属する写真モデルがあるかもしれません。次のように宣言することができます:

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 モデルのインスタンスからは、写真のコレクションを取得することができます:@employee.pictures。 同様に、@product.picturesを取得することもできます。

Pictureモデルのインスタンスがある場合、@picture.imageableを使用して親にアクセスできます。これを動作させるには、ポリモーフィックなインターフェースを宣言するモデルに外部キーカラムとタイプカラムの両方を宣言する必要があります。

class CreatePictures < ActiveRecord::Migration[7.1]
  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[7.1]
  def change
    create_table :pictures do |t|
      t.string :name
      t.references :imageable, polymorphic: true
      t.timestamps
    end
  end
end

ポリモーフィック関連図

2.10 セルフジョイン

データモデルを設計する際に、自身に関連を持つモデルがある場合があります。たとえば、すべての従業員を単一のデータベースモデルに格納したいが、マネージャーと部下のような関係を追跡できるようにしたい場合があります。このような状況は、セルフジョイン関連を使用してモデル化できます。

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を取得できます。

マイグレーション/スキーマでは、モデル自体に参照カラムを追加します。

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

注意:foreign_keyに渡されるto_tableオプションなど、他のオプションの詳細については、SchemaStatements#add_referenceを参照してください。

3 Tips、Tricks、および警告

RailsアプリケーションでActive Record関連を効率的に使用するために知っておくべきいくつかのことがあります。

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

3.1 キャッシュの制御

すべての関連メソッドはキャッシュを中心に構築されており、最新のクエリの結果をさらなる操作で使用できるようにします。キャッシュはメソッド間で共有されます。たとえば:

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

# キャッシュされた本のコピーを使用
author.books.size

# キャッシュされた本のコピーを使用
author.books.empty?

ただし、アプリケーションの他の部分でデータが変更された可能性があるため、キャッシュを再読み込みする必要がある場合は、関連に対してreloadを呼び出すだけです。

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

# キャッシュされた本のコピーを使用
author.books.size

# キャッシュされた本のコピーを破棄し、データベースに戻る
author.books.reload.empty?

3.2 名前の衝突を回避する

関連には任意の名前を自由に使用することはできません。関連はその名前のメソッドをモデルに追加するため、ActiveRecord::Baseのインスタンスメソッドと同じ名前を関連に付けることは良いアイデアではありません。関連メソッドはベースメソッドを上書きし、問題を引き起こします。たとえば、attributesconnectionは関連には適切な名前ではありません。

3.3 スキーマの更新

関連は非常に便利ですが、魔法ではありません。関連に合わせてデータベーススキーマを維持する責任があります。実際には、作成する関連の種類によって2つのことを行う必要があります。belongs_to関連では外部キーを作成する必要があり、has_and_belongs_to_many関連では適切な結合テーブルを作成する必要があります。

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

belongs_to関連を宣言するときは、必要に応じて外部キーを作成する必要があります。たとえば、次のモデルを考えてみてください。

class Book < ApplicationRecord
  belongs_to :author
end

この宣言は、booksテーブルに対応する外部キーカラムをバックアップする必要があります。新しいテーブルの場合、マイグレーションは次のようになるかもしれません。

class CreateBooks < ActiveRecord::Migration[7.1]
  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.1]
  def change
    add_reference :books, :author
  end
end

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

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

has_and_belongs_to_many関連を作成する場合は、明示的に結合テーブルを作成する必要があります。:join_tableオプションを使用して結合テーブルの名前を明示的に指定しない限り、Active Recordはクラス名の辞書順を使用して名前を作成します。したがって、authorとbookモデルの間の結合は、クラス名の辞書順で「authors_books」というデフォルトの結合テーブル名が付けられます。

警告:モデル名の優先順位は、String<=>演算子を使用して計算されます。これは、文字列の長さが異なり、最短の長さまで比較した結果が等しい場合、長い文字列が短い文字列よりも辞書的な優先順位が高いと見なされることを意味します。例えば、テーブルの名前が "paper_boxes" と "papers" の場合、"paper_boxes" の長さのために "papers_paper_boxes" という結合テーブル名が生成されると予想されますが、実際には "paper_boxes_papers" という結合テーブル名が生成されます(アンダースコア '_' は一般的なエンコーディングでは 's' よりも辞書的に 小さい からです)。

どのような名前であっても、適切なマイグレーションを使用して結合テーブルを手動で生成する必要があります。例えば、次の関連付けを考えてみましょう:

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.1]
  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 を渡すのは、そのテーブルがモデルを表していないためです。これは関連付けが正しく機能するために必要です。has_and_belongs_to_many の関連付けでモデルのIDが破損したり、IDの競合に関する例外が発生したりする場合は、この部分を忘れている可能性があります。

簡単のために、create_join_table メソッドを使用することもできます:

class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[7.1]
  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 クラスが同じスコープ内で定義されているからです。しかし、次の例は動作しません。なぜなら、SupplierAccount が異なるスコープで定義されているからです。

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つの方向で動作することがあります。そのため、2つの異なるモデルで宣言する必要があります。

class Author < ApplicationRecord
  has_many :books
end

class Book < ApplicationRecord
  belongs_to :author
end

Active Record は、関連付け名に基づいてこれら2つのモデルが双方向の関連付けを共有していることを自動的に識別しようとします。この情報により、Active Record は次のことができます。

  • すでにロードされたデータに対して不要なクエリを防ぐ:

    irb> author = Author.first
    irb> author.books.all? do |book|
    irb>   book.author.equal?(author) # ここで追加のクエリが実行されません
    irb> end
    => true
    
  • 不整合なデータを防ぐ(Author オブジェクトが1つしかロードされていないため):

    irb> author = Author.first
    irb> book = author.books.first
    irb> author.name == book.author.name
    => true
    irb> author.name = "Changed Name"
    irb> author.name == book.author.name
    => true
    
  • より多くのケースで関連付けを自動的に保存する:

    irb> author = Author.new
    irb> book = author.books.new
    irb> book.save!
    irb> book.persisted?
    => true
    irb> author.persisted?
    => true
    
  • 存在不在のバリデーションをより多くのケースで行う:

    irb> book = Book.new
    irb> book.valid?
    => false
    irb> book.errors.full_messages
    => ["Author must exist"]
    irb> author = Author.new
    irb> book = author.books.new
    irb> book.valid?
    => true
    

Active Record は、標準的な名前を持つほとんどの関連付けに対して自動的な識別をサポートしています。ただし、:through オプションまたは :foreign_key オプションを含む双方向の関連付けは、自動的に識別されません。

また、逆の関連付けにカスタムスコープがある場合、および 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

:foreign_key オプションのため、Active Record はもはや自動的に双方向の関連付けを認識しません。これにより、アプリケーションが次のような問題を引き起こす可能性があります: * 同じデータに対して不要なクエリを実行する(この例ではN+1のクエリが発生する):

```irb
irb> author = Author.first
irb> author.books.any? do |book|
irb>   book.author.equal?(author) # これは各本ごとに作者のクエリを実行します
irb> end
=> false
```
  • 不整合なデータを持つモデルの複数のコピーを参照する:

    irb> author = Author.first
    irb> book = author.books.first
    irb> author.name == book.author.name
    => true
    irb> author.name = "変更された名前"
    irb> author.name == book.author.name
    => false
    
  • 関連を自動保存できない:

    irb> author = Author.new
    irb> book = author.books.new
    irb> book.save!
    irb> book.persisted?
    => true
    irb> author.persisted?
    => false
    
  • 存在または不在を検証できない:

    irb> author = Author.new
    irb> book = author.books.new
    irb> book.valid?
    => false
    irb> book.errors.full_messages
    => ["Author must exist"]
    

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は双方向の関連を認識し、最初の例と同様の動作をします。

4 詳細な関連のリファレンス

以下のセクションでは、各種の関連の詳細、および関連を宣言する際に使用できるオプションについて説明します。

4.1 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
  • reset_association
  • association_changed?
  • association_previously_changed?

これらのメソッドのすべてで、associationは、belongs_toへの最初の引数として渡されたシンボルに置き換えられます。たとえば、次のような宣言がある場合:

class Book < ApplicationRecord
  belongs_to :author
end

Bookモデルの各インスタンスには、次のメソッドがあります:

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

注意:新しいhas_oneまたはbelongs_to関連を初期化する場合は、has_manyまたはhas_and_belongs_to_many関連に使用されるassociation.buildメソッドではなく、build_接頭辞を使用して関連を構築する必要があります。1つ作成するには、create_接頭辞を使用します。

4.1.1.1 association

associationメソッドは、関連するオブジェクト(あれば)を返します。関連するオブジェクトが見つからない場合は、nilを返します。

@book.author = @author

このオブジェクトのために関連オブジェクトがすでにデータベースから取得されている場合、キャッシュされたバージョンが返されます。この動作をオーバーライドして(およびデータベースの読み取りを強制するために)、親オブジェクトで#reload_associationを呼び出します。

@book.reload_author

関連オブジェクトのキャッシュされたバージョンをアンロードし、次にアクセスされる場合はデータベースからクエリされるようにするには、親オブジェクトで#reset_associationを呼び出します。

@book.reset_author
4.1.1.2 association=(associate)

association=メソッドは、このオブジェクトに関連オブジェクトを割り当てます。内部的には、関連オブジェクトから主キーを抽出し、このオブジェクトの外部キーを同じ値に設定することを意味します。

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

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

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

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

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

create_associationと同様ですが、レコードが無効な場合にActiveRecord::RecordInvalidを発生させます。

4.1.1.6 association_changed?

association_changed?メソッドは、新しい関連オブジェクトが割り当てられ、次の保存で外部キーが更新される場合にtrueを返します。 ```ruby @book.author # => # @book.author_changed? # => false

@book.author = Author.second # => # @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関連付けの動作をカスタマイズしたい場合があります。このようなカスタマイズは、関連付けを作成するときにオプションとスコープブロックを渡すことで簡単に行うことができます。たとえば、次のような2つのオプションを使用する関連付けです。

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に設定すると、Railsはロードされた関連メンバーを保存し、破棄されるメンバーを保存するために、親オブジェクトを保存するたびに呼び出されます。:autosaveオプションをfalseに設定することは、:autosaveオプションを設定しないこととは異なります。autosaveオプションが存在しない場合、新しい関連オブジェクトは保存されますが、更新された関連オブジェクトは保存されません。

4.1.2.2 :class_name

他のモデルの名前が関連付けの名前から派生できない場合は、class_nameオプションを使用してモデル名を指定できます。たとえば、本が著者に所属しているが、実際の著者を含むモデルの名前が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(*)クエリを実行するためにデータベースに問い合わせる必要があります。この呼び出しを避けるために、所属するモデルにカウンターキャッシュを追加できます。

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

class Author < ApplicationRecord
  has_many :books
end

この宣言では、Railsはキャッシュ値を最新の状態に保ち、sizeメソッドの応答としてその値を返します。

counter_cacheオプションは、belongs_to宣言を含むモデルに指定されますが、実際のカラムは関連する(has_many)モデルに追加する必要があります。上記の場合、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を介して所有者モデルの読み取り専用属性リストに追加されます。

所有者モデルの主キーの値を変更し、カウントされるモデルの外部キーも更新しない場合、カウンターキャッシュに古いデータが残る可能性があります。つまり、孤立したモデルもカウンターにカウントされます。古いカウンターキャッシュを修正するには、reset_countersを使用してください。

4.1.2.4 :dependent

:dependentオプションを次のように設定すると:

  • :destroy:オブジェクトが破棄されると、関連するオブジェクトにdestroyが呼び出されます。
  • :delete:オブジェクトが破棄されると、関連するすべてのオブジェクトがそのdestroyメソッドを呼び出さずに直接データベースから削除されます。
  • :destroy_async:オブジェクトが破棄されると、ActiveRecord::DestroyAssociationAsyncJobジョブがキューに入れられ、関連するオブジェクトにdestroyが呼び出されます。これを動作させるには、Active Jobを設定する必要があります。データベースで外部キー制約によってバックアップされている場合は、このオプションを使用しないでください。外部キー制約のアクションは、所有者を削除するトランザクション内で発生します。 警告:belongs_toのオプションとして、他のクラスとhas_manyの関連付けがある場合には指定しないでください。これを行うと、データベースに孤立したレコードが残る可能性があります。
4.1.2.5 :foreign_key

Railsでは、このモデルで外部キーを保持するために使用されるカラムは、関連付けの名前に接尾辞 _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を主キーとして持つ場合、todosテーブルにはguidカラムに外部キーuser_idを持たせたい場合、次のようにprimary_keyを使用してこれを実現できます。

class User < ApplicationRecord
  self.primary_key = 'guid' # 主キーはidではなくguid
end

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

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

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に設定すると、関連するオブジェクトのupdated_atまたはupdated_onタイムスタンプが、このオブジェクトが保存または削除されるたびに現在の時刻に設定されます。

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

class Author < ApplicationRecord
  has_many :books
end

この場合、本を保存または削除すると、関連する著者のタイムスタンプが更新されます。特定のタイムスタンプ属性を更新することもできます。

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メソッドを使用して、この関連付けが使用されるときに一緒にプリロードする必要がある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.book.author)、チャプターから本への関連付けに著者を含めることで、コードをより効率的にすることができます。

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の場合、必要な時に自動的に著者がプリロードされます。

4.1.3.3 readonly

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

4.1.3.4 select

selectメソッドを使用すると、関連するオブジェクトに関するデータを取得するために使用されるSQLのSELECT句をオーバーライドすることができます。デフォルトでは、Railsはすべての列を取得します。

belongs_to関連付けでselectメソッドを使用する場合は、正しい結果を保証するために: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
  • reset_association

これらのメソッドのすべてで、associationhas_oneに渡された最初の引数として渡されたシンボルで置き換えられます。たとえば、次の宣言がある場合:

class Supplier < ApplicationRecord
  has_one :account
end

Supplierモデルの各インスタンスには、次のメソッドがあります:

  • account
  • account=
  • build_account
  • create_account
  • create_account!
  • reload_account
  • reset_account

新しいhas_oneまたはbelongs_to関連付けを初期化する場合は、association.buildメソッドではなく、関連付けをビルドするためにbuild_接頭辞を使用する必要があります。作成するには、create_接頭辞を使用します。

4.2.1.1 association

associationメソッドは関連するオブジェクトを返します。関連するオブジェクトが見つからない場合はnilを返します。

@account = @supplier.account

関連するオブジェクトがこのオブジェクトのためにすでにデータベースから取得されている場合、キャッシュされたバージョンが返されます。この動作をオーバーライドして(データベースの読み取りを強制するために)、親オブジェクトで#reload_associationを呼び出します。

@account = @supplier.reload_account

関連するオブジェクトのキャッシュされたバージョンをアンロードし、次にアクセスする場合はデータベースからクエリするために、親オブジェクトで#reset_associationを呼び出します。

@supplier.reset_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を発生させます。

4.2.2 has_oneのオプション

Railsはほとんどの状況でうまく機能するように、賢明なデフォルト値を使用していますが、has_one関連付けの動作をカスタマイズしたい場合があります。このようなカスタマイズは、関連付けを作成する際にオプションを渡すことで簡単に行うことができます。たとえば、次のような2つのオプションを使用する関連付けがあります:

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に設定すると、Railsはロードされた関連メンバーを保存し、削除されるメンバーを保存するようにします。親オブジェクトを保存するたびに、:autosaveオプションがfalseに設定されていない限り、新しい関連オブジェクトは保存されますが、更新された関連オブジェクトは保存されません。

4.2.2.3 :class_name

もし他のモデルの名前が関連名から導き出されない場合、class_nameオプションを使用してモデル名を指定することができます。例えば、サプライヤーがアカウントを持っているが、実際のアカウントを含むモデルの名前がBillingである場合、以下のように設定します。

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

所有者が破棄されたときに関連するオブジェクトに何が起こるかを制御します。

  • :destroyは関連するオブジェクトも破棄します。
  • :deleteは関連するオブジェクトをデータベースから直接削除します(コールバックは実行されません)。
  • :destroy_async:オブジェクトが破棄されると、ActiveRecord::DestroyAssociationAsyncJobジョブがエンキューされ、関連するオブジェクトに対して破棄が呼び出されます。これを動作させるにはActive Jobを設定する必要があります。データベースに外部キー制約がある場合は、このオプションを使用しないでください。外部キー制約のアクションは、所有者を削除するトランザクション内で発生します。
  • :nullifyは外部キーをNULLに設定します。ポリモーフィックな関連では、ポリモーフィックなタイプのカラムもNULLに設定されます。コールバックは実行されません。
  • :restrict_with_exceptionは関連するレコードが存在する場合にActiveRecord::DeleteRestrictionError例外を発生させます。
  • :restrict_with_errorは関連オブジェクトが存在する場合にエラーを所有者に追加します。

NOT NULLのデータベース制約を持つ関連に対してnullifyオプションを設定または残さないようにする必要があります。このような関連に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関連については、このガイドの前の部分で詳しく説明されています。

4.2.2.11 :touch

:touchオプションをtrueに設定すると、このオブジェクトが保存または破棄されるたびに関連オブジェクトのupdated_atまたはupdated_onタイムスタンプが現在の時刻に設定されます。

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

class Account < ApplicationRecord
  belongs_to :supplier
end

この場合、サプライヤーの保存または破棄により、関連するアカウントのタイムスタンプが更新されます。特定のタイムスタンプ属性を更新することもできます。

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

スコープブロック内では、標準のクエリメソッドのいずれかを使用できます。以下にそれぞれについて説明します。

4.2.3.1 where

whereメソッドを使用すると、関連するオブジェクトが満たす必要のある条件を指定できます。

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

includesメソッドを使用して、この関連が使用されるときに一緒に読み込むべき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.account.representative)、サプライヤからアカウントへの関連に代表者を含めることで、コードをより効率的にすることができます。

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関連付けは、他のモデルとの一対多の関係を作成します。データベースの用語では、この関連付けは、他のクラスがこのクラスのインスタンスを参照する外部キーを持つことを意味します。

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

has_many関連付けを宣言すると、宣言するクラスには自動的に関連する17のメソッドが追加されます。

これらのメソッドのすべてで、collectionhas_manyに渡された最初の引数のシンボルに置き換えられ、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メソッドは、関連するすべてのオブジェクトの関連を返します。関連するオブジェクトがない場合は、空の関連を返します。

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

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

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

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

@author.books.delete(@book1)

警告: dependent: :destroyに関連付けられている場合、オブジェクトは破棄され、dependent: :delete_allに関連付けられている場合は削除されます。

4.3.1.4 collection.destroy(object, ...)

collection.destroyメソッドは、1つ以上のオブジェクトをコレクションから削除し、各オブジェクトに対してdestroyを実行します。

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=メソッドは、指定されたプライマリキーの値で識別されるオブジェクトのみを含むコレクションを作成し、必要に応じて追加および削除を行います。変更内容はデータベースに永続化されます。

4.3.1.8 collection.clear

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

@author.books.clear

警告: dependent: :destroyまたはdependent: :destroy_asyncに関連付けられているオブジェクトは削除されます。dependent: :delete_allと同様です。

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メソッドは、指定された条件に基づいてコレクション内のオブジェクトを検索しますが、オブジェクトは遅延ロードされるため、オブジェクトにアクセスされるとデータベースがクエリされます。

@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を発生させます。

4.3.1.17 collection.reload

collection.reloadメソッドは、関連するすべてのオブジェクトのリレーションを返し、データベースの読み取りを強制します。関連するオブジェクトが存在しない場合、空のリレーションが返されます。

@books = author.books.reload

4.3.2 has_manyのオプション

Railsは、ほとんどの状況でうまく機能するように適切なデフォルトを使用しますが、has_many関連の動作をカスタマイズしたい場合があります。このようなカスタマイズは、関連を作成する際にオプションを渡すことで簡単に行うことができます。たとえば、次の関連は2つのオプションを使用しています。

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に設定すると、Railsはロードされた関連メンバーを保存し、削除がマークされたメンバーを破棄します。親オブジェクトを保存するたびに、:autosaveオプションがfalseに設定されていない限り、新しい関連オブジェクトは保存されますが、更新された関連オブジェクトは保存されません。

4.3.2.3 :class_name

他のモデルの名前が関連名から派生できない場合、class_nameオプションを使用してモデル名を指定できます。たとえば、著者が多数の本を持っているが、実際の本を含むモデルの名前がTransactionである場合、次のように設定します。

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

このオプションは、カスタム名の :counter_cache を設定するために使用されます。belongs_to association:counter_cache の名前をカスタマイズした場合にのみ、このオプションが必要です。

4.3.2.5 :dependent

所有者が破棄されたときに関連するオブジェクトに何が起こるかを制御します。

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

:destroy:delete_all オプションは、コレクションから削除されたときに関連するオブジェクトを破棄するように collection.deletecollection= メソッドの意味も変えます。

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 テーブルは guid カラムの値を外部キーとして保持し、id の値ではないことです。次のように実現できます。

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_many :through 関連は、このガイドの前述のセクションで説明されているように、多対多の関係を実装する方法を提供します。

4.3.2.12 :validate

:validate オプションを false に設定すると、このオブジェクトを保存するときに新しい関連オブジェクトは検証されません。デフォルトでは、新しい関連オブジェクトはこのオブジェクトが保存されるときに検証されます。

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である本が作成されます。

4.3.3.2 extending

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

4.3.3.3 group

groupメソッドは、検索SQLのGROUP BY句を使用して結果セットを属性名でグループ化します。

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

includesメソッドを使用して、この関連付けが使用されるときに一緒にプリロードする必要がある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.books.chapters)、著者から本への関連付けにチャプターを含めることで、コードをより効率的にすることができます:

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レコードをスキップします。

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つの読み取りがあり、person.articlesは同じ記事を指しているにもかかわらず、両方を表示します。

では、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つの読み取りがあります。ただし、person.articlesは一意のレコードのみを表示するため、1つの記事のみが表示されます。

挿入時に永続化された関連付け内のすべてのレコードが一意であることを確認するために(関連付けを検査すると重複したレコードが見つからないことを確認できるようにするため)、テーブル自体に一意のインデックスを追加する必要があります。たとえば、readingsという名前のテーブルがあり、記事を人に1回しか追加できないようにする場合、次のようにマイグレーションに追加できます:

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

この一意のインデックスを持っている場合、同じ記事を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?のようなユニーク性をチェックする方法は競合状態になりますので、関連付けでの一意性を強制するためにinclude?を使用しないでください。例えば、上記の記事の例を使用して、次のコードは競合状態になる可能性があります。複数のユーザーが同時にこれを試みることができるためです。

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つのクラスを関連付けます。

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

has_and_belongs_to_many関連付けを宣言すると、関連付けを持つクラスには自動的に関連するいくつかのメソッドが追加されます。

これらのメソッドのすべてで、collectionhas_and_belongs_to_manyに渡された最初の引数として渡されたシンボルに置き換えられ、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関連付けの結合テーブルに、2つの外部キー以外の追加のカラムがある場合、これらのカラムはその関連付けを介して取得されたレコードの属性として追加されます。追加の属性を持つレコードは常に読み取り専用です。なぜなら、Railsはこれらの属性の変更を保存できないからです。

警告:has_and_belongs_to_many関連付けの結合テーブルで追加の属性を使用することは非推奨です。多対多の関係で2つのモデルを結合するテーブルでこのような複雑な動作が必要な場合は、has_and_belongs_to_manyの代わりにhas_many :through関連付けを使用する必要があります。

4.4.1.2 collection

collectionメソッドは、関連するすべてのオブジェクトのRelationを返します。関連するオブジェクトがない場合、空のRelationが返されます。

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

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

@part.assemblies << @assembly1

注意:このメソッドはcollection.concatおよびcollection.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? %>
  この部品はどのアセンブリにも使用されていません
<% 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メソッドは、指定された条件に基づいてコレクション内のオブジェクトを検索しますが、オブジェクトは遅延ロードされます。つまり、オブジェクトにアクセスされるときにのみデータベースがクエリされます。

@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メソッドは、関連する型の新しいオブジェクトを返します。このオブジェクトは、渡された属性からインスタンス化され、結合テーブルを介したリンクが作成されますが、関連するオブジェクトはまだ保存されません。

@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.1.18 collection.reload

collection.reloadメソッドは、関連するすべてのオブジェクトのリレーションを返し、データベースの読み込みを強制します。関連するオブジェクトが存在しない場合は、空のリレーションを返します。

@assemblies = @part.assemblies.reload

4.4.2 has_and_belongs_to_manyのオプション

Railsは、ほとんどの場合にうまく機能するように適切なデフォルトを使用していますが、has_and_belongs_to_many関連付けの動作をカスタマイズしたい場合があります。このようなカスタマイズは、関連付けを作成する際にオプションを渡すことで簡単に行うことができます。たとえば、次の関連付けでは2つのオプションを使用しています。

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は、他のモデルを指す外部キーを保持するために使用される結合テーブルの列が、そのモデルの名前に接尾辞 _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に設定すると、Railsはロードされた関連メンバーを保存し、削除されるメンバーを破棄します。親オブジェクトを保存するときに、:autosaveオプションをfalseに設定することは、:autosaveオプションを設定しないこととは異なります。:autosaveオプションが存在しない場合、新しい関連オブジェクトは保存されますが、更新された関連オブジェクトは保存されません。

4.4.2.3 :class_name

関連するモデルの名前が関連名から派生できない場合、:class_nameオプションを使用してモデル名を指定できます。たとえば、部品には多くのアセンブリがあるが、アセンブリを含むモデルの実際の名前がGadgetである場合、次のように設定します。

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

Railsは、このモデルを指す外部キーを保持するために使用される結合テーブルの列が、このモデルの名前に接尾辞 _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_tableオプションを使用してデフォルトをオーバーライドできます。

4.4.2.6 :validate

もし :validate オプションを false に設定すると、このオブジェクトを保存する際に新しい関連オブジェクトはバリデーションされません。デフォルトでは、新しい関連オブジェクトはこのオブジェクトを保存する際にバリデーションされます。

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 メソッドは、検索 SQL 内の GROUP BY 句を使用して結果セットを属性名でグループ化します。

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

includes メソッドを使用して、この関連が使用される際に一緒に読み込まれるべき二次関連を指定することができます。

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レコードをスキップします。

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 関連にオブジェクトを割り当てると、そのオブジェクトは自動的に保存されます(結合テーブルを更新するため)。1つの文で複数のオブジェクトを割り当てる場合、それらはすべて保存されます。

これらの保存のいずれかがバリデーションエラーにより失敗した場合、割り当てステートメントは false を返し、割り当て自体はキャンセルされます。

親オブジェクト(has_and_belongs_to_many 関連を宣言するオブジェクト)が未保存の場合(つまり、new_record?true を返す場合)、追加されたときに子オブジェクトは保存されません。親が保存されると、関連の未保存のメンバーは自動的に保存されます。

オブジェクトを has_and_belongs_to_many 関連に保存せずに割り当てる場合は、collection.build メソッドを使用します。

4.5 関連コールバック

通常のコールバックは、Active Record オブジェクトのライフサイクルにフックして、さまざまなポイントでそれらのオブジェクトと一緒に作業することができます。例えば、オブジェクトが保存される直前に何かを実行するために :before_save コールバックを使用することができます。

関連コールバックは通常のコールバックと似ていますが、コレクションのライフサイクルのイベントによってトリガーされます。利用可能な関連コールバックは次の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 コールバックが :abort をスローすると、オブジェクトはコレクションに追加されません。同様に、before_remove コールバックが :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が関連付けプロキシオブジェクトに自動的に組み込む機能に制限されることはありません。匿名モジュールを使用してこれらのオブジェクトを拡張し、新しいファインダー、クリエーター、またはその他のメソッドを追加することもできます。例えば:

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 の関連オブジェクトのコレクションを返します。

4.7 関連付け所有者を使用した関連付けスコープ

関連付けの所有者は、関連付けスコープにさらなる制御を必要とする場合に、スコープブロックに単一の引数として渡すことができます。ただし、注意点として、関連付けのプリロードは不可能になります。

class Supplier < ApplicationRecord
  has_one :account, ->(supplier) { where active: supplier.active? }
end

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

時には、異なるモデル間でフィールドと動作を共有したい場合があります。例えば、Car、Motorcycle、Bicycleモデルがあるとします。これらすべてに対して colorprice フィールドといくつかのメソッドを共有したいが、それぞれに固有の動作と別々のコントローラも必要です。

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

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

"タイプ" フィールドを追加していることに注意してください。すべてのモデルが単一のデータベーステーブルに保存されるため、Railsはこのカラムに保存されるモデルの名前を保存します。この例では、これは "Car"、"Motorcycle"、または "Bicycle" になります。STIは、テーブルに "type" フィールドがないと機能しません。

次に、Vehicleから継承するCarモデルを生成します。これには、--parent=PARENT オプションを使用できます。これにより、指定された親から継承するモデルが生成され、同等のマイグレーションは生成されません(テーブルが既に存在するため)。

例えば、Carモデルを生成するには:

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

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

class Car < Vehicle
end

これは、Vehicleに追加されたすべての動作がCarでも利用可能であることを意味します。関連付け、パブリックメソッドなど。

Carを作成すると、vehicles テーブルに "Car" が type フィールドとして保存されます:

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

次のSQLが生成されます:

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

Carレコードのクエリは、車である車両のみを検索します:

Car.all

次のようなクエリが実行されます:

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

6 委譲されたタイプ

単一テーブル継承(STI)は、サブクラスとその属性の間にほとんどの違いがない場合に最も効果的ですが、1つのテーブルを作成するためにすべてのサブクラスの属性を含みます。

このアプローチの欠点は、テーブルが膨張することです。他の何も使用していないサブクラスに固有の属性も含まれるためです。

次の例では、同じ "Entry" クラスから継承する2つのActive Recordモデルがあります。このクラスには subject 属性が含まれています。 ```ruby

スキーマ: entries[ id, type, subject, created_at, updated_at]

class Entry < ApplicationRecord end

class Comment < Entry end

class Message < Entry end ```

delegated_typeを使用することで、この問題を解決できます。

デリゲートされたタイプを使用するためには、データを特定の方法でモデル化する必要があります。要件は以下の通りです。

  • スーパークラスが、そのテーブルにおけるすべてのサブクラス間で共有される属性を格納する。
  • 各サブクラスは、スーパークラスを継承し、それに固有の追加属性のために別のテーブルを持つ必要がある。

これにより、意図しない形ですべてのサブクラス間で共有される属性を単一のテーブルに定義する必要がなくなります。

上記の例にこれを適用するために、モデルを再生成する必要があります。 まず、スーパークラスとして機能する基本的なEntryモデルを生成しましょう。

$ bin/rails generate model entry entryable_type:string entryable_id:integer

次に、デリゲーションのための新しいMessageCommentモデルを生成します。

$ bin/rails generate model message subject:string body:string
$ bin/rails generate model comment content:string

ジェネレータを実行した後、次のようなモデルが生成されます。

# スキーマ: entries[ id, entryable_type, entryable_id, created_at, updated_at ]
class Entry < ApplicationRecord
end

# スキーマ: messages[ id, subject, body, created_at, updated_at ]
class Message < ApplicationRecord
end

# スキーマ: comments[ id, content, created_at, updated_at ]
class Comment < ApplicationRecord
end

6.1 delegated_typeの宣言

まず、スーパークラスのEntrydelegated_typeを宣言します。

class Entry < ApplicationRecord
  delegated_type :entryable, types: %w[ Message Comment ], dependent: :destroy
end

entryableパラメータは、デリゲーションに使用するフィールドを指定し、MessageCommentをデリゲートクラスとして含めます。

Entryクラスにはentryable_typeentryable_idのフィールドがあります。これは、delegated_typeの定義でentryableの名前に_type_idの接尾辞が追加されたフィールドです。 entryable_typeはデリゲート先のサブクラスの名前を格納し、entryable_idはデリゲート先のサブクラスのレコードIDを格納します。

次に、モジュールを定義してデリゲートされたタイプを実装する必要があります。これは、has_one関連付けにas: :entryableパラメータを宣言することで行います。

module Entryable
  extend ActiveSupport::Concern

  included do
    has_one :entry, as: :entryable, touch: true
  end
end

そして、作成したモジュールをサブクラスに含めます。

class Message < ApplicationRecord
  include Entryable
end

class Comment < ApplicationRecord
  include Entryable
end

この定義が完了すると、Entryデリゲータは次のメソッドを提供します。

メソッド 戻り値
Entry#entryable_class MessageまたはComment
Entry#entryable_name "message"または"comment"
Entry.messages Entry.where(entryable_type: "Message")
Entry#message? entryable_type == "Message"の場合にtrueを返す
Entry#message entryable_type == "Message"の場合にメッセージレコードを返し、それ以外の場合はnilを返す
Entry#message_id entryable_type == "Message"の場合にentryable_idを返し、それ以外の場合はnilを返す
Entry.comments Entry.where(entryable_type: "Comment")
Entry#comment? entryable_type == "Comment"の場合にtrueを返す
Entry#comment entryable_type == "Comment"の場合にコメントレコードを返し、それ以外の場合はnilを返す
Entry#comment_id entryable_type == "Comment"の場合にentryable_idを返し、それ以外の場合はnilを返す

6.2 オブジェクトの作成

新しいEntryオブジェクトを作成する際に、同時にentryableサブクラスを指定できます。

Entry.create! entryable: Message.new(subject: "hello!")

6.3 追加のデリゲーションの追加

Entryデリゲータを拡張し、delegatesを定義してサブクラスへのポリモーフィズムを使用してさらに強化することができます。 例えば、Entryからサブクラスへのtitleメソッドのデリゲートを定義する場合は次のようにします。

class Entry < ApplicationRecord
  delegated_type :entryable, types: %w[ Message Comment ]
  delegates :title, to: :entryable
end

class Message < ApplicationRecord
  include Entryable

  def title
    subject
  end
end

class Comment < ApplicationRecord
  include Entryable

  def title
    content.truncate(20)
  end
end

フィードバック

このガイドの品質向上にご協力ください。

タイポや事実の誤りを見つけた場合は、ぜひ貢献してください。 開始するには、ドキュメントへの貢献セクションを読んでください。

不完全なコンテンツや最新でない情報も見つかるかもしれません。 メインのドキュメントに不足しているドキュメントを追加してください。 修正済みかどうかは、まずEdge Guidesを確認してください。 スタイルと規約については、Ruby on Rails Guides Guidelinesを確認してください。

修正すべき点を見つけたが、自分で修正できない場合は、 問題を報告してください

そして最後に、Ruby on Railsのドキュメントに関するあらゆる議論は、公式のRuby on Railsフォーラムで大歓迎です。