jhamm
jhamm

Reputation: 25032

Return type from a ActiveRecord query

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

Answers (2)

jdoe
jdoe

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

Marlin Pierce
Marlin Pierce

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,

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

Upvotes: 1

Related Questions