Does writing has_many extensions for ActiveRecord requery the database?
If I have something like this:
class Post < ActiveRecord::Base
has_many :comments, :as => :commentable do
def approved
find(:all, :conditions => {:approved => true})
end
end
end
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
end
...when I do this, I get 2 hits to the database (not including finding the post :p):
post = Post.first
post.comments #=> [Comment1, Comment2...]
post.comments开发者_C百科.approved #=> [Comment1, Comment7...]
It seems like it should just filter the current comments array in memory, no? Is it doing that? Reason I ask is because the console shows SELECT * FROM ...
on post.comments.approved
, even though I already called post.comments
. Shouldn't this be better optimized in ActiveRecord?
The AR executes a new query for any finder calls inside a association extension method.
You can refer to the cached result set by using self
.
has_many :comments, :as => :commentable do
def approved
# uses the cached result set
self.select{|c| c.approved == true}
end
end
It's optional, as in some cases you might only want to load the associated objects when needed. If you want them all loaded into memory, you need to explicitly declare the objects you'd like included with the initial query, using the :include flag. Example:
post = Post.find(:first, :include => :comment)
You might have to rewrite your extension to take advantage of the feature... an approach would be to change your "approved" function to iterate through the comments array attached to each post, and return a new array with the nonapproved comments filtered out. The "find" you have defined explicitly goes back to the database.
If your query is really that simple, then what you want is a named scope:
class Comment
named_scope :approved, :conditions => {:approved => true}
end
Then, you can do:
@post.comments.approved.count #=> 1 DB hit!
@post.comments.count #=> Another DB hit - can't reuse same scope
Look at #scope (#named_scope in Rails 2.3).
精彩评论