2

I have the parent Model Intervention which in turn accepts the nested attributes for the model ExternalFeedback. I followed this tutorial (http://tutorials.pluralsight.com/ruby-ruby-on-rails/ruby-on-rails-nested-attributes) so that I can add dynamically as many external_feedbacks as needed, but when I try to save the parent form with the external_feedback rendered in the _form and another created dynamically, rails gives the error: "Unpermitted parameters: 0, feedback_form_1", saving only the parent's attributes. In application.js I created a new_id variable to give simpler IDs to the fields.

This error only happens when I add extra fields using jQuery. If I don't add extra fields to the form, rails saves data correctly to DB, saving the only external_feedback created.

Can anyone help me? I tryied so many solutions and nothing works. What can be wrong? Thank you so much in advance!

Here is the relevant code:

The parameters in the log console are now like these (UPDATED) (solved previous problem of creating an extra feedback_form attribute by enclosing the _feedbacks.html.erb code in a div with id="feedback_form"):

Parameters : {
"utf8" => "✓",
"authenticity_token" => "hxbegdsEOu4BiS7cj7TCBvtpNJpQcEKK0AVI8gfF9LZ+RApGwoimq50dctRfN6Wn/yUou6qyLxNUZ6UgXZl8uw==",
"user_id" => "1",
"incident_id" => "62",
"intervention" => {
    "incident_priority_id" => "1",
    "begin_date(1i)" => "2016",
    "begin_date(2i)" => "11",
    "begin_date(3i)" => "4",
    "begin_date(4i)" => "09",
    "begin_date(5i)" => "49",
    "end_date(1i)" => "",
    "end_date(2i)" => "",
    "end_date(3i)" => "",
    "end_date(4i)" => "",
    "end_date(5i)" => "",
    "description" => "dasdsada ss dasd",
    "intervention_status_id" => "1",
    "forwarded_to" => "",
    "external_feedbacks_attributes" => {
        "0" => {
            "date(1i)" => "2016",
            "date(2i)" => "11",
            "date(3i)" => "4",
            "date(4i)" => "09",
            "date(5i)" => "49",
            "feedback_source" => "11",
            "external_ticket" => "11",
            "feedback" => "11",
            "_destroy" => "false"
        },
        "feedback_form_1" => {
            "date(1i)" => "2016",
            "date(2i)" => "11",
            "date(3i)" => "4",
            "date(4i)" => "09",
            "date(5i)" => "49",
            "feedback_source" => "22",
            "external_ticket" => "22",
            "feedback" => "222",
            "_destroy" => "false"
        }
    }
},
"commit" => "Gravar"

}

intervention.rb:

 class Intervention < ApplicationRecord

  belongs_to :incident
  belongs_to :user

  belongs_to :incident_priority
  belongs_to :intervention_status

  validates_presence_of :user_id, :incident_id

  has_many :external_feedbacks, :inverse_of => :intervention, dependent: :destroy
  accepts_nested_attributes_for :external_feedbacks, :allow_destroy => true, :reject_if => :all_blank

end

interventions/_form.html.erb:

<div id="feedback-forms">
  <%= f.fields_for :external_feedbacks do |fb| %>

      <%= render 'feedbacks', f: fb %>

  <% end %>
</div>

<div class="actions" align="right">
  <%= link_to_add_fields 'Adicionar novo feedback', f, :external_feedbacks %>
  <%= link_to 'Cancelar', incidents_path(:mirth => @mirth, :project => @project), class: "btn btn-info" %>
  <%= f.submit "Gravar", class: "btn btn-info" %>
</div>

application_helper.rb:

def link_to_add_fields(name = nil, f = nil, association = nil, options = nil, html_options = nil, &block)
    # If a block is provided there is no name attribute and the arguments are
    # shifted with one position to the left. This re-assigns those values.
    f, association, options, html_options = name, f, association, options if block_given?

    options = {} if options.nil?
      html_options = {} if html_options.nil?

      if options.include? :locals
        locals = options[:locals]
      else
        locals = {}
    end

    if options.include? :partial
      partial = options[:partial]
    else
      partial = 'feedbacks'
    end

    # Render the form fields from a file with the association name provided
    new_object = f.object.class.reflect_on_association(association).klass.new
    fields = f.fields_for(association, new_object, child_index: 'feedback_form') do |builder|
      render(partial, locals.merge!(f: builder))
    end

    # The rendered fields are sent with the link within the data-form-prepend attr
    html_options['data-form-prepend'] = raw CGI::escapeHTML(fields)
    html_options['href'] = '#'

    content_tag(:a, name, html_options, &block)
end

_feedbacks.html.erb (UPDATED):

<div id="feedback_form">
  <div class="form-group form-inline">
    <%= f.label 'Data' %>
    <%= f.datetime_select :date, class: "form-control" %>
  </div>

  <div class="form-group form-inline">
    <%= f.label 'Origem da informação' %>
    <%= f.text_field :feedback_source, class: "form-control" %>
  </div>

  <div class="form-group form-inline">
    <%= f.label '# Ticket' %>
    <%= f.text_field :external_ticket, class: "form-control" %>
  </div>

  <div class="form-group form-inline">
    <%= f.label :feedback %>
    <%= f.text_area :feedback, rows: 4, class: "form-control" %>
  </div>

  <%= f.hidden_field :_destroy %>
  <%= link_to 'Apagar', '#', class: 'remove-feedback' %>

  <hr>
  <br>
</div>

application.js (UPDATED):

$("div#feedback-forms").on('click', '.remove-feedback', function (event) {

    event.preventDefault(); // Prevent link from following its href

    $(this).closest("[id^=feedback-form]").remove(); //procura div com id 'feedback-form*'

});

var new_id = 1;  


$('[data-form-prepend]').click( function(e) {

    var obj = $( $(this).attr('data-form-prepend') );

    obj.find('input, select, textarea').each( function() {
        $(this).attr( 'name', function() {
            //return $(this).attr('name').replace( 'new_record', (new Date()).getTime() );
            return $(this).attr('name').replace( 'feedback_form', 'feedback_form_' + new_id );
        });
    });

    obj.insertBefore( this );

    new_id++;

    return false;
});

interventions_controller.rb:

def new


@incident = Incident.find(params[:incident])
@user = User.find(current_user.id)
@intervention = Intervention.new(user: @user, incident: @incident)
@intervention.external_feedbacks.build

.....

end


def create

    @incident = Incident.find(params[:incident_id])
    @user = User.find(params[:user_id])
    @intervention = Intervention.create(intervention_params)
    @intervention.incident = @incident
    @intervention.user = @user

....

end

.....

def intervention_params
    params.require(:intervention).permit(:id, :user_id, :incident_id, :incident_priority_id, :begin_date, :end_date, :description,
                                         :intervention_status_id, :forwarded_to,
                                         external_feedbacks_attributes: [:id, :date, :feedback_source, :external_ticket,
                                                                         :feedback, :intervention_id, :_destroy])

  end
7
  • when you inspect your rendered html is there an empty feedback_form attribute/element somewhere? It looks like you might have one lying around that is being submitted before your js turns it into a child form. Commented Nov 3, 2016 at 18:05
  • I can't find any extra form in the page :/ I can send my files if you think it is easier to figure out the problem. Commented Nov 4, 2016 at 9:42
  • I solved the problem of the empty feedback_form attribute by enclosing the _feedbacks.html.erb code inside a div with id="feedback_form" (check UPDATED info above). Still having the problem of not saving the external_feedbacks_attributes when 1 or more are generated dynamically. How can I change the white listed parameters in interventions_controller.rb to accept dynamically generated parameters? I saw tutorials but couldn't make it work in my case. Commented Nov 4, 2016 at 10:03
  • Can you remove , :reject_if => :all_blank just to make sure the validation isn't messing with it. And remove validates_presence_of :user_id, :incident_id. Basically try removing any validations to see if that's causing it. If it is there are some ways to fix them to work properly. Commented Nov 4, 2016 at 14:06
  • I did that but the problem continues. I tryed a temporary fix by doing params.require(:intervention).permit! to allow mass assignment, and it saves the parameters correctly. But what I really wanted was to dynamically set the parameters to permit for the external_feedbacks_attributes, but can't make it work. I did: params.require(:intervention).permit(:user_id, :incident_id, :incident_priority_id, :begin_date, :end_date, :description, :intervention_status_id, :forwarded_to).tap do |whitelisted| whitelisted[:external_feedbacks] = params[:intervention][:external_feedbacks] end Commented Nov 4, 2016 at 16:43

1 Answer 1

2

This should have jumped out at me sooner. It's tied to your hash keys. Rails expects integer values but in your JS you're passing it as new_id_[integer]. Check out this answer that goes into also.

Replace this line in your application.js:

return $(this).attr('name').replace( 'feedback_form', 'feedback_form_' + new_id );

with this:

return $(this).attr('name').replace( 'feedback_form', 'feedback_form_' + new_id );

or to follow the tutorial you were following a bit closer you could use this:

return $(this).attr('name').replace( 'feedback_form', (new Date()).getTime() );

I did have to change a the click handler to get it to fire properly as well but I'm thinking that was my setup. The click event handler to add fields ended up like this for me:

$(document).on('click', '*[data-form-prepend]', function(e) {
  var obj = $( $(this).attr('data-form-prepend') );
  obj.find('input').each( function() {
    $(this).attr( 'name', function() {
      return $(this).attr('name').replace( 'feedback_form', (new Date()).getTime() );
    });
  });
  obj.insertBefore( $(this) );
  return e.preventDefault();
});
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you for your anwers, they clarified things to me! But my question was about not being able to save nested attributes that where generated dynamically. I don't know if you saw my last comment, where I set to mass assign the attributes. But what I really wanted is to remove that mass assign and only permit the class and nested attributes, plus the dynamically generated ones. Do you know how can I do that?
@Ayanami Sorry if i'm not getting it. The above should allow you to remove the mass assignment and accept the attributes for Interventions and the nested attributes for ExternalFeedbacks. From what I can tell from your code, your Interventions form loads with the Intervention attribute fields and a single ExternalFeedbacks set of fields. Then the button lets you add more ExternalFeedbacks fields (these are the dynamically created ones). The dynamically added form fields are not being saved because of the hash key ("feedback_form_1") you are creating in your javascript.
I did what you suggested and solved the problem! Thank you so much you saved my day!! :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.