MTI (multiple table inheritance)
昨天我們介紹了STI,運用在大量重複欄位出現時
可以用同一個表格實作複數以上的model
但是當不同的欄位比相同的還多很多
卻又想要適度的統整時,STI就不太適合
這個時候就可以考慮比較不為人知的MTI
顧名思義,這是複數表格繼承
接續上面的範例
也就是原本的Dog與Cat都有自己的表格,但同時也繼承了Animal
實作上稍有不同
首先在migration的時候就要特別注意,
雖然實作的model是Dog,Cat,但是table不一樣
#animal一樣
create_table :animals do |t|
t.string :name
t.string :type
t.timestamps null: false
end
#cat,dog需要以companions後綴
create_table :cat_companions do |t|
t.integer :cat_id
t.string :sleep
end
create_table :dog_companions do |t|
t.integer :dog_id
t.string :run
end
接著rake db:migrate
#animal.rb
class Animal < ActiveRecord::Base
#...
end
#cat.rb
class Cat < Animal
has_one :companion, class_name: "CatCompanion", inverse_of: :cat, dependent: :destroy, autosave: true
delegate :sleep, :sleep=, to: :lazily_built_companion
private
def lazily_built_companion
companion || build_companion
end
end
#cat_companion.rb
class CatCompanion < ActiveRecord::Base
belongs_to :cat, inverse_of: :companion
validates :cat, presence: true
end
#dog.rb
class Dog < Animal
has_one :companion , class_name: "DogCompanion", inverse_of: :dog, dependent: :destroy, autosave: true
delegate :run, :run=, to: :lazily_built_companion
private
def lazily_built_companion
companion || build_companion
end
end
#dog_companion.rb
class DogCompanion < ActiveRecord::Base
belongs_to :dog, inverse_of: :companion
validates :dog, presence: true
end
登愣!設定到這邊就完成啦!
如果進console測試Dog.create(name: "Woo", run: "fast")
是可以成功寫入
Dog這個model不存在真實的表格,裡面的欄位由Animal與DogCompanion組合而成
取用時也跟一般的實體model沒什麼不同
例如:
dog = Dog.last
dog.name
=> "Woo"
dog.run
=> "fast"
可惜的是,這樣方便的技術(除了初始的設定稍微麻煩一些之外)有一個障礙
原生的ActiveRecord可能會有問題,例如:
create_table :dog_companions do |t|
t.integer :dog_id
t.integer :master_id
t.string :run
end
create_table :masters do |t|
t.string :name
t.timestamps null: false
end
假設新增了主人(Master)的model,每個主人會有多隻狗
Master對Dog為一對多
#master.rb
class Master < ActiveRecord::Base
has_many :dogs
end
#dog.rb
class Dog < Animal
#...省略
belongs_to :master
delegate :run, :run=,:master_id,:master_id=, to: :lazily_built_companion
#...省略
end
在給取值時會發生下面的問題
m = Master.create(name: "Bater")
m.dogs
=>Dog Load (0.7ms) SELECT "animals".* FROM "animals" WHERE "animals"."type" IN ('Dog') AND "animals"."master_id" = ? [[nil, 1]]
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: animals.master_id: SELECT "animals".* FROM "animals" WHERE "animals"."type" IN ('Dog') AND "animals"."master_id" = ?
我們可以看到他依然會在animal裡面試圖尋找master_id這個欄位
如果反過來從dog去查詢
d = Dog.create(name: "Woo",run: "slow",master_id: 1)
d.master_id
=> 1
雖然直接呼叫master_id可以得到正確的值
但是d.master
卻只會得到nil的回應
要解決這樣的問題,可以透過增加model方法,例如在Dog Model寫下
def get_master
Master.find(master_id)
end
之後的Dog就可以透過get_master方法取得Master
可惜反向卻會遇上一樣的問題Dog.where(master_id: 1)
依然只會在animal裡面找master_id
這個問題目前還沒找到解答,等知道了之後再行補充。
以上就是今天的內容,有沒有很簡單呢?