Search code examples
ruby-on-railsruby-on-rails-3activerecordactive-relation

Why/How is a class method accessible to an array of records only through an association in Rails 3?


Consider a simple example, where we have 2 models, Article and Category.

class Article < ActiveRecord::Base
  belongs_to :category

  def self.search(title)
    where(:title => title)
  end
end

class Category < ActiveRecord::Base
  has_many :articles
end

Now, on the rails console :

Article.all.search('test article')

As expected returns an error

NoMethodError: undefined method `search' for #<Array:0x9aa207c>

But when I do this

Category.first.articles.search('test article')

returns a set of records!

This prompts a check on the classes of

 Article.all

and

Category.first.articles

both returning the Array class.

Obviously, Article's class method 'search' is being inducted in run time and prompting a 'correct' return of records when accessed through its association (Category) but behaving otherwise when accessed by the class itself (Article).

So, What's happening?


Solution

  • This is because when you perform .all the query is actually executed, so the object returned will be a basic array. However, when you perform Category.first, it will return a Category object, then articles is actually making use of ActiveRecord::Reflection::AssociationReflection and doing an extension of an Array. For example, in rails c try:

    Category.first.articles.ancestors
    

    vs

    Category.first.articles.all.ancestors #throws an error
    

    The second one throws an error, because at this point the object is just a simple Array. The first, however, is made up of something like this:

    Article(...), ActiveRecord::Base, ActiveRecord::Reflection, Object (and so on)
    

    As another example, try this:

    a = Category.first.articles; ObjectSpace.each_object(Class).select {|k| a < k }
    #=> [Object, BasicObject, ActiveRecord::Base] 
    

    You can see that although it looks like an Array, it's inheriting those other classes.