How can an ActiveRecord::Relation object call class methods?
class Project < ActiveRecord::Base
has_many :tasks
end
class Task < ActiveRecord::Base
Here in ActiveRecord::Relation, Relation is representing whole table and your class Post is map with table,
So ActiveRecord::Relation is array or single record it can access class method.
For anyone struggling to actually access a filtered list of records inside a class method you can simply call all which will return you the list you are expecting rather than all of the records in that table which is accessed with relation.
There's not much documentation on the application on class methods to ActiveRecord::Relation objects, but we can understand this behavior by taking a look at how ActiveRecord scopes work.
First, a Rails model scope will return an ActiveRecord::Relation object. From the docs:
Class methods on your model are automatically available on scopes. Assuming the following setup:
class Article < ActiveRecord::Base
scope :published, -> { where(published: true) }
scope :featured, -> { where(featured: true) }
def self.latest_article
order('published_at desc').first
end
def self.titles
pluck(:title)
end
end
First, invoking a scope returns an ActiveRecord::Relation object:
Article.published.class
#=> ActiveRecord::Relation
Article.featured.class
#=> ActiveRecord::Relation
Then, you can operate on the ActiveRecord::Relation object using the respective model's class methods:
Article.published.featured.latest_article
Article.featured.titles
It's a bit of a roundabout way of understanding the relationship between class methods and ActiveRecord::Relation, but the gist is this:
ActiveRecord::Relation objectsActiveRecord::Relation objects have access to class methodsIt's extremely easy to explore. You just do so:
class Project < ActiveRecord::Base
has_many :tasks
end
class Task < ActiveRecord::Base
belongs_to :project
def self.initial_tasks #class methods
1 / 0
end
end
Then call Project.first.tasks.initial_tasks and you get:
Division by zero
...
.../gems/activerecord-4.1.0/lib/active_record/relation/delegation.rb:70:in `block in re
.../gems/activerecord-4.1.0/lib/active_record/associations/collection_proxy.rb:872:in `
.../gems/activerecord-4.1.0/lib/active_record/relation.rb:286:in `scoping'",
.../gems/activerecord-4.1.0/lib/active_record/associations/collection_proxy.rb:872:in `
.../gems/activerecord-4.1.0/lib/active_record/relation/delegation.rb:70:in `initial_tasks'",
And that's all you need basically. Easy to explore but not so easy to understand.
Now I'll explain what that means.
When you call Project#tasks method it does not return you ActiveRecord::Relation object. Actually it returns you an instance of runtime-created class named Task::ActiveRecord_Associations_CollectionProxy inherited from ActiveRecord::Associations::CollextionProxy that in turn inherited from ActiveRecord::Relation. This runtime-created class is linked with Task class and contains dynamically-defined (via method_missing) proxy-methods that delegate calls to Task class methods and merge association scope with class-defined scope returned by class-level methods.
How it works (really non-trivial):
DelegateCache has DelegateCache.inherited callback that defines @relation_delegate_cache attribute every time you inherit ActiveRecord::Base. It means all AR::Base descendant classes will have such attribute. The callback calls DelegateCache#initialize_relation_delegate_cache method which in order fills cache attribute with runtime-created classes:
[
ActiveRecord::Relation,
ActiveRecord::Associations::CollectionProxy,
ActiveRecord::AssociationRelation
].each do |klass|
delegate = Class.new(klass) {
include ClassSpecificRelation
}
const_set klass.name.gsub('::', '_'), delegate
cache[klass] = delegate
end
Here these classes get unusual names a-la Task::ActiveRecord_Associations_CollectionProxy
mentioned earlier.
#initial_tasks on Project.tasks). On such call it dynamically defines new runtime-class instance methods that delegate to class-level methods. Now you have Task class linked to Task::ActiveRecord_Associations_CollectionProxy class containing all instance-level methods that proxy calls to Task class-level methods get scope result and merge it with current association scope (here).That's how AR prefers dynamically-defined methods on runtime-created classes over using inefficient method_missing calls on ActiveRecord::Relation.
I think it's OK if you do not understand all this stuff. Just call class-level methods on associations :)