Типы коллекций Ruby в 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
так ясно, что Руби не думает foo_instance.bars
является массивом и все же:
foo_instance.bars.instance_of?(Array) -> true
3 ответов
как говорили другие, объект ассоциации на самом деле не является массивом. Чтобы узнать реальный класс, сделайте это в 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:: Bse#find, который вызывается при выполнении foo_instance.палка.найти (), поможет следующее:
class << foo_instance.bars
undef find
end
foo_instance.bars.find {...} # Array#find is now called
это связано с тем, что класс AssociationProxy делегирует все методы, о которых он не знает (через method_missing) своему #target, который является фактическим базовым экземпляром массива.
Я предполагаю, что это потому, что ActiveRecord захватил метод find для его собственная цель.
это не совсем реальное объяснение. 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.
если вы хотите работать на такие результаты, вам не нужны навыки метапрограммирования вообще. Просто сделайте запрос и убедитесь, что вызовите метод find для реального объекта массива, а не для экземпляра, который крякает как массив.
foo_instance.bars.all.find { ... }
The all
метод является методом поиска ActiveRecord (ярлык для find (: all)). Он возвращает array
результатов. Тогда вы можете вызвать Array#find
метод на массиве пример.
Ассоциации ActiveRecord на самом деле являются экземплярами отражения, которое переопределяет instance_of? и связанные с этим методы лгать о том, какой это класс. Вот почему вы можете делать такие вещи, как добавление именованных областей (скажем, "недавние"), а затем вызывать foo_instance.палка.недавний. Если бы "бары" были массивом, это было бы довольно сложно.
попробуйте проверить исходный код ("найдите отражения.rb " должен отслеживать его на любом Unix-ish box). Чад Фаулер дал очень информативный доклад об этом тема, но я не могу найти никаких ссылок на нее в интернете. (Кто-нибудь хочет отредактировать этот пост, чтобы включить некоторые?)