问题
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?
回答1:
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).
回答2:
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,
- You can chain scopes and query methods, but only until first or all. After first or all you cannot call more scopes and query methods.
- When you call all, you get a Result Array. Some of the Array methods have been redefined to act on the database, so if you want to operate on the array returned, call to_a. An example is count, which if called on the Result Array will query the database for how many records would be in the array if the array where queried again.
来源:https://stackoverflow.com/questions/10429217/return-type-from-a-activerecord-query