views:

162

answers:

2
A: 

They way I see it is that you don't have a many to many relationship to the products. You have it to the product of that particulary variety (if any). So in order to keep track of it, I would use an extra model. That way you would be able to keep the relationship between products and varieties as well.

The models:

class User < ActiveRecord::Base
  has_many :user_products, :dependent => :destroy
  has_many :products, :through => :user_products
  has_many :varieties, :through => :user_products
end

class UserProduct < ActiveRecord::Base
  belongs_to :user
  belongs_to :product
  belongs_to :variety
end

class Product < ActiveRecord::Base
  has_many :varieties
end

class Variety < ActiveRecord::Base
  belongs_to :product
end

The controller:

class UserProductsController < ApplicationController
    before_filter do 
        @user = User.find(params['user_id'])
    end


    def create
      product = Product.find(params['product_id'])
      variety = Variety.find(params['variety_id']) if params['variety_id']

      @user_product = UserProduct.new
      @user_product.user = user
      @user_product.product = product
      @user_product.variety = variety
      @user_product.save
    end

    def destroy
        # Either do something like this:
        conditions = {:user_id => @user.id}
        conditions[:product_id] = params[:product_id] if params[:product_id]
        conditions[:variety_id] = params[:variety_id] if params[:variety_id]

        UserProduct.destroy_all conditions
    end
end

The views: If you are not interested in grouping the varieties into the different products and just put them as a list it is enough with this:

# users/show.html.erb
<%= render @user.user_products %>

# user_products/_user_product.html.erb
<%= h user_product.product.name %> 
<%= h user_product.variety.name if user_product.variety %>

Otherwise something a bit more complicated have to be added. Some of it might be possible to put in the model and controller by using association proxies, but I can't help you there

# users/show
<% for product in @user.products do %>
    <%= product.name %>
    <%= render :partial => 'variety', 
        :collection => @user.varieties.find_by_product_id(product.id) %>
<% end %>

# users/_variety
<%= variety.name %>

Splitting it up in partials is of course not necessary (and in this example, maybe a bit ridiculous), but it helps in separating the different parts, especially if you want to add more things to display.

And to answer your questions:

  1. Since UserProduct is a join model, you don't really need to keep track of it's individual id. You have the User, the Product and the Variety (in those cases it exists). That's all you need, that combination is unique and thus, you can find the record for deletion. Also, a join model should never be edited (provided that you don't have more attributes in it than these fields), It is either create or delete, since all it do is keep the associations.
  2. Since it is basically a viewing thing it is best to place it in the views. You could of course use the controller or the model to collect all the products and the varieties beforehand by doing something like this:

Moving all of the logic to the controller and the model is probably not a good idea since they are not supposed to know (or care) how you want to display the data. So it is only a matter of preparing it enough.

@products_for_user = []
@user.products.each do |product|
 collected_product = {:product => product, 
                      :varieties => @user.varieties.find_by_product_id(product.id)}
  @products_for_user << collected_product
end
Jimmy Stenke
Thanks for the follow up, Jimmy. Very helpful.
MikeH
A: 

MikeH

You might just have a typo here, @user.user_product should be @user.user_products

When I try the code listed below in the show view, I get the following error: undefined method `user_product' for #:

<% @user.products.each do |product| %>  
  <% @user.user_product.find_by_product_id(product.id).varieties %>  
<% end %>

Also you might consider a hierarchical type system, where you just have products like "Fuji", "Macintosh", and "Apples".

"Fuji" and "Macintosh" would then have a column "parent_id" set to the id of "Apples".

Just a thought.

ToreyHeinz
Thanks. That was actually a mistype by me in the question. Interesting suggestion about the hierarchical system. I hadn't thought of that. I'll need to look it up to see how it's implemented.
MikeH