0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rails4 アソシエーション

Last updated at Posted at 2018-05-31

Active Recordの関連付け(アソシエーション)

注意点

  • テーブル名は必ず複数形
  • クラス名は必ず単数系
  • 関連づけから自動的にモデルクラス名を推測するので、記載するときは、気をつける
  • rails generateに記載するのは、クラス名なので、単数系(migrationの作成は自動で、複数形のテーブルを生成してくれる)

関連づけの種類

モデルに記載する

belongs_to (1:1の従属 A→B)

orderは、customerに 従属 している

order:customerの場合

class Order < ActiveRecord::Base
  belongs_to :customer
end

orderのmodel classに、belongs_to :customer を記載する

belongs_toは、 必ず単数系

has_one (1:1の所有 A←B)

supplierは、accountを 所有 している

class Supplier < ActiveRecord::Base
  has_one :account
end

has_many (1:多の所有 A←B)

「0個以上の」インスタンスを 所有

customerは、orderを0個以上 所有 している

class Customer < ActiveRecord::Base
  has_many :orders
end

has_manyは、 必ず複数系

has_many :through (多:多 中間の所有 A→B←C)

2つのモデルの間に「第3のモデル」(結合モデル)が介在する
相手モデルの「0個以上」のインスタンスとマッチ

例:患者(patient)が医師(physician)との診察予約(appointment)

class Physician < ActiveRecord::Base
  has_many :appointments
  has_many :patients, through: :appointments
end
 
class Appointment < ActiveRecord::Base
  belongs_to :physician
  belongs_to :patient
end
 
class Patient < ActiveRecord::Base
  has_many :appointments
  has_many :physicians, through: :appointments
end
migration
class CreateAppointments < ActiveRecord::Migration
  def change
    create_table :physicians do |t|
      t.string :name
      t.timestamps
    end
 
    create_table :patients do |t|
      t.string :name
      t.timestamps
    end
 
    create_table :appointments do |t|
      t.belongs_to :physician # ここ重要
      t.belongs_to :patient   # ここ重要
      t.datetime :appointment_date
      t.timestamps
    end
  end
end

1つのドキュメントに多くの節(section)があり、1つの節の下に多くの段落(paragraph)がある状態で、節をスキップしてドキュメントの下のすべての段落の単純なコレクションが欲しいとします

ツリーの様な構造

class Document < ActiveRecord::Base
  has_many :sections
  has_many :paragraphs, through: :sections
end
 
class Section < ActiveRecord::Base
  belongs_to :document
  has_many :paragraphs
end
 
class Paragraph < ActiveRecord::Base
  belongs_to :section
end

!! throughは、そこを通るイメージ !!

has_one :through(1:1 中間の所有 A→B→C)

1人の提供者(supplier)が1つのアカウントに関連付けられ、さらに1つのアカウントが1つのアカウント履歴に関連付けられる場合

A → B → C の用な関係(1:1)

class Supplier < ActiveRecord::Base
  has_one :account
  has_one :account_history, through: :account
end
 
class Account < ActiveRecord::Base
  belongs_to :supplier
  has_one :account_history
end
 
class AccountHistory < ActiveRecord::Base
  belongs_to :account
end

has_and_belongs_to_many(多:多 互いに従属&所有)

「多対多」のつながりを作成しますが、through:を指定した場合と異なり、第3のモデル(結合モデル)が介在しません
※結合用のテーブルは必要(モデルは不要)

class Assembly < ActiveRecord::Base
  has_and_belongs_to_many :parts
end
 
class Part < ActiveRecord::Base
  has_and_belongs_to_many :assemblies
end
migration
class CreateAssembliesAndParts < ActiveRecord::Migration
  def change
    create_table :assemblies do |t|
      t.string :name
      t.timestamps
    end
 
    create_table :parts do |t|
      t.string :part_number
      t.timestamps
    end
 
    create_table :assemblies_parts, id: false do |t|
      t.belongs_to :assembly
      t.belongs_to :part
    end
  end
end

tips

belongs_toとhas_oneどちらを選ぶか

外部キーをどちらに置くか
外部キー = belongs_to

どちらが主語(目的)か。

  • 供給者がアカウントを持っている ○
  • アカウントが供給者を持っている ×
class Supplier < ActiveRecord::Base
  has_one :account
end
 
class Account < ActiveRecord::Base
  belongs_to :supplier
end
class CreateSuppliers < ActiveRecord::Migration
  def change
    create_table :suppliers do |t|
      t.string :name
      t.timestamps
    end
 
    create_table :accounts do |t|
      t.integer :supplier_id #外部キーの指定 (3or4系 t.references :supplier と書く)
      t.string  :account_number
      t.timestamps
    end
  end
end

has_many :throughとhas_and_belongs_to_manyのどちらを選ぶか

has_many :through

リレーションシップのモデルそれ自体を独立したエンティティとして扱いたい
中間モデルを使いたい場合

has_and_belogns_to_many

リレーションシップのモデルで何か特別なことをする必要がまったくないのであれば、結合モデルの不要なhas_and_belongs_to_manyリレーションシップを使用するのがシンプル
中間モデルは不要な場合

まとめ

結合モデルで検証(validation)、コールバック、追加の属性が必要なのであれば、has_many :throughを使用した方がよい

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

ある1つのモデルが他の複数のモデルに属していることを、1つの関連付けだけで表現する
※エイリアスを設定して、対応するイメージ

写真を、社員と商品の両方に従属させるようにしたい

class Picture < ActiveRecord::Base
  belongs_to :imageable, polymorphic: true
end
 
class Employee < ActiveRecord::Base
  has_many :pictures, as: :imageable
end
 
class Product < ActiveRecord::Base
  has_many :pictures, as: :imageable
end
migration
class CreatePictures < ActiveRecord::Migration
  def change
    create_table :pictures do |t|
      t.string  :name
      t.references :imageable, polymorphic: true
      t.timestamps
    end
  end
end

自己結合

1つのデータベースモデルに全従業員を格納しておきたいが、マネージャーと部下(subordinate)の関係も追えるようにしておきたい場合

class Employee < ActiveRecord::Base
  has_many :subordinates, class_name: "Employee",
                          foreign_key: "manager_id"
 
  belongs_to :manager, class_name: "Employee"
end
migration
class CreateEmployees < ActiveRecord::Migration
  def change
    create_table :employees do |t|
      t.references :manager #自信に外部キーを設定する
      t.timestamps
    end
  end
end

注意事項

キャッシュ制御

関連付けのメソッドは、すべてキャッシュを中心に構築される
別の場所から関連テーブルが更新された場合、キャッシュが残り、悪影響を及ぼす可能性がある

関連付けのメソッド呼び出しでtrueを指定する

customer.orders(true).empty? 

名前衝突の回避

関連付けを作成すると、モデルにその名前のメソッドが追加される
ActiveRecord::Baseのインスタンスで既に使用されているような名前を関連付けに使用するのは禁物

スキーマの更新

belongs_toは、対象テーブルに外部キーが設定されている必要がある

class CreateOrders < ActiveRecord::Migration
  def change
    create_table :orders do |t|
      t.datetime :order_date
      t.string   :order_number
      t.integer  :customer_id # 外部キーは、t.references or t.モデル名_id
    end
  end
end

has_and_belongs_to_manyは、それに対応する結合(join)テーブルを明示的に作成しておく

自動:辞書順に結合したもの
手動::join_tableオプションを指定してする

class CreateAssembliesPartsJoinTable < ActiveRecord::Migration
  def change
	# 主キーは設定しないこと
    create_table :assemblies_parts, id: false do |t|
      t.integer :assembly_id
      t.integer :part_id
    end
  end
end

関連付けのスコープ制御

関連付けによって探索されるオブジェクトは、 現在のモジュールのスコープ内のみ

異なる名前空間にあるモデルを関連付けるには、関連付けの宣言で 完全なクラス名を指定する

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

双方向関連づけ

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

途中で共通の値を更新した場合、双方向連携は正常に動作しない

inverse_ofオプションをつけることで回避できるが、下記制限がある

  • :through関連付けと併用することはできません。
  • :polymorphic関連付けと併用することはできません。
  • :as関連付けと併用することはできません。
  • belongs_to関連付けの場合、has_manyの逆関連付けは無視されます。

標準な名前であれば、通常でサポートされるが、
下記の場合サポートされないため、明示する必要がある
しかし、polymorphicなどは、上記制限のため、使えない?

  • :conditions
  • :through
  • :polymorphic
  • :foreign_key
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?