rails named_scope ignores eager loading
Two models (Rails 2.3.8):
- User; username & disabled properties; User has_one :profile
- Profile; full_name & hidden properties
I am trying to create a named_scope that eliminate the disabled=1 and hidden=1 User-Profiles. 开发者_运维技巧 The User model is usually used in conjunction with the Profile model, so I attempt to eager-load the Profile model (:include => :profile).
I created a named_scope on the User model called 'visible':
named_scope :visible, {
:joins => "INNER JOIN profiles ON users.id=profiles.user_id",
:conditions => ["users.disabled = ? AND profiles.hidden = ?", false, false]
}
I've noticed that when I use the named_scope in a query, the eager-loading instruction is ignored.
Variation 1 - User model only:
# UserController
@users = User.find(:all)
# User's Index view
<% for user in @users %>
<p><%= user.username %></p>
<% end %>
# generates a single query:
SELECT * FROM `users`
Variation 2 - use Profile model in view; lazy load Profile model
# UserController
@users = User.find(:all)
# User's Index view
<% for user in @users %>
<p><%= user.username %></p>
<p><%= user.profile.full_name %></p>
<% end %>
# generates multiple queries:
SELECT * FROM `profiles` WHERE (`profiles`.user_id = 1) ORDER BY full_name ASC LIMIT 1
SHOW FIELDS FROM `profiles`
SELECT * FROM `profiles` WHERE (`profiles`.user_id = 2) ORDER BY full_name ASC LIMIT 1
SELECT * FROM `profiles` WHERE (`profiles`.user_id = 3) ORDER BY full_name ASC LIMIT 1
SELECT * FROM `profiles` WHERE (`profiles`.user_id = 4) ORDER BY full_name ASC LIMIT 1
SELECT * FROM `profiles` WHERE (`profiles`.user_id = 5) ORDER BY full_name ASC LIMIT 1
SELECT * FROM `profiles` WHERE (`profiles`.user_id = 6) ORDER BY full_name ASC LIMIT 1
Variation 3 - eager load Profile model
# UserController
@users = User.find(:all, :include => :profile)
#view; no changes
# two queries
SELECT * FROM `users`
SELECT `profiles`.* FROM `profiles` WHERE (`profiles`.user_id IN (1,2,3,4,5,6))
Variation 4 - use name_scope, including eager-loading instruction
#UserConroller
@users = User.visible(:include => :profile)
#view; no changes
# generates multiple queries
SELECT `users`.* FROM `users` INNER JOIN profiles ON users.id=profiles.user_id WHERE (users.disabled = 0 AND profiles.hidden = 0)
SELECT * FROM `profiles` WHERE (`profiles`.user_id = 1) ORDER BY full_name ASC LIMIT 1
SELECT * FROM `profiles` WHERE (`profiles`.user_id = 2) ORDER BY full_name ASC LIMIT 1
SELECT * FROM `profiles` WHERE (`profiles`.user_id = 3) ORDER BY full_name ASC LIMIT 1
SELECT * FROM `profiles` WHERE (`profiles`.user_id = 4) ORDER BY full_name ASC LIMIT 1
Variation 4 does return the correct number of records, but also appears to be ignoring the eager-loading instruction.
Is this an issue with cross-model named scopes? Perhaps I'm not using it correctly.
Is this sort of situation handled better by Rails 3?
From railsapi.com:
Eager loading of associations
[...] Since only one table is loaded at a time, conditions or orders cannot reference tables other than the main one. If this is the case Active Record falls back to the previously used LEFT OUTER JOIN based strategy. For example
Post.find(:all, :include => [ :author, :comments ], :conditions => ['comments.approved = ?', true])
will result in a single SQL query with joins along the lines of: LEFT OUTER JOIN comments ON comments.post_id = posts.id and LEFT OUTER JOIN authors ON authors.id = posts.author_id.
I believe this answers your question... there's no eager loading in "variation #4" because you references profiles
table on your named_scope
.
I believe the following may give you what you are looking for:
@users = User.visible.scoped(:include => :profile)
This did the trick for me, but I'm not joining with other tables in the definition of my named scope.
Jim Benton provides an elegant way of adding this to ActiveRecord on his blog: http://autonomousmachine.com/posts/2009/10/28/add-a-scope-for-easier-eager-loading
精彩评论