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 :)