views:

1452

answers:

3

I'm wondering what the easiest/most elegant way of selecting attributes from join models in has_many :through associations is.

Lets say we have Items, Catalogs, and CatalogItems with the following Item class:

class Item < ActiveRecord::Base
      has_many :catalog_items
      has_many :catalogs, :through => :catalog_items
end

Additionally, lets say that CatalogueItems has a position attribute and that there is only one CatalogueItem between any catalog and any item.

The most obvious but slightly frustrating way to retrieve the position attribute is:

@item         = Item.find(4)
@catalog      = @item.catalogs.first
@cat_item     = @item.catalog_items.first(:conditions => {:catalog_id => @catalog.id})
position      = @cat_item.position

This is annoying because it seems that we should be able to do @item.catalogs.first.position since we have completely specified which position we want: the one that corresponds to the first of @item's catalogs.

The only way I've found to get this is:

class Item < ActiveRecord::Base
      has_many :catalog_items
      has_many :catalogs, :through => :catalog_items, :select => "catalogue_items.position, catalogs.*"
end

Now I can do Item.catalogs.first.position. However, this seems like a bit of a hack - I'm adding an extra attribute onto a Catalog instance. It also opens up the possibility of trying to use a view in two different situations where I populate @catalogs with a Catalog.find or with a @item.catalogs. In one case, the position will be there, and in the other, it won't.

Does anyone have a good solution to this?

Thanks.

A: 

You can do something like this:

# which is basically same as your "frustrating way" of doing it
@item.catalog_items.find_by_catalogue_id(@item.catalogs.first.id).position

Or you can wrap it into in an instance method of the Item model:

def position_in_first_catalogue
  self.catalog_items.find_by_catalogue_id(self.catalogs.first.id).position
end

and then just call it like this:

@item.position_in_first_catalogue
Milan Novota
The instance method works, but it still just seems like there should be a better way. I didn't specifically want the first catalog, I was just using it as an example. I guess you could do @item.position_in_catalog(catalog_id).
arjun
Yes, it's really easy to rewrite it for @otem.position_in_catalog(catalog). However, I don't see any other way how you could implement what you want.
Milan Novota
@item.catalogs.first.position <-- this doesn't specify any position at all. With @item.catalogs you just specify a subset of all catalogs, from which you then select the first one. So, what you end up with is just a catalog, which is agnostic about the way you obtained it.
Milan Novota
You didn't notice the :select statement added in the second example. It makes it such that if you pull the catalogs for an item, it will pull the position along with it. It seems like the most convenient way, but it also seems dangerous.
arjun
You don't need to call self there! `catalog_items.find_by_catalogue_id` will do.
Ryan Bigg
A: 

You should be able to do @catalog.catalog_item.position if you provide the other end of the association.

class Catalog < ActiveRecord::Base
  belongs_to :catalog_item
end

Now you can do Catalog.first.catalog_item.position.

Samuel
A catalog has_many catalog_items and has_many items through catalog_items, so this isn't quite what I was looking for..thanks though.
arjun
This will not work for the many-to-many relationship expressed by has_many :through.
mercilor
A: 

Why don't You just

@item = Item.find(4)
position = @item.catalog_items.first.position

why do you go through catalogs? It doesn't make any sense to me since you are looking for first ANY catalog!?

aivarsak
I'm not sure what you mean by "first ANY catalog." Anyways, I'm not necesarily looking for the first catalog - that was just an example. Say that an Item has 50 catalogs and I want the item's position in catalog #6823..
arjun
I mean, seems like You need just first position from catalog_items for current item and catalogs are needless for this query.
aivarsak
You don't know WHICH position you want from catalog_items. If an item belongs to 50 catalogs and you want it's position in the 47th catalog (but don't know what order the catalogs are in) then you need the catalog.
arjun