2009-07-08 18 views
9

如果我有在ActiveRecord的子對象的集合對象,即紅寶石類型的集合中的ActiveRecord

class Foo < ActiveRecord::Base 
    has_many :bars, ... 
end 

,我嘗試運行Array的find針對收集方法:

foo_instance.bars.find { ... } 

我收到:

ActiveRecord::RecordNotFound: Couldn't find Bar without an ID 

我認爲這是因爲ActiveRecord劫持了find方法爲自己的目的。現在,我可以使用detect,一切都很好。然而,爲了滿足自己的好奇心,我嘗試使用元編程來明確地偷find方法回來一個運行:

unbound_method = [].method('find').unbind 
unbound_method.bind(foo_instance.bars).call { ... } 

,我收到此錯誤:

TypeError: bind argument must be an instance of Array 

這麼清楚Ruby沒有想到foo_instance.bars是一個數組,但:

foo_instance.bars.instance_of?(Array) -> true 

任何人可以幫助我的這個和方法相關的解釋來解決它metaprogramm ING?

回答

6

正如其他人所說,一個關聯對象實際上不是一個數組。爲了找出真正的類,在IRB做到這一點:

class << foo_instance.bars 
    self 
end 
# => #<Class:#<ActiveRecord::Associations::HasManyAssociation:0x1704684>> 

ActiveRecord::Associations::HasManyAssociation.ancestors 
# => [ActiveRecord::Associations::HasManyAssociation, ActiveRecord::Associations::AssociationCollection, ActiveRecord::Associations::AssociationProxy, Object, Kernel] 

要擺脫的ActiveRecord ::被調用,當你做foo_instance.bars.find(),下面將幫助BSE#find方法的:

class << foo_instance.bars 
    undef find 
end 
foo_instance.bars.find {...} # Array#find is now called 

這是因爲AssociationProxy類的所有方法委託它不知道(通過的method_missing)將其#target,這是實際的底層數組實例。

9

I assume this is because ActiveRecord has hijacked the find method for its own purposes.

這不是真正的解釋。 foo_instance.bars不返回數組的實例,而是返回ActiveRecord::Associations::AssociationProxy的實例。這是一個特殊的類,旨在作爲持有該關聯的對象和關聯對象之間的代理。

AssociatioProxy對象充當數組,但它不是一個真正的數組。以下細節直接從文檔中獲取。

# Association proxies in Active Record are middlemen between the object that 
# holds the association, known as the <tt>@owner</tt>, and the actual associated 
# object, known as the <tt>@target</tt>. The kind of association any proxy is 
# about is available in <tt>@reflection</tt>. That's an instance of the class 
# ActiveRecord::Reflection::AssociationReflection. 
# 
# For example, given 
# 
# class Blog < ActiveRecord::Base 
#  has_many :posts 
# end 
# 
# blog = Blog.find(:first) 
# 
# the association proxy in <tt>blog.posts</tt> has the object in +blog+ as 
# <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and 
# the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro. 
# 
# This class has most of the basic instance methods removed, and delegates 
# unknown methods to <tt>@target</tt> via <tt>method_missing</tt>. As a 
# corner case, it even removes the +class+ method and that's why you get 
# 
# blog.posts.class # => Array 
# 
# though the object behind <tt>blog.posts</tt> is not an Array, but an 
# ActiveRecord::Associations::HasManyAssociation. 
# 
# The <tt>@target</tt> object is not \loaded until needed. For example, 
# 
# blog.posts.count 
# 
# is computed directly through SQL and does not trigger by itself the 
# instantiation of the actual post records. 

如果您想處理結果數組,您根本不需要元編程技巧。只需進行查詢,並確保在真實的Array對象上調用find方法,而不是在quacks like an array上的實例。

foo_instance.bars.all.find { ... } 

all方法是一個ActiveRecord finder方法(用於找到捷徑(:所有))。它返回結果的array。然後您可以在數組實例上調用Array#find方法。

+1

要澄清這裏,.all方法實際上檢索所有關聯模型,根據關聯的類型可能有巨大的內存影響。例如,如果它是User has_many:posts,那麼您可能正在檢索用戶的整個發佈歷史記錄,這可能是大量的數據。在可能的情況下,嘗試使用條件或命名範圍構建查找調用,以獲得更好的性能。 – tadman 2009-07-08 19:38:20

3

ActiveRecord關聯實際上是Reflection的實例,它覆蓋了instance_of?和相關的方法來說謊它是什麼類。這就是爲什麼你可以做一些事情,比如添加命名範圍(比如說「recent」),然後調用foo_instance.bars.recent。如果「酒吧」是一個數組,這將是非常棘手。

嘗試檢查源代碼(「locate reflections.rb」應在任何unix-ish框中跟蹤它)。 Chad Fowler就此話題發表了非常豐富的討論,但我似乎無法在網絡上找到任何鏈接。 (任何人想編輯這篇文章,包括一些?)