开发者

Why/how is it possible to call class methods of a model on a collection/array returned by a relationship?

开发者 https://www.devze.com 2023-02-02 11:26 出处:网络
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:

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

0

精彩评论

暂无评论...
验证码 换一张
取 消