Search code examples
ruby-on-railshas-many-throughform-forfields-for

has_many through form listing each record of the other parent


I'm trying to create an order form that also serves as a list for all the available products. I have the models with the has_many through associations, and I managed to create collection_select fields to create the records on the join model which works fine, but I cannot figure out how to build a list showing each product and accepting additional attributes for the join records.

My models (somewhat simplified, I also have images and such):

class Supply < ActiveRecord::Base
  attr_accessible :available, :name

  has_many :order_lines
  has_many :orders, through: :order_lines

  accepts_nested_attributes_for :order_lines

end

class Order < ActiveRecord::Base
  attr_accessible :month, :user_id, :order_lines_attributes

  belongs_to :user

  has_many :order_lines
  has_many :supplies, through: :order_lines

  accepts_nested_attributes_for :order_lines

end

class OrderLine < ActiveRecord::Base
  attr_accessible :amount, :supply_id, :order_id
  belongs_to :order
  belongs_to :supply

  validates_presence_of :amount

end

Order controller:

def new
  @order = Order.new
  @supplies = Supply.available
  @supplies.count.times { @order.order_lines.build }

  respond_to do |format|
    format.html # new.html.erb
    format.json { render json: @supply_order }
  end
end

Order_line field from Order form:

<%= f.fields_for :order_lines do |order_line| %>
  <%= order_line.label :amount %>
  <%= order_line.number_field :amount %>
  <%= order_line.label :supply_id %>
  <%= order_line.collection_select(:supply_id, @supplies, :id, :name) %>
<% end %>

In place of this I would like to have a list with one order_line per supply (with name and other attributes) which gets saved if there's an amount defined. Any help appreciated!


Solution

  • Just add an order_item for each supply. Instead of this:

    @supplies.count.times { @order.order_lines.build }
    

    Do this:

    @supplies.each { |supply| @order.order_lines.build(supply: supply) }
    

    Then in your order form you don't need the collection_select for supply_id, just show the name:

    <%= order_line.object.supply.name %>
    

    Make sure you ignore nested attributes with an empty amount:

    accepts_nested_attributes_for :order_lines, reject_if: proc { |attr| attr.amount.nil? || attr.amount.to_i == 0 }
    

    You don't need the accepts_nested_attributes_for in the Supply model.