Undesireable lazy loading


#1

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


#2

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.


#3

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


#4

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.


#5

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 :slight_smile:

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 :wink: