Topic: has many throug multiple join model

Hello
At first, sorry form my english. I'm spending last two days trying to figure this out.
I have 5 tables. Tables categories, products, properties and join model tables category_properties, product_properties.

Structure of join model tables

  create_table "product_properties", :force => true, :options => 'ENGINE=MyISAM' do |t|
    t.column "product_id", :integer, :default => 0
    t.column "property_id", :integer, :default => 0
    t.column "property_value", :string, :default => 0
   end
 
   create_table "category_properties", :force => true, :options => 'ENGINE=MyISAM' do |t|
    t.column "category_id", :integer
    t.column "property_id", :integer
    t.column "property_unit", :string
   end

Models
class Category < ActiveRecord::Base
  acts_as_tree :order => "name"
 
  has_many :products
  has_many :category_properties, :dependent => :destroy
  has_many :properties, :through => :category_properties
 
def property_ids=(property_ids)
 
  category_properties.each do |cat_prop|
   cat_prop.destroy unless property_ids.include? cat_prop.property_id
  end

  property_ids.each do |property_id|
   self.category_properties.create(:property_id => property_id) unless category_properties.any? { |d| d.property_id == property_id }
  end
 
end

end

class Product < ActiveRecord::Base
  belongs_to :category
  has_many :product_properties, :dependent => :destroy
  has_many :properties, :through => :product_properties
 
end

class Property < ActiveRecord::Base
has_many :category_properties, :dependent => :destroy
has_many :categories, :through => :category_properties

has_many :product_properties, :dependent => :destroy
has_many :products, :through => :product_properties
 
end

class ProductProperty < ActiveRecord::Base
  belongs_to :product
  belongs_to :property
end

class CategoryProperty < ActiveRecord::Base
  belongs_to :category
  belongs_to :property
end


I can add property to category using checkbox like this

<ul>
<% Property.find(:all).each do |p| %>
<li><%= check_box_tag 'category[property_ids][]' ,
                        p.id,
                        @category.properties.include?(p) %>
<%= p.name %></li>
<% end %>
</ul>

in the category edit form. But i can't use the text area for input the property_unit. Is there a way how to do that? Or i need another controller to handle this?

I can add product to category using link in categories listing.

<%= link_to 'Add Product', :controller => "products", :action => "new", :category_id => category %>

On the new product form i want to fill in the product properties values.
How can i dynamicaly generate text inputs only for properties belongs to category of the product? When i use the console product.category.properties.find :all works fine same as category.properties.find :all, but when i want to use the same in the controller, the result is error class category undefined sad
How can i list only properties linked to product category on the product editing form. I want to make text area when i can write the property value and store it in join model table proruct_properties. Maybe i should add something to product model but i dont know what. Can i save properties through product model? If not how can i use two models for saving data from one form?

Any help you might have would be great. I'm totaly newbie to rails.

Re: has many throug multiple join model

Those are some difficult problems, but it looks like you're on the right track (no pun intended).

If you haven't already, check out the two tutorials on creating and editing multiple models in one form. You should be able to apply this to your last problem (creating the product with properties). The most significant change from the tutorial would probably be the new action. Instead of just generating 5 empty properties, you would generate properties which match the category. For example:

def new
  category = Category.find(params[:category_id])
  @product = category.products.build
  category.properties.each { |property| @product.product_properties.build(:property => property) }
end

Untested.

You mentioned you are getting an error when fetching the properties from a category? Can you give the details of this error? The exact wording and a stack trace would be helpful.

As for the property checkboxes on the Category model. I'm not sure of the best way to do this. A simple approach would be to not do it in one form, but instead create the category first. On the categories show page, provide an "add property" link which would go to a separate from for adding a property to that value along with the unit. You could place this form in a CategoryPropertiesController or something. You can add AJAX to this process so it can be much faster - all on one page.

Railscasts - Free Ruby on Rails Screencasts

Re: has many throug multiple join model

Thank you very much. I'm going to try it and let know how it works.

The error was

undefined method `category' for Product:Class

#on this line
<% Product.category.property.find(:all).each do |p| %>


But I'll going to remake all the product properties stuff the way you sugested.

I made the category property thing this way because property has the same unit for whole category but may have different in another category. For example property lenght may have unit meter in one category and unit cm in the next category.

Last edited by zattrix (2006-12-03 18:12:25)

Re: has many throug multiple join model

zattrix wrote:

Thank you very much. I'm going to try it and let know how it works.

The error was

undefined method `category' for Product:Class

#on this line
<% Product.category.property.find(:all).each do |p| %>


But I'll going to remake all the product properties stuff the way you sugested.

"Product" is a class, so you probably want to call category on an instance of that class. For example:

 <% @product.category.property.find(:all).each do |p| %>

zattrix wrote:

I made the category property thing this way because property has the same unit for whole category but may have different in another category. For example property lenght may have unit meter in one category and unit cm in the next category.

I wasn't suggesting you change the database design - I think it is great the way it is. Instead I recommend you move the property settings out of the category form. This will simplify things a lot since you can create each CategoryProperty on a separate form after the category has been created. You can put a simple select box in that form for selecting the property to add to the category. I can give you an example if you need it.

This approach may not be as good on the user-interface side, but at least it's a start. You can add AJAX to this to make the uesr interface more efficient so the user doesn't have to go to a separate page for each property they add.

Railscasts - Free Ruby on Rails Screencasts

Re: has many throug multiple join model

Thank you for your advices.

I'm trying to create the product with properties but the property_id in table product_properties is always 0. The product_id and the property_value items are correct.

#create action in controller
def create
   @product = Product.new(params[:product])
     params[:properties].each_value do |property|
     @product.product_properties.build(property) unless property.values.all?(&:blank?)
   end
   if @product.save
     redirect_to :action => 'index'
   else
     render :action => 'new'
end

#form in new.rhtml
<% @product.product_properties.each_with_index do |property, index| %>
  <% fields_for "properties[#{index}]", property do |f| %>
    <p><%= f.text_field :property_value %></p>
  <% end %>
<% end %>


Am i need to specify the property id in the form? How can i do that? May i use hidden field with category id for each property?

Is there a way how can i access the property name and unit (category_properties/property_unit) in the product model? For example if i want to have property name, text input for property value and property unit in the form. I tried to use <%= @product.product_property.name %>  for the name.

EDIT
stupid mistake i have to use something like @product.properties and the name from the atributes using iteration, now i have to figure out how to do that smile am i right?
But there is another question is there a way how to get the name and the unit of the property together or i need to use smething like @product.category.category_properties and another iteration?

Last edited by zattrix (2006-12-04 08:02:17)

Re: has many throug multiple join model

zattrix wrote:

stupid mistake i have to use something like @product.properties and the name from the atributes using iteration, now i have to figure out how to do that smile am i right?

Hmm, I'm not sure. The reason the property_id wasn't being set is it wasn't passed through the form. You can pass it using a hidden field:

<% @product.product_properties.each_with_index do |property, index| %>
  <% fields_for "properties[#{index}]", property do |f| %>
    <%= f.hidden_field :property_id %>
    <p><%= f.text_field :property_value %></p>
  <% end %>
<% end %>

zattrix wrote:

But there is another question is there a way how to get the name and the unit of the property together or i need to use smething like @product.category.category_properties and another iteration?

You can do this, but it's not very optimized:

# in product_property.rb
def property_unit
  property.category_properties.find_by_category_id(product.category_id).property_unit
end

You can then call that whenever you want the unit:

<% for product_property in @product.product_properties %>
  <%= product_property.property_value %>
  <%= product_property.property_unit %>
<% end %>

Untested.

Railscasts - Free Ruby on Rails Screencasts

Re: has many throug multiple join model

Hidden field works well. Thank you.

The property_unit definition is ok, but it generate aditional database traffic (as you say not very optimized). Maybe i should store it in product_properties table, because i need it in product listing too, i will copy the property name too, it willl be a lot easier and i'll save the database traffic.
I know it is not so good design to have one item on two places but i think it will work better.
I'll be back soon with the new code.

Re: has many throug multiple join model

I don't want to create a new thread for this. So i post it here maybe someone can help me.
I have link to add image to the product in the product list view.

<%= link_to 'Add Image', :controller => "product_images", :action => "new", :id => product %>

But i need :product_id instead of :id in the image add form. Is it possible?

I used this for displaying property name in the new product form

#property edit part  in new product form
  <% @product.product_properties.each_with_index do |property, index| %>
         <% fields_for "properties[#{index}]", property do |f| %>
           <%= f.hidden_field :property_id %>
           <p><%= Property.find(property.property_id).name%> <%= f.text_field :property_value %></p>
         <% end %>
      <% end %>

I wanted to use the same thing in the edit form but it isn't worked.
I don't know how to access the property_id in the product edit form.

#property edit part in update product form
<% for @property in @product.product_properties %>
          <%= error_messages_for :property %>
          <% fields_for "property[]" do |f| %>
           <p> <%= f.text_field :property_value %></p>
          <% end %>

        <% end %>

Last edited by zattrix (2006-12-05 19:18:35)

Re: has many throug multiple join model

zattrix wrote:

<%= link_to 'Add Image', :controller => "product_images", :action => "new", :id => product %>

But i need :product_id instead of :id in the image add form. Is it possible?

Is this what you want?

<%= link_to 'Add Image', :controller => "product_images", :action => "new", :product_id => product %>

Railscasts - Free Ruby on Rails Screencasts

Re: has many throug multiple join model

Yes. Thanks a lot.
Problem was that I forgot to change the product_image controller's new action.

#new action in product cotnroller
product = Product.find(params[:product_id])
@product_image = product.product_images.build

Now it is working.
Thanks

Re: has many throug multiple join model

Would you expliain me this piece of code :product_id => product from that link, please.
Maybe it is a stupid question but i don't like copy and paste of code if i don't understand it.

Re: has many throug multiple join model

It means add a parameter called "product_id" with the value of "product.id" to the URL.

Re: has many throug multiple join model

Certainly. I don't like to copy and paste code I don't understand either, and it's not a stupid question. smile

Most Rails applications handle the :controller, :action, and :id parameters in a special way by placing a slash between them: controller/action/id. But, you aren't limited to just these three parameters, you can add any other parameters you want. Any other parameter will be placed in the URL query (after a question mark). For example:

url_for :controller => 'foo', :action => 'bar', :id => 1
# => /foo/bar/1

url_for :controller => 'foo', :action => 'bar', :blah => 'test'
# => /foo/bar?blah=test

url_for :controller => 'foo', :action => 'bar', :product_id => 1
# => /foo/bar?product_id=1


In Rails, the model has a special behavior regarding URLs. If you place it directly in the URL hash, it will automatically use the id of the model in the URL. For example:

product = Product.find(1)
url_for :controller => 'foo', :action => 'bar', :product_id => product
# => /foo/bar?product_id=1

This is just the same as calling product.id:

product = Product.find(1)
url_for :controller => 'foo', :action => 'bar', :product_id => product.id
# => /foo/bar?product_id=1

Does that make sense?

Railscasts - Free Ruby on Rails Screencasts

Re: has many throug multiple join model

Yes, it is. Thanks a lot.