2012-05-03 85 views
4

我们的Rails 3应用程序有型号为PersonMessage。消息可以是特定于个人的(当消息person_id列已设置)或者它们可以是“全局”(当person_id列为NULL时)。Rails“has_many”与NULL外键的关联

我们希望有使用简单has_many关系:conditions option这样:

class Person < ActiveRecord::Base 
    has_many :messages, 
     :conditions => proc { ['(messages.person_id IS NULL) OR ' + 
          '(messages.person_id = ?)'], self.id } 
    # ... 
end 

但现在看来,该has_many类方法执行后编码“条件”选项为逻辑“与”条款与Person对象的ID相等的外键约束(例如“FROM messages WHERE person_id=123 AND (person_id IS NULL OR person_id=123)”)。看起来没有办法允许具有空外键的关联对象属于这种关联。

Rails 3/ActiveRecord提供了一种方法来做到这一点,或者我必须破解我自己的关联方法吗?

回答

1

像你想的使用状况下,你不能有一个OR子句ActiveRecord关联。你可以没有条件的关联,然后添加一个方法来包含你想要的全局消息。这样,在构建相关记录时,您仍然可以利用该关联。

# person.rb 
has_many :messages 

def all_messages 
    Message.where('messages.person_id=? OR messages.person_id IS NULL', id) 
end 

这里是我的Squeel宝石标准插头,这是非常方便的像这样的,如果你不希望有SQL位代码中的更高级的查询。一探究竟。

+0

不幸的是,这意味着你不能这样做:Person.limit(2).includes(:all_messages),因为all_messages不是关联。 –

+0

@JoeVanDyk是的,你说得对。但是,包含(:all_messages)会执行额外的SQL查询来执行急切加载。因此,除了语法外,根据我的建议,在性能方面没有实际差异。 –

+0

执行包含(:all_messages)会导致最多一个额外的查询。如果你确实Person.limit(100).includes(:all_messages),这会导致两个查询。如果您使用上面的#all_messages,那将会是101个查询来加载所有内容。 –

0

我认为你是正确的,你可以放弃的has_many,并创建一个范围与外部连接,这样的:

class Person < ActiveRecord::Base 
    scope :messages lambda { 
     joins("RIGHT OUTER Join messages on persons.id = messages.person_id") 
     .where('persons.id = ?',self.id) 
    }