1

I've looked through every post I can imagine regarding this, but can't come across a solution. I have a Task with a nested resource of Task Products which grabs its ID's from another database. There can be multiple Task Products nested in a Task, and each one is dynamically created with some Javascript and an Application Helper (both of which are purely a copy/paste on my part).

I built up the code for these nested parameters with the help of Railscast 196/197 and this post. All the relevant code is below, and then onto the problem:

task.rb:

class Task < ApplicationRecord
  has_many :task_products, :dependent => :destroy
  accepts_nested_attributes_for :task_products, :reject_if => lambda { |p| p.values.all?(&:blank?) }, :allow_destroy => true
end

task_controller.rb:

class TasksController < ApplicationController

  def new
    @task = Task.new
    @task.task_products.build
  end

    def task_params
  params.require(:task).permit(:id, :task_name, . . ., :created_at, :updated_at, 
                               :task_products_attributes => [:task_id, :product_id, :created_at, :updated_at])
end

application_helper.rb:

module ApplicationHelper

  def new_child_fields_template(form_builder, association, options = {})
    options[:object] ||= form_builder.object.class.reflect_on_association(association).klass.new
    options[:partial] ||= association.to_s.singularize
    options[:form_builder_local] ||= :f

    content_tag(:div, :id => "#{association}_fields_template", :style => "display: none") do
      form_builder.fields_for(association, options[:object], :child_index => "new_#{association}") do |f|
        render(:partial => options[:partial], :locals => { options[:form_builder_local] => f })
      end
    end
  end

application.js:

$(function() {
  $('form a.add_child').click(function() {
    var association = $(this).attr('data-association');
    var template = $('#' + association + '_fields_template').html();
    var regexp = new RegExp('new_' + association, 'g');
    var new_id = new Date().getTime();
    $(this).parent().before(template.replace(regexp, new_id));
    return false;
  });
});

_form.html.erb:

<%= form_for @task, url: tasks_path, method: :post, :html => {:multipart => true } do |f| %>
    <%= f.fields_for :task_products, TaskProduct.new do |builder| %>
      <%= render "task_product", :f => builder %>
    <% end %>
    <p><%= add_child_link "Add Product", :task_products %></p>
    <%= new_child_fields_template f, :task_products %>

_task_product.html.erb:

<div class="fields">
  <div class="form-group">
    <%= f.label :product_id, "Product:", class: 'col-sm-3 control-label' %>
      <%= f.collection_select(:product_id, @items, :id, :item_select, {:include_blank => 'Select Item...'}, {:class => 'form-control'}) %>
      <%= remove_child_link "Remove Product", f %>
  </div>
</div>

Code in console after submit along with the error it produced:

Parameters: {"utf8"=>"✓", "authenticity_token"=>"KDQ...8bxur2A==", 
"task"=>{"task_type"=>"Store Order", 
    "task_products_attributes"=>{
    "0"=>{"product_id"=>"1", "_destroy"=>"false"}, 
    "1484792726712"=>{"product_id"=>"2", "_destroy"=>"false"}, 
    "new_task_products"=>{"product_id"=>"", "_destroy"=>"false"}}, 
    "initiated_by"=>"1", "initiated_for"=>"5", "active"=>"true", "status"=>"Pending Acceptance"}, 
"commit"=>"Create Task"}

Unpermitted parameters: 0, 1484792726712, new_task_products

Generated HTML examples of error producing code of multiple generated selects:

<select class="form-control" name="task[task_products_attributes][0][product_id]" id="task_task_products_attributes_0_product_id">
<select class="form-control" name="task[task_products_attributes][1484794199756][product_id]" id="task_task_products_attributes_1484794199756_product_id">

and its template, which I thought should be throwing back ' "_destroy"=>true' in the console?:

<select class="form-control" name="task[task_products_attributes][new_task_products][product_id]" id="task_task_products_attributes_new_task_products_product_id">

The problem, as seen above, is that my generated Task Product form is adding an additional bracketed identifier(child index?) into my select box for each item, and I don't know how to dive into the Application Helper to remove it, or move it where it belongs. Or does it actually belong there and I'm doing something else wrong? I get the feeling that each individual Task Product should have it's own unique identifier, but Rails isn't having any of that and says its unpermitted.

The 'new_child_fields_template' in the Application Helper is a bit over my head, and while I understand how it's getting the unique identifier to inject into my Task Product form, I don't know how it's throwing it into the "name=" field of my select that is apparently screwing everything up. Anybody have some guidance on how I can overcome this problem? Thanks!

Community
  • 1
  • 1
Tyler P
  • 63
  • 2
  • 7
  • I believe I may of had a similar problem with the hidden field you are talking about. `CTRL+F` and search for `include_hidden` on this page: `http://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html`. You can use that as a param in the `#collection_check_boxes`. `= collection_check_boxes(:object, :field, [opts], :method1, :method2, {include_hidden: false}, ... &block` – Shiyason Jan 19 '17 at 04:22
  • See if this helps [how to permit an array with strong parameters](http://stackoverflow.com/a/30285592/2981429) – max pleaner Jan 19 '17 at 04:33
  • @maxple, thank you for the link (it'll be useful later!) but I don't think this is what I'm trying to accomplish. The generated code from the Application Helper is putting an additional value into the "name" field of the select dropdown, and that is trying to be passed as a value for the form. I don't need it to be passed as a value. All I need to do is make sure the Task Product is linked to the Task it is a part of (which I think happens automatically), so all I think I need to do is figure out how to get rid of the second bracketed item in the name column. – Tyler P Jan 19 '17 at 05:01
  • @Shiyason, thank you for the response, but this isn't a problem with hidden fields. Just for kicks, I added the "include_hidden" clause though, and it didn't change the Unpermitted error. – Tyler P Jan 19 '17 at 05:02
  • What do you mean by "which grabs its ID's from another database" do you really mean another DB or another table? – max Jan 19 '17 at 06:52
  • @max, that's actually irrelevant to the problem. It just means that the value that is generated for the Task Product dropdown is taken from the Item database. – Tyler P Jan 19 '17 at 14:39

2 Answers2

2

For nested attributes to work nicely with strong attributes, you need to pass the id and _destroy attributes to the list of attributes. In your tasks_controller.rb:

params.require(:task).permit(
    task_products_attributes: [
        :id, :product_id, :created_at, :updated_at, :_destroy
    ]
)

For a clearer explanation you can go to this answer.

Community
  • 1
  • 1
  • You could use dinamically generated fields, so you don't risk forgetting any of it like this: `task_products_attributes: ([:_destroy] + TaskProducts.column_names.map(&:to_sym))` – ErvalhouS Jan 19 '17 at 13:36
  • @zamakkat then I'm really confused because I've done exactly this, and the post you linked describes the exact same problem I'm having, but the "Unpermitted Parameters" problem persists. This is frustrating beyond belief, because I have no idea what's keeping this from working, I've looked at every possible solution I can think of. – Tyler P Jan 19 '17 at 23:34
  • Clarification: This is what my params.require is now, and it's still throwing the "Unpermitted" errors for the "0"=>, etc. parameter:: task_products_attributes: [:product_id, :task_id, :created_at, :updated_at, :id, :_destroy] /////////////// "task_products_attributes"=>{"0"=>{"product_id"=>"2", "_destroy"=>"false"}, "1484881558666"=>{"product_id"=>"3", "_destroy"=>"false"}, "new_task_products"=>{"product_id"=>"", "_destroy"=>"false"}}, . . . Unpermitted parameters: 0, 1484881558666, new_task_products – Tyler P Jan 20 '17 at 03:43
  • Why do you have both `task_id` and `product_id` in the params? Could you move the `id` to the begging so your params look something like this: `:id, :product_id, :_destroy`. See if that works... – Justin Ho Tuan Duong Jan 20 '17 at 06:21
0

One final comment, unless somebody has any additional input as to why including ":id" in my nested parameters isn't working.

I finally got it working by changing the permitted parameters to

params.require(:task).permit!  

This apparently is a blanket permit for anything that happens to fall into the :task parameter, whether it's nested or not. I gather that this isn't recommended, but the site it's for is closed off to very few team members, so I assume this shouldn't be an issue? Additionally, I am still looking for insight into why the template for "new_task_products" isn't giving me a "destroy=>1" like it should be since the product_id is blank. Is this code not correct?

accepts_nested_attributes_for :task_products, 
    :reject_if => lambda { |p| p.values.all?(&:blank?) },
    :allow_destroy => true

I might try to move the template to a somewhere outside of the view though to avoid the issue. Appreciate the help everyone!

Tyler P
  • 63
  • 2
  • 7