Search code examples
ruby-on-railsrubycocoon-gem

Nested fields not being added on form submit


I am using the cocoon gem to try and achieve adding an object which belongs to another with nested fields. I have a 'user_resolution' which has many 'milestones'. I have set up the associations accordingly in both of these models. For some reason, milestones are failing to be created, however if I add one manually in the database I can successfully update it. I am able to dynamically add the fields and remove them using the cocoon gem but that is all. When I click 'add milestone' it redirects me to the show view of the user resolution and throws the success message saying user resolution has been updated, no errors are thrown but the milestone(s) is/are not created.

user_resolution.rb

has_many :milestones
accepts_nested_attributes_for :milestones, reject_if: :all_blank, allow_destroy: true

milestone.rb

belongs_to :user_resolution

I have set up the nested form within the edit view as for now I only want users to add a milestone to a resolution in the edit view.

user_resolutions/edit.html.erb

<%= form_for(@user_resolution) do |f| %>

  <div class="field">
    <%= f.label :name %><br>
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :description %><br>
    <%= f.text_area :description %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>

    <%= f.fields_for :milestones do |milestone| %>
      <%= render 'milestone_fields', f: milestone %>
    <% end %>

    <%= link_to_add_association 'Add Milestone', f, :milestones %>
    <%= f.submit "Add Milestone" %>

<% end %>

_milestone_fields.html.erb

<div class="nested-fields">
   <div class="field-row">
       <%= f.label :name, 'Name' %>
       <%= f.text_field :name %>
   </div>
   <div class="field-row">
       <%= f.label :description, 'Name' %>
       <%= f.text_area :description %>
   </div>
   <div class="field-row">
       <%= f.label :severity, 'severity' %>
       <%= f.check_box :severity %>
   </div>
   <div class="field-row">
       <%= f.label :target_date, 'target_date' %>
       <%= f.date_select :target_date %>
   </div>

   <%= link_to_remove_association 'Remove', f %>

</div>

The permitted parameters within the user resolutions controller also contain the following

milestones_attributes: [:id, :user_resolution_id, :name, :description, :target_date, :severity, :complete, :_destroy]

The milestones themselves have no views, they only have a model and a controller. The controller create action (which i'm unsure is required for nested forms) contains the standard following code

def create
    @milestone = Milestone.new(milestone_params)

    if @milestone.save
        redirect_to user_resolutions_path, 
        :flash => { :success => "You successfully created a milestone" }
    else
        redirect_to new_milestone_path, 
        :flash => { :error => "Oops something went wrong. Try again." }
    end
end

I've been as informative as I can but if you need anything else let me know. Thanks guys.


Solution

  • which i'm unsure is required for nested forms

    You don't need a create action for milestones - they'll be populated from the user_resolutions#create controller action.

    There are several things to look at with this. I'll detail some here. This won't be a specific answer, but may help point you in the right direction.


    Firstly, you need to make sure you're receiving the correct params.

    Cocoon does a great job building the nested form - you need to make sure it's obliging Rails' nested attribute structure.

    To do this, you should right-click > view source.

    In the f.fields_for section (it won't be called that in the HTML), you'll be looking for the equivalent to the following:

    <input type="text" name="milestones_attributes[0][name]" value="">
    

    The important thing to note is the name...

    Each time you use a form, or any Rails view helper for that matter, you're really just building standard HTML. form_for just creates an HTML form, and thus any params contained within it need to adhere to a certain structure for Rails to recognize the params.

    The f.fields_for elements will typically be called x_attributes[:id][:param] - this is passed to Rails, which cycles through each [:id] to determine the number of nested params to add.

    You need to check the source for the above naming structure. If you see it, that's good. If not, it means you haven't built your form properly.


    Secondly, you need to make sure your objects are being built in the controller.

    I'm not sure how Cocoon does this, but essentially, each time you use f.fields_for, you have to build the associated object before:

    def new
        @user_reservation = UserReservation.new
        @user_reservation.milestones.build #-> this is what makes f.fields_for work
    end
    

    If the first step shows incorrect element naming, it means your associative objects are not being built (which is why they're not being recognized).

    To test it, you should build the associative objects in the new method, before sending.


    Finally, you'll want to post your params.

    These tell you in explicit detail what Rails is doing with the nested attributes, allowing you to determine what's happening with them.

    Sorry for the long-winded answer. You'll not have received any answers anyway, so I felt it prudent to give you something.