# Eager loading for the readership tracking problem

**URL:** <https://www.ruby-forum.com/t/eager-loading-for-the-readership-tracking-problem/86012>\
**Category:** Rails\
**Created:** [February 19, 2007, 12:10am UTC](https://www.ruby-forum.com/t/eager-loading-for-the-readership-tracking-problem/86012 "2007-02-19T00:10:54Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![msojka](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/b5a626/32.png) [@msojka](https://www.ruby-forum.com/u/msojka)\
**Post date:** [February 19, 2007, 12:10am UTC](https://www.ruby-forum.com/t/eager-loading-for-the-readership-tracking-problem/86012/1 "2007-02-19T00:10:54Z")

</div>

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](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?

---

<div class="post-metadata">

**Author:** ![msojka](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/b5a626/32.png) [@msojka](https://www.ruby-forum.com/u/msojka)\
**Post date:** [February 19, 2007, 11:25am UTC](https://www.ruby-forum.com/t/eager-loading-for-the-readership-tracking-problem/86012/2 "2007-02-19T11:25:12Z")

</div>

Martin Sojka wrote:

> 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

Perhaps something like this would work:

class Post \< ActiveRecord::Base  
cattr\_accessor :current\_user\_id  
has\_one :current\_user\_readership, :class\_name =\> ‘Readership’,  
:conditions =\> ‘readerships.reader\_id =  
#{current\_user\_id}’  
end

Post.current\_user\_id = current\_user.id  
@posts = Post.find :all, :include =\> [:current\_user\_readership,  
:author],  
:order =\> ‘posts.created\_at DESC’, :limit =\>  
50

Otherwise you’ll have to either use the available AR mods that allow  
the writing of custom eager sql, or select the author and readership  
info into the Post models.

–  
We develop, watch us RoR, in numbers too big to ignore.

---

<div class="post-metadata">

**Author:** ![msojka](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/b5a626/32.png) [@msojka](https://www.ruby-forum.com/u/msojka)\
**Post date:** [February 19, 2007, 11:31pm UTC](https://www.ruby-forum.com/t/eager-loading-for-the-readership-tracking-problem/86012/3 "2007-02-19T23:31:17Z")

</div>

> Perhaps something like this would work:
> 
> class Post \< ActiveRecord::Base  
> cattr\_accessor :current\_user\_id  
> has\_one :current\_user\_readership, :class\_name =\> ‘Readership’,  
> :conditions =\> “readerships.reader\_id =  
> #{current\_user\_id}”  
> end

Mark, thanks for great idea. I’ve started to play with this in the  
console. It works great when I hardcode the current\_user\_id into the  
:conditions (like :conditions =\> “readerships.reader\_id = \> 9”).

But so far I had no luck to use the current\_user\_id in the condition. It  
always results in blank space there, eventually resulting in bad SQL -  
SELECT \* FROM readerships WHERE … AND (readerships.reader\_id = ).

In the console, Post.current\_user\_id = 9 assigns the class variable  
well. I then can see it when typing:

Post.current\_user\_id  
=\> 9  
p = Post.find :first  
p.current\_user\_id  
=\> 9

But calling p.current\_user\_readership results in the invalid SQL  
described above.

Somehow, the current\_user\_id is not passed into the :conditions  
statement.

Otherwise this solution would be great since I can assign  
current\_user\_id to Post before calling the find to eager load just the  
current user readership. I just need to find the way to pass that  
variable into the has\_one :conditions.

Anybody solved this before? Thanks!

---

<div class="post-metadata">

**Author:** ![msojka](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/b5a626/32.png) [@msojka](https://www.ruby-forum.com/u/msojka)\
**Post date:** [February 20, 2007, 2:24am UTC](https://www.ruby-forum.com/t/eager-loading-for-the-readership-tracking-problem/86012/4 "2007-02-20T02:24:22Z")

</div>

Martin Sojka wrote:

> console. It works great when I hardcode the current\_user\_id into the  
> =\> 9  
> Otherwise this solution would be great since I can assign  
> current\_user\_id to Post before calling the find to eager load just the  
> current user readership. I just need to find the way to pass that  
> variable into the has\_one :conditions.
> 
> Anybody solved this before? Thanks!

Sorry, it looks like the sql interpolation is not done on the  
model class, perhaps it’s on the association class itself. If  
that’s the case you can’t use the accessor-setting approach  
since has\_one does not support association extensions.

However I’ve posted a before about setting the conditions for  
an association dynamically:

[http://groups.google.com/group/rubyonrails-talk/browse\_thread/thread/7f7f715d917b9faa](http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/7f7f715d917b9faa)

That should work:

class Post \< ActiveRecord::Base  
has\_one :current\_user\_readership, :class\_name =\> ‘Readership’

# def self.current\_user=(user) reflect\_on\_association(:current\_user\_readership).options[:conditions]

```
      "readerships.reader_id = #{user.id}"

```

end  
end

Post.current\_user = current\_user  
@posts = Post.find :all, :include =\> [:current\_user\_readership,  
:author],  
:order =\> ‘posts.created\_at DESC’, :limit =\>  
50

–  
We develop, watch us RoR, in numbers too big to ignore.

---

<div class="post-metadata">

**Author:** ![msojka](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/b5a626/32.png) [@msojka](https://www.ruby-forum.com/u/msojka)\
**Post date:** [February 20, 2007, 11:08pm UTC](https://www.ruby-forum.com/t/eager-loading-for-the-readership-tracking-problem/86012/5 "2007-02-20T23:08:25Z")

</div>

Thanks Mark! This works like expected. Now I can finish it 😉
