user10756
user10756

Reputation: 649

Rails nested form many-to-many special case

Let consider the classic realization of many-to-many model in Rails.

class Order
  has_many   :order_products
  has_many   :products, through: order_products
  accepts_nested_attributes_for :order_products
end

class Product
  has_many :order_products
  has_many :orders, through: order_products
end

class OrderProduct
  belongs_to :order
  belongs_to :product
  accepts_nested_attributes_for :products
end

<%= form_for(@order) do |f| %>
  <% f.fields_for :order_products do |op|%>
  <%= op.label :amount %><br>
  <%= op.text_field :amount %>

  <% op.fields_for :product do |p|%>
     <%= p.label :name %><br>
     <%= p.text_field :name %>
  <% end %>
<% end %>

The problem is in my case I have a full constant tables products, with predefined set of products. I need to show all list of products on create/edit view of order and user should set amount of each product he needs, there is no way to add additional products. order_products is a join table.

In classic example, user by himself can add/delete products to products table, in my case he can only choose the needed product from predefined set and his choose should be recorded in order_products table.

The above code is given for classic case, I don't know how to fit it to my case.

I would appreciate your help.

Addendum: The following is the code that I have right now

<%= form_<%= form_for(@order) do |f| %>
  <% f.fields_for :order_products do |op|%>
  <%= op.label :amount %><br>
  <%= op.text_field :amount %>
  <%= op.label :product_id, "Product" %>
  <%= op.select :product_id, Product.all.collect { |p| [p.name, p.id] } %>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

I had to add the following code to order controller, in order to create all non icluded products to order with 0 amount, such that all of them are on the form. It's not the best way, do you know how to do it correctly?

Product.all.each { |p| order.orderproducts.push(OrderProduct.new(:product_id => p.id,:order_id => 1, :amount => 0)) if [email protected]?{|b| b.product_id == p.id}}

Upvotes: 0

Views: 568

Answers (1)

Shadwell
Shadwell

Reputation: 34774

Seems like you need an amount column/attribute on OrderProduct and that you can remove the nested products declaration on that class.

You can then create OrderProduct instances from your order specifying the product that you want and the amount of that product. You don't need any nestedness for product because you have those created already.

Your form then becomes something like:

<%= form_for(@order) do |f| %>
  <% f.fields_for :order_products do |op|%>
  <%= op.label :amount %><br>
  <%= op.text_field :amount %>
  <%= op.label :product_id, "Product" %>
  <%= op.select :product_id, Product.all.collect { |p| [p.name, p.id] } %>
<% end %>

It would also be worth adding a validation to your OrderProduct to ensure that amount is a valid, positive, integer.

validates_numericality_of :amount, 
    :only_integer => true, 
    :greater_than_or_equal_to => 0

Upvotes: 2

Related Questions