Forum: Ruby on Rails undesireable lazy loading

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Cb1614ffbd3293d667120265af0852be?d=identicon&s=25 Piotr Chmolowski (Guest)
on 2006-05-13 12:01
Hi,

My work with Rails has been fairly straightforward, until yesterday...
I've encountered rather a peculiar problem that I'm not able to solve
myself as a rails-newbie.

The model code looks like this:

class Link < ActiveRecord::Base
  has_one :user_vote
end

class UserVote < ActiveRecord::Base
  belongs_to :user
  belongs_to :link
end

I have a SQL query that is responsible for pulling out all links along
with votes from a database:

links = Link.find_by_sql(
  ["SELECT * FROM links LEFT JOIN user_votes ON links.id =
user_votes.link_id" +
   " AND user_votes.user_id = ?", session[:user_id]]
)

If a user hadn't voted for a given link yet, an empty record is returned
(NULL).  The problem is that when trying to call that unexisting
associated object (link.user_vote) Rails attempts to retrieve it from
the database. This can result in getting a vote that belongs to another
user.

Is it possible to disable the lazy-loading feature for that particular
association?

Is there any other way to perform the above-mentioned JOIN without using
find_by_sql?

thanks in advance,
--
Piotr Chmolowski
98bc21f9bba2819c01ffe40bff8dc084?d=identicon&s=25 Tom Ten Thij (Guest)
on 2006-05-13 13:49
(Received via mailing list)
> Is there any other way to perform the above-mentioned JOIN without using
> find_by_sql?


In your user model:

class User < ActiveRecord::Base
  has_many :user_votes
end

Then you can say:

User.find(session[:user_id]).user_votes

Tom.
Cb1614ffbd3293d667120265af0852be?d=identicon&s=25 Piotr Chmolowski (Guest)
on 2006-05-13 14:52
Hi,

> class User < ActiveRecord::Base
>   has_many :user_votes
> end
>
> Then you can say:
>
> User.find(session[:user_id]).user_votes

This is not what I'm trying to achieve. I need a to pull out a list of
links and check if the logged-in user has already voted for a given
link. Using has_many would require n + 1 queries for n links. That's way
too much. Besides, when specyfying the has_many association I'd need to
include session[:user_id] in :conditions, which is AFAIK unfeasible, as
the session isn't available in model:

class Link
  has_many :user_votes, :conditions => ["user_id = ?",
session[:user_id]]
end
Fb23bc8cd4030c526b0689276b34c8bd?d=identicon&s=25 Bryan Duxbury (bryanduxbury)
on 2006-05-13 15:23
> This is not what I'm trying to achieve. I need a to pull out a list of
> links and check if the logged-in user has already voted for a given
> link. Using has_many would require n + 1 queries for n links.

You can do User.find(session[:user_id], :include =>
:user_vote).user_votes to retrieve all the user votes in one query.

> That's way
> too much. Besides, when specyfying the has_many association I'd need to
> include session[:user_id] in :conditions, which is AFAIK unfeasible, as
> the session isn't available in model:
>
> class Link
>   has_many :user_votes, :conditions => ["user_id = ?",
> session[:user_id]]
> end

You wouldn't need to do this. Since you're looking for votes cast on
links for a given user, just do @user.user_votes. Any links not voted on
won't have vote objects, so there won't be any nil anywhere.

This should probably do what you want:

User.find(session[:user_id]).user_votes.find(:all, :include => :link)

then just iterate through the list. Should only be 2 queries.
Cb1614ffbd3293d667120265af0852be?d=identicon&s=25 Piotr Chmolowski (Guest)
on 2006-05-13 16:34
> This should probably do what you want:
>
> User.find(session[:user_id]).user_votes.find(:all, :include => :link)
>
> then just iterate through the list. Should only be 2 queries.

OK, I guess I haven't explained it clearly enough. My site is pretty
similar to digg.com - I have a list of links, that everybody can vote
on. Users can vote only after logging in. Only 1 vote is allowed for 1
link. The code you suggested wouldn't be suitable here, as it would give
me only the links that the user had already voted on, and I want them
all :-)

Basically, the SQL I've posted in my first message does exactly what I
want, but Rails is messing up the result trying to load the vote again,
which can result in getting another user's vote.

My link table looks like this:

id
title
url
description

The votes table:

id
link_id
user_id
voted_at

The SELECT * FROM links LEFT JOIN user_votes ON links.id =
user_votes.link_id AND user_votes.user_id = session[:user_id]

lists all links. If the current user hadn't voted for a given link yet,
mysql returns an empty record. When iterating through that list of links
I check if the user can vote for a given link:

<% unless link.user_vote %>
  <%= link_to "vote!", :action => "vote" %>
<% end %>

Rather that returning nil here, Rails performs the following (or
similar) SQL query:

SELECT * FROM user_votes WHERE link_id = X LIMIT 1

English is still my Achilles heel, so forgive me for my grammar and the
way of explaining things in general ;-)
This topic is locked and can not be replied to.