Rails - Build deeply nested objects dynamically (Cocoon / nested_form)

I currently have a complex, deeply nested form and use the Cocoon gem to dynamically add sections as needed (for example, if the user wants to add another vehicle to the sale form). The code looks like this:

<%= sale.fields_for :sale_vehicles do |sale_vehicles_builder| %>
    <%= render :partial => "sale_vehicles/form", :locals => {:f => sale_vehicles_builder, :form_actions_visible => false} %>    
<% end -%>
<div class="add-field-links">
    <%= link_to_add_association '<i></i> Add Vehicle'.html_safe, sale, :sale_vehicles, :partial => 'sale_vehicles/form', :render_options => {:locals => {:form_actions_visible => 'false', :show_features => true, :fieldset_label => 'Vehicle Details'}}, :class => 'btn' %>
</div>

      

This works very well for the first level of nesting - the object is sale_vehicle

correctly built by Cocoon and the form displays as expected.

The problem arises when there is another level of nesting - the partial expression sale_vehicle

looks like this:

<%= f.fields_for :vehicle do |vehicle_builder| %>
    <%= render :partial => "vehicles/form", :locals => {:f => vehicle_builder, :f_parent => f, :form_actions_visible => false, :show_features => true, :fieldset_label => 'Vehicle Details'} %>
<% end -%>

      

Partial for is vehicle

rendered borderless because no object sale_vehicle.vehicle

was created.

What I need to do is build a nested object along with the main object (Cocoon does not currently create any nested objects), but what is the best way to do this? Is there a way to select nested forms from the helper code so that they can be built?

Cocoon currently creates the main object like this:

if  instance.collection?
    f.object.send(association).build
else
    f.object.send("build_#{association}")
end

      

If I could do something like the following it would keep everything nice and simple, but I'm not sure how to get f.children

- is there a way to access the nested form collectors from the parent form constructor?

f.children.each do |child|
    child.object.build
end

      

Any help was appreciated to get this working, or suggest some other way to create these objects dynamically.

Thank!

EDIT: It might be worth mentioning that this question seems to be related to both the Cocoon gem mentioned above and Ryan Bates's nested_form gem . Problem # 91 for the Cocoon gem appears to be the same problem as this, but the workaround suggested by dnagir (object building delegation) is not ideal in this situation as it will cause problems in other forms.

+18


source to share


1 answer


I see no in your second nested form link_to_add_association

.

Inside the cocoon, link_to_add_association

creates a new element when the user wants to dynamically add it.

Or do you mean that once created sale_vehicle

it should automatically contain vehicle

? I would assume the user would have to select the vehicle to sell?

I have a test project that demonstrates double nested forms: the project has tasks that can have sub-tasks.

But maybe it doesn't relate so well to what you want to do?

You don't show your models, but if I understand the relationship correctly,

sale 
  has_many :sale_vehicles
sale_vehicle
  has_one :vehicle (has_many?)

      

So, if you have sale_vehicle

one that might have vehicle

, then I would suggest that your user first add sale_vehicle

in sale

and then click on the link to add vehicle

. This is what a cocoon can do perfectly. If, on the other hand, you want to make in the dynamic creation of the cocoon sale_vehicle

was created by a vehicle

, I see a number of different parameters.

Use after_initialize

I can't say I'm a real fan of this, but in after_initialize

your callback, you sale_vehicle

can always create the model you want vehicle

.

My guess is that since yours is sale_vehicle

invalid / cannot exist without a model vehicle

, it will be responsible for being able to create a nested model right after assembly.

Note what after_initialize

is done for every object creation, so this can be costly. But this can be a quick fix. If you reject empty nested models this should work imho.

Using Decorator / Presenter



To the user, sale_vehicle

and vehicle

seem to be one object, so why not create a decorator consisting of sell_vehicle and a vehicle that is presented in one (nested) form and when saving this, the decorator knows to save it to the correct models.

Note: There are different terms for this. A decorator usually only extends one class with some viewer, but it can also be a composition of different models. Alternative terms: presenter, view model.

Anyway, the decorator / presenter function is an abstraction of the main database for your users. So, for some reason, you needed to split one object into two database models (for example, to limit the number of columns to keep readable models ...), but for the user it is still the only object. So, "imagine" it as a whole.

Allow cocoon to call custom method build

Not sure if I'm a fan of this, but it's definitely a possibility. It is already supported if the "nested model" is not ActiveRecord :: Association, so it shouldn't be too hard to add. But I disagree with this addition. All of these parameters add complexity to the job.

EDIT: Simplest fix

Inside the partial, just create the required child object. This should happen before fields_for

, and then you are good to go. Something like

<% f.object.build_vehicle %>
<%= f.fields_for :vehicle do |vehicle_builder| %>
    <%= render :partial => "vehicles/form", :locals => {:f => vehicle_builder, :f_parent => f, :form_actions_visible => false, :show_features => true, :fieldset_label => 'Vehicle Details'} %>
<% end -%>

      

Conclusion

I personally really like the decorator approach, but it can be a little heavy. Just create the object before rendering the call fields_for

, this way you are always sure there is at least one.

I am interested to hear your thoughts.

Hope this helps.

+23


source







All Articles