来自 http://hi.baidu.com/rainchen/item/034bd0d60a24a32938f6f7dd
现有个场景,有两个model:person和album,需要添加一个图片来做为其头像/封面。添加一个image model,按照以往需要对这几个model做以下关联设置:
class Person < ActiveRecord::Base
has_one :image, :dependent => :destroy
end
class Album < ActiveRecord::Base
has_one :image, :dependent => :destroy
end
class Image < ActiveRecord::Base
belongs_to :person
belongs_to :album
end
相应的image的migration要添加上关联字段:
t.column :person_id, :integer, :null => false, :default => 0
t.column :album_id, :integer, :null => false, :default => 0
不复杂,但是麻烦,如果以后加个book model之类的,也需要有个图做封面的,那又要改image model里的关联和migration了,一点都不DRY。
这时就该 polymorphic 上场了。
先上代码:
class Person < ActiveRecord::Base
has_one :image, :as => :iconable, :dependent => :destroy
end
class Album < ActiveRecord::Base
has_one :image, :as => :iconable, :dependent => :destroy
end
class Image < ActiveRecord::Base
belongs_to :iconable, :polymorphic => true
end
解释:
polymorphic的实现是添加一个虚拟的第三方model(作为原model的一个形态)来维护彼此的联系。
首先person和album在关联时添加一个:as选项,设置一个虚拟的iconable model,可以直白理解为“每个person都有一个iconable形态的image”。
然后设置image属于iconable,且设置:polymorphic => true 表明这个iconable model是虚的,仅仅用来维持关联。
iconable是虚的,但用来维持彼此联系的“证据”还是必须实实在在的,修改image的migration为:
# 关联对象的id
t.column :iconable_id, :integer, :null => false, :default => 0
# 关联对象的类名,如:Person
t.column :iconable_type, :string, :null => false, :default => ''
这样就可以在controller里这样使用:
# 添加:
@person = Person.new(params[:person])
@person.build_image(params[:image])
@person.save
# 读取:
@person.image
# image 的属主:
@person.iconable
相关参考:
HowToUsePolymorphicAssociations
UnderstandingPolymorphicAssociations
has_many_polymorphs for dummies