This is an example of implementing dynamic fields of has_many associations.
We have two models Price and Product with has_many association as defined below.
class Price < ActiveRecord::Base
belongs_to :product
end
class Product < ActiveRecord::Base
has_many :prices
accepts_nested_attributes_for :prices, allow_destroy: true
def initialize(attrs=nil)
super
self.prices << Price.new if self.prices.empty?
end
end
We define route for adding dynamic price fields.
resources :products do
get :new_price, on: :collection
end
In controller, we make sure to include prices_attributes in permitted attributes.
We don't need any controller action for new_price since it will be processed directly by new_price.js.erb
private
def product_params
params.require(:product).permit(:name, :other_product_fields...
prices_attributes: [
:id, :value, ...,
])
end
This is the javascript for rendering new dynamic fields based on the next_index received.
We render the dynamic fields with child_index set as next_index. Also, next_index of new button is incremented.
<% field = "" %>
<% form = "" %>
<%# build a single price field %>
<% form_for Product.new do |f| %>
<% form = f %>
<% f.fields_for :prices, Price.new, child_index: params[:next_index] do |fa,idx| %>
<% field = fa %>
<% end %>
<% end %>
$("#new-price").append("<%= escape_javascript(render(partial: "price", locals: {f: form, fa: field}))%>");
$("#new-price-btn").attr({href: "<%= escape_javascript(new_price_products_path(next_index: params[:next_index].to_i+1)) %>"})
In price partial file, _destroy checkbox is included so that we can remove dynamic fields on save.
/ Price fields ...
/ Actions
= fa.check_box :_destroy
Finally, we add new price button in product form.
=form_for @product do |f|
/ Product fields...
=f.fields_for :prices do |fa,idx|
/ Price
= render partial: "price", locals: {f: f, fa: fa}
/ Add new price button
= link_to new_price_products_path(next_index: f.object.prices.length), id: "new-price-btn", class: "btn btn-info btn-sm", remote: true do
=t("helpers.links.new")