原E文地址:http://blog.hasmanythrough.com/2006/04/20/many-to-many-dance-off
我已注意到使用 Rails 关联在创建多对多关系的两种方式上有些混乱。出现混乱是可以理解的,因为has_many :through 还有些新,写它的文章并不多。
Has_and_belongs_to_many 出现的较早,确定的角色与资料超出了你为什么使用多对多关系的假设。事实上,许多人似乎并没有抓住本质上的区别。
就像我们从观看经典影片所学到的,告诉我们两个预期选择之间的区别最好方式是它们的舞蹈。你得观察每个人的表演。那么我们必须在两者之间选择哪个演员呢?
一、连接表:简单的关联
表:
create_table "dancers_movies", :id => false do |t| t.column "dancer_id", :integer, :null => false t.column "movie_id", :integer, :null => false end
模型:
class Dancer < ActiveRecord::Base has_and_belongs_to_many :movies end class Movie < ActiveRecord::Base has_and_belongs_to_many :dancers end
has_and_belongs_to_many 关联的设置很简单。连接表只有被连接模型的外键,没有主键或其它属性。(其它属性使用暂时的 push_with_attributes 来支持,但该特征被废弃了。)此处没有给连接表使用的模型类。
二、连接模型:富关联(Rich Associations)
表:
create_table "appearances", do |t| t.column "dancer_id", :integer, :null => false t.column "movie_id", :integer, :null => false t.column "character_name", :string t.column "dance_numbers", :integer end
模型:
class Appearance < ActiveRecord::Base belongs_to :dancer belongs_to :movie end class Dancer < ActiveRecord::Base has_many :appearances, :dependent => true has_many :movies, :through => :appearances end class Movie < ActiveRecord::Base has_many :appearances, :dependent => true has_many :dancers, :through => :appearances end
has_many :through 关联对这个简单例子来说很容易设定的,但是当使用其它特征如 polymorphism 时要有些技巧。用于连接模型的表有个主键并包含了类似其它模型的属性。
三、Checking out the moves
下面是对两种操作基本特征的比较
关联Association | has_and_belongs_to_many | has_many :through |
AKA | habtm关联 | through association遍历关联 |
结构Structure | 连接表(Join Table) | 连接模型(Join Model) |
主键Primary Key | no | yes |
富关联Rich Association | no | yes |
代理集合Proxy Collection | yes | no |
Distinct 选择 | yes | yes |
自引用Self-Referential | yes | yes |
早期加载Eager Loading | yes | yes |
多态性Polymorphism | no | yes |
多向连接N-way Joins | no | yes |
该表格内包含了很多东西,现在我们分别看一下:
(1)、结构:
has_and_belongs_to_many 使用了简单的连接表,表的每一行记录只有两个外键。没有为连接表使用模型类的连接记录从不会被直接访问。
has_many :through 更新连接表为一个完整的模型。它使用一个模型类来表现表内的条目。
(2)、主键:
连接表没有主键。我听说一些人,如从外键对创建一个主键,但 Rails 从不为任何东西使用主键。我不能肯定你为什么要得到创建的主键,虽然它可能依据你数据库给你一些性能上的好处。 (我不是一个 DBA ,所以对于主键不想说得太多。)
连接模型有键,就像其它模型。这意味着你可以直接地访问及管理记录。
(3)富关联:
在 Rails 1.1 以前,你使用 push_with_attributes 来存储额外的属性到你的 habtm 连接表内。这样做会带来很多问题,包括随后不能更新属性。Push_with_attributes 现在不再使用了。如果你希望一个带有额外属性的富关联的话,使用连接模型。
(4)、代理集合:
使用 habtm 的一个好处是,关联是个代理集合。这意味着你可以使用关联的 << 方法在连接表内创建条目,就像 has_many 关联。因为连接模型记录有这些额外属性,所以用同样方式来自动创建连接表条目会更复杂。所以你必须手工创建连接模型条目。(更完整的解释,可参阅我的 Why aren't join models proxy collections? 文章。)
(5)、Distinct 选择:
有时候,连接表(或模型)在同样的记录之间可能有多个引用。例如,一个人可能是一本书的作者及插图绘制者。如果你有多个引用,数据库会返回给你所有与你查询相关多条记录。选项 :uniq 告诉关联过滤重复的对象,以便你只得到每个记录的一条显示。这类似于在 SQL 中使用 DISTINCT 关键字,但这是在 Ruby 内而不是数据库内移除重复发生的记录。这篇文章原只由 habtm 支持的 :uniq 才能查到,但现在通过 through 关联也可以查到。
(6)、自引用:
Habtm 与 through 关联都可是自引用的。Users 的朋友用户就是一个自引用关系的例子。你可以通过在关联上使用带有 :foreign_key 与 :association_foreign_key 选项的 habtm 来完成。同样也可以使用 through 关联,因为它的做法不太明显,所以稍后我会写出如何处理它。
(7)、早期加载
Habtm 与 through 关联都通过 :include 选项来支持被关联对象的早期加载。
(8)、多态性:
连接模型与 through 关联可以用 polymorphic 模型类型工作。
(9)、多向连接
一个 habtm 关联只可以连接两个模型。但有时候你需要表现多个模型的关联。例如登记的可能是一位飞行家,一个乘客,及一个座位的分配。使用 through 关联,你可以创建一个连接模型,它连接你需要的多个模型。技巧部分是如何构建可方便地得到被关联对象的查询。
四、And the winner is...
has_and_belongs_to_many 的使用范围狭小。但 has_many :through 则很广泛。
Seriously, there's no way to pick a winner here. Like any engineering decision, choosing a join table or a join model is a matter of picking the right tool for the job (or the right dancer for the part). Now that you've seen our players go head to head, you can make a better choice about who should get that part.
五、回帖部分:
1、David on 2006年4月21日
关于 distinct 选择 ---- 我认为你可以附加 :select 'DISTINCT *' 给关联,类似于: has_many foos, :through => bars, :select 'DISTINCT *'