Why/how is it possible to call class methods of a model on a collection/array returned by a relationship?
So I came across an interesting bit of code today and it caught my attention. I didn't think this was possible. The line of code was:
@post.comments.approved_comments
Looks alright, right? But what is really happening is that the approved_comments
method is being called on an instance of an Array
object that is returned by a has_many
relationship (i.e., a post has many comments). What's even more interesting is that approved_comments
is a class method defined on the Comment
model.
What has me a bit perplexed is I thought you can't chain off a relationship, not directly at least because a relationship returns an Array
, not an ActiveRecord::Relation
object. I've asked a question related to this before and the answer that was given to me is that you can chain off a relationship by using the scoped
method or one of the many methods that are added to array because of the relationship (i.e., Section 4.3.1 Methods Added by has_many).
What also has me perplexed is that approved_comments
is a method that we wrote; it is not a method added by the has_many
relationship method. So how is it possible that I am able to access it? Even more confusing, how is it that I am able t开发者_如何学编程o access it off an Array
object?
There is, of course, some magic going on. I'm wondering if someone could point me to some documentation or if any experts have an answer to this. Not a big issue because it seems to work rather nicely, but I'd really like to know how just to educate myself.
Thanks for the help in advance!
Code
Post
class Post
has_many :comments
end
Comment
class Comment
def self.approved_comments
where(:approved => true)
end
end
Usage
@post.comments.approved_comments
It has to do with ActiveRecord's way of loading a query. It doesn't execute until you call a method on the object. As a result, it can build the entire query more efficiently. HOWEVER, what you are trying to do is more easily accomplished using a scope:
class Post
has_many :comments
end
class Comment
scope :approved_comments, where(:approved => true)
end
Now you can do Comment.approved_comments to get ALL approved comments or @post.comments.approved_comments to get approved comments for a Post.
Further more, a scope can take optional parameters. If you wanted to get all posts approved since a User's last visit (for example):
class Comment
scope :approved_since, lambda{ |date| where(['approved_at > ?', date]) }
end
Then you could call: @post.comments.approved_since(@user.last_login)
for example
Look at the Rails ActiveRecord Named Scope for more information.
This one here explains exactly what you're asking http://railscasts.com/episodes/5-using-with-scope
精彩评论