Topic: Model Association: Brain Failure

Hello all.

If someone can help with my models, I'd be very greatful. I get the feeling I need a cunning association that I don't have.

Ok, to simplify: I have a table of ITEMS which live in CATEGORIES. USERS log into the site, and may possible have FAVOURITE_ITEMS. That's about it.

So:

class Item < ActiveRecord::Base
  belongs_to :category
  has_many :favourite_items
end

class Category < ActiveRecord::Base
  has_many :items
end

class User < ActiveRecord::Base
  has_many :favourite_items
end

class Favourite_Items < ActiveRecord::Base
  belongs_to :user
  belongs_to :item
end


Now, my user is all logged in, and has selected a category, so in my controller I have:

@u = User.find(1) # Really comes from the session
@c = Category.find(1)
@i = @c.items

And in my view, I simply use a partial to display the items.

<%= render(:partial => "items", :collection => @i) %>

So far so good.

Now, what I want to do is put a nice star (or something!) next to the users favourite items -- ideally by using lovely railsish <% if @i.favourite_for_user?(@u.id) %>, but if I placed this code into the Item model, how would I avoid doing 1 SQL query for each item?

I thought about trying to :include => 'favourite_items' when finding the items in my controller code, but I would then include everyone's favourite items, not just the currently logged in user.

Hopefully I'm making some kind of sense. Any pointers will be greatly appreciated.

NeilS.

Re: Model Association: Brain Failure

You should be able to add the :include to the User find call (not to the categories one)

My RoR journey  -- thoughts on learning RoR and lessons learned in applying TDD and agile practices.

Re: Model Association: Brain Failure

<% if @u.favorite_items.include?(@i) %>

I think that the ActiveRecord caching will minimize SQL calls in that case, only reading the favorite_items the first time around. Can anyone else confirm this?

However your models are not right. Best way would be to rename your favorite_items table as items_users. Then if you use has_and_belongs_to_many, you don't need any explicit model for the join table.

Then in your User, put
has_many_and_belongs_to_many :items

and vice versa in your items.

This way you don't need any primary key in the join table. If you really want to rename the User.items as User.favorite_items, you need to do:

class User < ActiveRecord::Base
  has_and_belongs_to_many :favorite_items, :join_table => "items_users", :class_name => "Item"
end

and in Items

has_and_belongs_to_many :users, :join_table => "items_users"

... I think. I am not sure that :include is necessary in this case. You shouldn't need to do an inner join on the association table for every time you hit the database, just to test membership. Wouldn't that require explicitly specifying table names in the conditions to check identity of the items?

Last edited by evan (2006-07-06 13:37:35)

Re: Model Association: Brain Failure

Evan,

Brilliant - thanks a lot for your help. I've got that working now.

For the record, I kept the table called 'favourite_items' (otherwise in the future, I could really see myself looking at a 'items_users' table, and thinking "Huh?"), and set the :join_table manually in my models. Everything worked like a charm.

For the record, ActiveRecord does cache the query results - I see only one query on 'favourite_images' in my log, which is great.

Possibly oddly, I did try adding :include => 'users' when I retrieve my items to see if I could lose that one extra query, but it still seemed to do it regardless ... oh well, I'm not that worried as it's only one query.

Thanks again,
NeilS.