Reputation: 25032
What will let me know if I am going to get a Relation, Array, or some other type back from an ActiveRecord call? I know I can type .class in the console and figure it out, but is there something in the call itself that will let me know what I am asking for?
Upvotes: 5
Views: 4977
Reputation: 15771
You know, Rails sometimes lies to you -- all magicians do :)
Rails allows you to build complex queries by chaining your has_many
associations. The core of this functionality is a bunch of XXXAssocation (like HasManyAssociation
) classes.
When you call .class
on a has_many
association your call is applied in fact for HasManyAssociation
instance. But here's the magic starts:
# collection_proxy.rb
instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/ }
Rails undefs (hides) methods of HasManyAssociation
instance (except the few, as you can see in the regular expression) and then uses delegation and method_missing
to pass your call to some underlying array (if you're trying to fetch records) or to association itself (if you're chaining your association):
delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from,
:lock, :readonly, :having, :pluck, :to => :scoped
delegate :target, :load_target, :loaded?, :to => :@association
delegate :select, :find, :first, :last,
:build, :create, :create!,
:concat, :replace, :delete_all, :destroy_all, :delete, :destroy, :uniq,
:sum, :count, :size, :length, :empty?,
:any?, :many?, :include?,
:to => :@association
def method_missing(method, *args, &block)
match = DynamicFinderMatch.match(method)
if match && match.instantiator?
send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r|
proxy_association.send :set_owner_attributes, r
proxy_association.send :add_to_target, r
yield(r) if block_given?
end
end
if target.respond_to?(method) || (!proxy_association.klass.respond_to?(method) && Class.respond_to?(method))
if load_target
if target.respond_to?(method)
target.send(method, *args, &block)
else
begin
super
rescue NoMethodError => e
raise e, e.message.sub(/ for #<.*$/, " via proxy for #{target}")
end
end
end
else
scoped.readonly(nil).send(method, *args, &block)
end
end
So, HasManyAssociation
instance decides what to handle by itself and what needs to be accomplished via hidden array (class
method isn't what HasManyAssociation
interested in so it will be called on this hidden array. The result, of course, will be Array
, which is a little deception).
Upvotes: 2
Reputation: 10089
Here is my perception, along the lines of what I think is important to know. It's mostly from memory and off the top of my head with a little console experimentation, so I'm sure if this gets passed around it could be improved. Comments welcome, and requested.
Derived ActiveRecord class --> Record Instance
find
Derived ActiveRecord class | Relation --> Relation
where, select, joins, order, group, having, limit, offset, a scope
Derived ActiveRecord class | Relation --> Record Instance
find
Derived ActiveRecord class | Relation --> Result Array
all
Result Array --> Array
to_a
So what's important is,
Upvotes: 1