2009-07-08 17 views
9

Si tengo un objeto con una colección de objetos secundarios en ActiveRecord, es decirtipos de Ruby de colecciones en ActiveRecord

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

y trato de ejecutar el método de matriz find contra esa colección:

foo_instance.bars.find { ... } 

Recibo:

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

Supongo que esto se debe a que ActiveRecord ha secuestrado el find método para sus propios fines. Ahora, puedo usar detect y todo está bien. Sin embargo, para satisfacer mi propia curiosidad, he tratado de utilizar metaprogramming robar explícitamente el método find de vuelta para una carrera:

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

y recibo este error:

TypeError: bind argument must be an instance of Array 

tan claramente Rubí no piensa foo_instance.bars es una matriz y, sin embargo:

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

¿Puede alguien ayudarme con una explicación de esto y de una manera de conseguir alrededor de él con metaprogramm ¿En g?

Respuesta

6

Como han dicho otros, un objeto de asociación no es realmente una matriz.Para averiguar la clase de bienes, hacer esto en 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] 

para deshacerse de la ActiveRecord :: Bse el método # hallazgo que es llamada cuando se hace foo_instance.bars.find(), lo siguiente ayuda:

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

Esto es porque los delegados de clase AssociationProxy todos los métodos que no sabe sobre (a través de method_missing) a su #target, que es la instancia actual matriz subyacente.

9

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

Esa no es realmente la verdadera explicación. foo_instance.bars no devuelve una instancia de Array sino una instancia de ActiveRecord::Associations::AssociationProxy. Esta es una clase especial destinada a actuar como un proxy entre el objeto que contiene la asociación y el asociado.

El objeto AssociatioProxy actúa como una matriz, pero en realidad no es una matriz. Los siguientes detalles se toman directamente de la documentación.

# 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. 

Si desea trabajar en la matriz de resultados, no necesita ninguna habilidad de metaprogramación. Simplemente realice la consulta y asegúrese de llamar al método find en un objeto Array real y no en una instancia que sea quacks like an array.

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

El método all es un método de búsqueda ActiveRecord (un acceso directo para find (: todos)). Devuelve array de resultados. Luego puede llamar al método Array#find en la instancia de la matriz.

+1

Para aclarar aquí, el método .all realmente recupera todos los modelos asociados que pueden tener un gran impacto de memoria dependiendo del tipo de asociación. Por ejemplo, si se trata de User has_many: posts, es posible que esté recuperando el historial de publicación completo de un usuario, que podría ser una gran cantidad de datos. Donde sea posible, intente construir una llamada de búsqueda con condiciones o ámbitos nombrados para un mejor rendimiento. – tadman

3

Las asociaciones de ActiveRecord son en realidad instancias de Reflection, que anulan instance_of? y métodos relacionados para mentir sobre qué clase es. Es por esto que puede hacer cosas como agregar ámbitos con nombre (por ejemplo, "reciente") y luego llamar a foo_instance.bars.recent. Si "barras" era una matriz, esto sería bastante complicado.

Intenta verificar el código fuente ("locate reflections.rb" debería rastrearlo en cualquier cuadro unix-ish). Chad Fowler ha dado una charla muy informativa sobre este tema, pero parece que no puedo encontrar ningún enlace en la web. (¿Alguien quiere editar esta publicación para incluirla?)

Cuestiones relacionadas