How can an ActiveRecord::Relation object call class methods
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:
- By definition, model scopes return
ActiveRecord::Relation
objects - By definition, scopes have access to class methods
- Therefore,
ActiveRecord::Relation
objects have access to class methods
Calling Class method via Rails ActiveRecord Association
To answer your question directly,
Comment.scope_attributes
Will return a hash of those attributes the current scope on Comment would set. You can test the effect an association has on this like so
I'm not sure I'd use this though - it seems a little odd to have a class method that can only be invoked on scopes of a particular form.
Rails method call on active record relation
You should use active record scope for yo
class DriverInvoice
scope :for_company, -> (company) { where(driver_id: current_affilate_company.drivers.pluck(:id))}
end
@paid_drivers = DriverInvoice.for_company(current_affilate_company)
Then you can use other filters by defining other scope
s or using where
chain
method on activerecord::relation
How can i make this method return the exact same object it begun with?
by_post
method operates on the class (User
), not on the relation, thus what you get is actually the "object it begun with" :)
So in order to return a relation from the by_post
method, you'd want to load it:
self.by_post(condition)
return self.all if condition.blank? # self.scoped for Rails 3
##something
end
How did the class method call on ActiveRecord::Relation takes a context?
My initial answer was the right one: it can't work...
I guess I finally convinced myself because docs should rule...
Well, I think what's important in the doc is the idea: you can chain scopes with class methods.
But the implementation of the class method given in example is definitely wrong.
Calling a class method through an object instance
This works because scopes of ActiveRecord relations are merged. It's not that find_incomplete
is running on individual task instances.
@project.tasks
creates an ActiveRecord scope of the tasks for that project instance and then that scope is still in effect when your find_incomplete
method is called.
Take a look at the documentation here: http://guides.rubyonrails.org/active_record_querying.html#scopes
Your find_incomplete
method works in the same way as the self.published
example in the docs.
Think of the underlying SQL query that would run:
@project.tasks
would create a where
condition like SELECT * FROM projects WHERE project_id = <project_id>
find_all_by_complete
then merges in an and
condition for complete = 0
I think the other piece of the puzzle that might help is that @project.tasks
is not just a simple array array of Task
objects, although it will have been converted to such if you type project.tasks
in the Rails console. project.tasks
is actually an Active Record relation object (or more precisely a proxy)
There are a number of reasons and benefits for this but the main 2 are that it allows chaining and it allows the underlying query to be run on demand only if/when needed.
The relation has a sequence of rules for how method calls are delegated, one of which is to call class methods on the class of the associated objects. (the relation knows that it's a relation to Tasks
)
So you are correct when you wrote tasks.find_incomplete
is still equal to Task.find_incomplete
except that when find_incomplete
is called through project.tasks
a scope narrowing down to the project_id
is already in effect.
Related Topics
Redefining a Single Ruby Method on a Single Instance with a Lambda
Guard with Rspec on Rails 4 Giving a Lot of Warnings
Looping Through an Array with Step
Has_Many :Through with Counter_Cache
Why Do People Say That Ruby Is Slow
Differencebetween Send_Data and Send_File in Ruby on Rails
Ruby Convert Array to Nested Hash
What Are the Differences Between "Private", "Public", and "Protected Methods"
Understanding How Establish_Connection Works in Activerecord
How Can an Activerecord::Relation Object Call Class Methods
Strange Inability to Require Config/Boot After Upgrading to Ruby 1.9.2
How to Automate Chrome Request Blocking Using Selenium-Webdriver for Ruby
Irb History Not Working with Ruby 2.3.0
Ruby: Accessing Rake Task from a Gem Without Rails
How to Run a Single Rspec Test
"Which in Ruby": Checking If Program Exists in $Path from Ruby
How to Set Up Simple Http Authentication for a Ruby on Rails App on Heroku