注意点
- テーブル名は必ず複数形
- クラス名は必ず単数系
- 関連づけから自動的にモデルクラス名を推測するので、記載するときは、気をつける
- 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
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
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
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
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