關聯將外鍵操作封裝為語義化方法,實現:
@customer.orders.create(...) 自動填充 customer_id。@customer.destroy 級聯刪除訂單(dependent: :destroy)。@customer.orders.where(status: 'shipped').includes(:line_items)。| 關聯 | 關係 | 外鍵位置 | 典型場景 |
|---|---|---|---|
belongs_to | 一對一(從屬) | 本表 | Order 屬於 Customer |
has_one | 一對一(擁有) | 關聯表 | Supplier 擁有一個 Account |
has_many | 一對多 | 關聯表 | Customer 擁有多個 Order |
has_many :through | 多對多(帶中間模型) | 中間表 | Physician ↔ Patient 通過 Appointment |
has_one :through | 一對一(經中間模型) | 中間表 | Supplier → Account → AccountHistory |
has_and_belongs_to_many | 多對多(無中間模型) | 連接表 | Assembly ↔ Parts |
class Supplier < ActiveRecord::Base has_one :account end class Account < ActiveRecord::Base belongs_to :supplier # accounts 表有 supplier_id end # 使用 supplier.build_account(attributes) supplier.create_account(attributes) supplier.account # 返回 Account 或 nil
選擇原則:外鍵在哪張表,那張表就用 belongs_to;另一邊用 has_one。
class Customer < ActiveRecord::Base has_many :orders, dependent: :destroy end class Order < ActiveRecord::Base belongs_to :customer end # 使用 @customer.orders.create(order_date: Time.now) @customer.orders.where(status: 'paid') @customer.orders.count
關聯名稱必須用複數(:orders)。
class Physician < ActiveRecord::Base has_many :appointments has_many :patients, through: :appointments end class Appointment < ActiveRecord::Base belongs_to :physician belongs_to :patient # 可擁有自己的屬性:appointment_date, notes end class Patient < ActiveRecord::Base has_many :appointments has_many :physicians, through: :appointments end
中間模型 Appointment 是獨立實體,支持驗證、回調、額外屬性。
class Assembly < ActiveRecord::Base has_and_belongs_to_many :parts end class Part < ActiveRecord::Base has_and_belongs_to_many :assemblies end # 遷移:無 id 的連接表 create_table :assemblies_parts, id: false do |t| t.integer :assembly_id t.integer :part_id end
連接表無主鍵、無模型,僅存雙向 ID。不需在關聯上掛業務邏輯時使用。
需要中間模型承載業務邏輯(驗證/回調/屬性)? ├─ 是 → has_many :through └─ 否 → has_and_belongs_to_many
一個模型屬於多種不同模型(如圖片同時屬於員工和產品)。
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 # 遷移 create_table :pictures do |t| t.string :name t.references :imageable, polymorphic: true # 生成 imageable_id + imageable_type t.timestamps end
同一表內的層級關係(員工-經理、分類-子分類)。
class Employee < ActiveRecord::Base has_many :subordinates, class_name: "Employee", foreign_key: "manager_id" belongs_to :manager, class_name: "Employee" end # 遷移 add_column :employees, :manager_id, :integer add_index :employees, :manager_id
| 選項 | 適用關聯 | 作用 |
|---|---|---|
dependent: :destroy | has_many/has_one | 刪除主對象時級聯刪除關聯對象(觸發回調) |
dependent: :delete_all | has_many | 直接執行 SQL 刪除,跳過回調,性能更高 |
dependent: :nullify | has_many/belongs_to | 置外鍵為 NULL |
counter_cache: true | belongs_to | 自動維護父表 children_count 列 |
inverse_of: :parent | 雙向關聯 | 優化內存,避免重複加載同一對象 |
validate: true | has_many/has_one | 保存父對象時驗證子對象 |
autosave: true | has_many/has_one | 自動保存關聯對象(新建/修改/標記刪除) |
@customers.each { |c| c.orders } → Customer.includes(:orders).all。inverse_of 確保 @order.customer.orders 包含 @order 而不再查庫。has_many :through 不能用於 HABTM 遷移:需先建中間模型遷移,再建立關聯。add_index :pictures, [:imageable_type, :imageable_id] 必加。ActiveRecord 關聯是 Rails 區別於其他 ORM 的殺手級特性。掌握六大類型選型邏輯、多態與自連接建模、關聯選項微調,能讓你用極少代碼表達複雜領域關係,同時獲得高性能查詢與數據一致性保障。記住:外鍵在誰那裡,誰就 belongs_to,其餘順勢而為。