How to ? Rails 3 ActiveRecord eager loading and AREL

Hello everyone,

I would like to eager load scoped records to avoid queries executed in a
loop (huge performance impact).

The “scoped” part is what is giving me a hard time.

I’m using Rails3 RC.

Does anyone have any idea how I can do it?

Here is a test case : we have an “Article” and a “Comment” Activerecord
models,

article has many comments
comment belongs to article

Comment has a scope defined like this (app/models/comment.rb) :


def recent
where [“published_at >= ?”, 2.days.ago]
end


In the ArticlesController (app/controllers/comments_controller.rb) :


def index
@articles = Article.includes(:comments)
end


In the view (haml) (app/views/articles/index.html.haml):


  • @articles.each do |article|
    = article.title
    • article.comments.each do |comment|
      = comment.body

this works fine and no query is executed when accessing the comments for
each articles (“includes” call in the controller handled the eager
loading).

But if you try to display only the recent comments for each article :


  • @articles.each do |article|
    = article.title
    • article.comments.recent.each do |recent_comment|
      = recent_comment.body

Now a query is executed for every article in order to fetch it’s recent
articles.

What can I write (presumably in the “include” call) to get the eager
loading to work in this situation ?

Cheers,

M-Ryan

On Aug 5, 9:48 am, M- Ryan [email protected] wrote:

Hello everyone,

I would like to eager load scoped records to avoid queries executed in a
loop (huge performance impact).

The “scoped” part is what is giving me a hard time.

Can you define a scope at class level, instead of defining a method?

scope :recent, where [“published_at >= ?”, 2.days.ago]

Then when you later do

article.comments.recent

the join/include clauses should be chained together first, resulting
in max one query per article.

Jeff

Thanks for your replay Jeff.

Unfortunately, declaring my scope with the “scope” method instead of a
regular method returning an ActiveRecord::Relation doesn’t solve my
problem. (it also creates a new one since “2.days.ago” is only evaluated
when the app loads the classes and not every time the method is called)

I think the includes method should support relations as well as
associations (since associations/scopes/relations are so well integrated
with eachother in rails 3).

That way the following :

@articles = Article.includes(:comments => :recent)

would build the relation in a way that recent comments are eagerly
loaded (instead of complaining that comments have no association named
“recent”)

Cheers,

M-Ryan

On Aug 6, 3:03 am, M- Ryan [email protected] wrote:

Unfortunately, declaring my scope with the “scope” method instead of a
regular method returning an ActiveRecord::Relation doesn’t solve my
problem. (it also creates a new one since “2.days.ago” is only evaluated
when the app loads the classes and not every time the method is called)

He made a mistake. It should be:

scope :recent, lambda { where [“published_at >= ?”, 2.days.ago] }

I think the includes method should support relations as well as
associations (since associations/scopes/relations are so well integrated
with eachother in rails 3).
That way the following :

@articles = Article.includes(:comments => :recent)

Ok, I see. You want to scope the eager-loaded objects. I guess this is
a case where Datamapper’s identity map comes in handy.

I haven’t tried this yet, but you might be able to put add scopes to
the association itself.

has_many :recent_comments, :class_name => “Comment”, :conditions
=> …

Hm, though that won’t do the lazy eval. I checked the code for
has_many. The finder_sql is constructed the old way, not Arel all the
way down.

The compromise I can think of is to extend the collection code so it
loads all the comments in one batch and insert them into your
articles. The other one I can think of is to use AJAX to load the
recent comments and have a dedicated controller handle that. It’s
probably a better design than trying to load a set of articles and
recent comments for all of them, in a single go, then rendering them
to the page.

Ho-Sheng H.