Hello,
In summary, I’m trying to find the best solution for eager loading one
association based on the current_user passed from the controller.
This is the necessary models code:
class User < ActiveRecord::Base
has_many :posts
has_many :readerships
end
class Post < ActiveRecord::Base
belongs_to :author, :class_name => ‘User’
has_many :readerships
end
class Readership < ActiveRecord::Base
belongs_to :post
belongs_to :reader, :class_name => ‘User’
end
Next I have action in the posts controller that lists 50 most recent
posts plus I want to show whether the post was read by the current_user
(logged in user) or not using the ‘NEW’ icon (each readership includes
read_at attribute that should be also displayed when the post was read
by the current_user).
The thing is that I want to prevent additional SQL queries to get the
current_user’s readership for each post (resulting in 50 more queries in
the view).
I cannot use this:
@posts = Post.find :all,
:include => [ :readerships, :author ],
:conditions => “readerships.reader_id IS NULL OR readerships.reader_id
= #{current_user.id}”,
:order => ‘posts.created_at DESC’,
:limit => 50
because this would exclude the posts that were read by some other users
except the current_user. If no user read the post, reader_id for that
joined row would be NULL so it would be included which is good. But if
some other user read the post (and not the current_user) the joined row
would not be included because reader_id in the joined row would not be
NULL nor current_user.id
It seems like I cannot use this:
@posts = Post.find :all,
:include => [ :readerships, :author ],
:joins => “LEFT OUTER JOIN readerships ON (readerships.post_id =
posts.id AND readerships.reader_id=#{current_user.id})”,
:order => ‘posts.created_at DESC’,
:limit => 50
at first I thought that it is possible to override include join with
custom :join but after reading about Table Aliases in AR
(http://api.rubyonrails.com/classes/ActiveRecord/Associations/ClassMethods.html)
it seems like the custom join takes precedence but it doesn’t override
the join generated by :include.
I know that I can omit the :include and just use 2 custom joins (one for
author and one for readership) but that would not map the results into
model objects automatically. Plus I hope there is some elegant solution
for this.
IMO the ideal solution for this should be something like this (it is not
valid code, just an example):
@posts = Post.find :all,
:include => [ :readerships.by_reader(current_user), :author ],
:order => ‘posts.created_at DESC’,
:limit => 50
:readerships.by_reader would be defined in the Post model as the
readerships extension method accepting reader and using it’s id as the
join condition ( e.g. ON (readerships.post_id = #{id} AND
readerships.reader_id=#{reader.id} )
Any ideas?