has_many :through, Yes. belongs_to :through, No.

自从Rails出世以来,就提供了has_onehas_manyhas_and_belongs_to_many,以及belongs_to这几种典型的模型关联关系。先说has_and_belongs_to_many

has_and_belongs_to_many是一个好东西,仅用一张中间链表就模拟出了拟双向多对多的关系。而rails并没有就此止步,从rails1.1开始,添加了一个新的关联关系,即 has_many :through 。他仍然是habtm关系,但是更加优雅,让在后台默默无闻的中间链表从此有了自己的模型,主键,并可以扩充模型方法,用来实现更加优雅的的查询等...我自己一直把他认为是一种高级的habtm,并且尽量使用这种高级habtm,以保持灵活性,方便将来的扩充。

有下面一种场景:

class Shop < ActiveRecord  
  belongs_to :city
  belongs_to :country, :through => :city
end  

可以实现Shop#country么?答案是不可以。hasmany和hasone都有:through了,belongs_to偏偏就没有,为了实现同等效果,我通常写这样的方法:

def country  
  city.country
end  

有点丑,不是么?这里解释了为什么 RailsCore团队 迟迟没有添加对belongs_to :through的支持,Rick Olson (Rails 的 Core 成员)这么说:

Because belongsto is for a foreign key thats in the current table. If you're going :through a model, it's not the same and hasone should be used.

这已经是大概两年半以前的事情了,其实要优雅的处理这种情况很容易,使用ActiveSupport提供的 delegate 。但是在 Rails2.3.2 之前。我并不敢大胆这么用,因为如果你的delegate的目标对象不存在的话,调用delegate方法会出”NoMethodError"异常。所以,我被迫这样写:

def country  
  city ? city.country : nil
end  

好消息是从 Rails2.3.2 开始为delegate添加了一个新的参数 :allow_nil ,使得当委托对象不存在时直接返回nil,以后就可以大胆的用了:

delegate :country, :to => :city, :allow_nil => true