Reputation: 1061
If you're facing a similar issue you might found interesting (re)reading this posts:
I'm trying to implement nested forms with several models and a dynamically added collection_select
field, based on this great tutorial https://www.driftingruby.com/episodes/nested-forms-from-scratch
Sadly, I currently have trouble to retrieve the Product.id from my controller to save the record appropriately.
In my schema:
When I trigger a submission with multiples OrderProduct records, only the last :product_id is passed:
Started PATCH "/formulars/1/orders/53"
Parameters:
{
"authenticity_token"=>"[FILTERED]",
"product_id"=>{"id"=>"7"}, # <- :product_id of the last collection_select
"order"=>
{
"order_products_attributes"=>
{
"1612773831378"=>{"quantity"=>"1", # <- each OrderProduct dynamically added
"_destroy"=>"false"}, # <-
"1612774917407"=>{"quantity"=>"6", # <- frontend JS substitutes the associated
"_destroy"=>"false"}, # <- objects' :object_id with a unique
"1612774918874"=>{"quantity"=>"4", # <- timestamp.
"_destroy"=>"false"}}}, # <- I need to access :product_id from here,
} # for each OrderProduct added
},
"commit"=>"Modifier ce(tte) Commande",
"formular_id"=>"1",
"id"=>"53" # <- Order's :id
}
Here's how the field is dynamically prepend to the form:
# app/views/orders/_form.html.erb
<%= form_with(model: [formular, order] do |form |%> # <- Form for an Order binded to a Formular (nested routes)
....
<%= link_to_add_product_row('Add an article', form, :order_products) %> # <- Call a Helper method
<% end %>
# app/helpers/application_helper.rb
def link_to_add_product_row(name, f, association, **args)
new_object = f.object.send(association).klass.new # <- create a new instance of the associated object
# f.object == #<Order id: 53> in this context
# new_object == #<OrderProduct id: nil, order_id: nil, product_id: nil, quantity: 1>
id = new_object.object_id # <- get its unique :object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize, f: builder) # 'orders/_order_product' is a partial shown below
end
link_to(name, '#', class: "add_fields " + args[:class], data: {id: id, fields: fields.gsub("\n",'')})
end
# app/views/orders/_order_product.html.erb
<tr>
#
# I need this collection_select to:
# - display the Product's name
# - display only Products associated to the selected Formular
# - pre-select the Product record, if set
# - return the Product.id to the backend
#
<td><%= collection_select(:product_id, :id, @formular.products, :id, :name) %></td>
<td></td>
<td><%= f.number_field :quantity, hide_label: true %></td>
<td><%= f.submit %></td>
<td>
<%= f.hidden_field :_destroy, as: :hidden, method: :delete %>
<%= link_to 'Delete', '#', class: 'remove_order_product' %>
</td>
</tr>
$(document).on('turbolinks:load', function() {
$('form').on('click', '.add_fields', function(event) {
var regexp, time;
time = new Date().getTime();
regexp = new RegExp($(this).data('id'), 'g');
$('.fields').append($(this).data('fields').replace(regexp, time));
return event.preventDefault();
});
});
# app/controllers/orders_controller.rb
# before_action callback executed on :create and :update
# goal is to retrieve OrderProducts from params, and associate
# them to the current instance of Order
def set_order_products
order_products_params = order_params.dig(:order, :order_products_attributes)
if order_products_params
order_products_params.each_value do |param|
product = Product.find(param['product_id'])
@order.order_products.build(product: product,
quantity: param['quantity'])
end
end
end
# If OrderProducts params were like:
#
# {"TIMESTAMP"=>{
# "product_id"=>"42", # <- this parameter is missing
# "quantity"=>"3",
# "_destroy"=>"false"}}
#
# then I'd be able to identify to which product this OrderProduct is associated to
Let me know if you want mode code.
Any idea about how to achieve this goal ? I've tried adding hidden_fields
, tweaking collection_select
, using a select
and options_from_collection_for_select
/ options_for_select
; but I must admit that I'm lost (and I may have overcomplexified my schema).
TL_DR: How can I retrieve a Product's ID from a field_for(:order_products)
dynamically generated inside Order's controller ?
Upvotes: 0
Views: 589
Reputation: 1061
From Can someone explain collection_select to me in clear, simple terms? https://stackoverflow.com/a/8908298/11509906
zquares said:
With regards to using form_for, again in very simple terms, for all tags that come within the form_for, eg. f.text_field, you dont need to supply the first (object) parameter. This is taken from the form_for syntax.
I changed my collection_select
from:
<%= collection_select(:product_id, :id, @formular.products, :id, :name) %>
# Parameters: {"authenticity_token"=>"[FILTERED]", "product_id"=>{"id"=>"7"}, "order"=>{"order_products_attributes"=>{"1612782399659"=>{"quantity"=>"3", "_destroy"=>"false"}, "1612782403591"=>{"quantity"=>"5", "_destroy"=>"false"}}}, "commit"=>"Modifier ce(tte) Commande", "formular_id"=>"1", "id"=>"53"}
to:
<%= f.collection_select(:product_id, @formular.products, :id, :name) %>
# Parameters: {"authenticity_token"=>"[FILTERED]", "order"=>{"order_products_attributes"=>{"1612782084184"=>{"product_id"=>"3", "quantity"=>"3", "_destroy"=>"false"}, "1612782085172"=>{"product_id"=>"33", "quantity"=>"-3", "_destroy"=>"false"}}}, "commit"=>"Modifier ce(tte) Commande", "formular_id"=>"1", "id"=>"53"}
So now I can fetch the associated product within Order's controller
Upvotes: 0