1

Hi in rails the nested parameters are passed with attributes appended to the key's and then its is passed to permit

If I receive normal hash and want to append attribute to each nested key before permit is called

How to do that ?

"project":{  
  "project_name":"test",
  "tentative_start_date":"2018-12-12",
  "tentative_end_date":"2019-12-12",
"project_roles":[  
     {  
        "role_id":1,
        "project_role_skills":[  
           {  
              "skill":{  
                 "skill_type":"C++",
                 "id":2
              }
           }
        ],
        "project_role_users":[  

        ],
        "role_end_date":"2018-12-12",
        "role_start_date":"2018-12-12"
     }
  ]

} }

This is the request I received and I want to append attribute to project_roles,project_role_skill and so on so that rails accept that

Please anyone help

rishabh agarwal
  • 99
  • 1
  • 13

2 Answers2

0

Have you tried this? Rails 4 - Strong Parameters - Nested Objects If it doesn't work for you, can you please specify what is your form currently and what is your current method using permit?

CecileV
  • 171
  • 1
  • 8
  • the response that i receive I have mentioned above and I want to convert it to "project_roles_attributes" as it is nested and rails accept in this way only – rishabh agarwal Dec 11 '18 at 11:35
  • sorry I still don't understand. Can you please add the `code` for your form and for your controller action that uses permit? – CecileV Dec 11 '18 at 11:43
  • params.require(:project) .permit(:date, :id, :created_at, :updated_at, :project_name, :tentative_start_date, :tentative_end_date, :actual_start_date, :actual_start_date, :actual_end_date, :customer_id, customer_attributes: [:id, :name, :email, :sales_executive, :contact_person, :notes], project_roles_attributes: [ – rishabh agarwal Dec 11 '18 at 11:49
  • like you can see here project_roles_attribute is permitted but the response i get has project_roles – rishabh agarwal Dec 11 '18 at 11:50
  • Ok so what if you try what is in the link I pasted in my answer, something like `params.require(:project).permit(, :project_roles => [:role_id, :project_role_skills => [] ])`. Can you get the permitted attributes you want? – CecileV Dec 11 '18 at 11:58
0

TL;DR:

def params_with_deep_appended_nested_attributes(_params)
  modified_params = _params.clone

  _params.each do |k, v|
    if v.is_a?(Array)
      modified_params["#{k}_attributes"] = []

      v.each_with_index do |vv, index|
        if vv.is_a?(ActionController::Parameters)
          modified_params["#{k}_attributes"][index] = params_with_deep_appended_nested_attributes(vv)
        end
      end

      modified_params.delete(k)

    elsif v.is_a?(ActionController::Parameters)
      modified_params["#{k}_attributes"] = params_with_deep_appended_nested_attributes(v)
      modified_params.delete(k)
    end
  end

  modified_params
end

Usage Example:

# rails console
example_json_hash_request = {
  "project": {
    "project_name":"test",
    "tentative_start_date":"2018-12-12",
    "tentative_end_date":"2019-12-12",
    "project_roles": [
       {  
          "role_id":1,
          "project_role_skills":[  
             {  
                "skill":{  
                   "skill_type":"C++",
                   "id":2
                }
             }
          ],
          "project_role_users":[  

          ],
          "role_end_date":"2018-12-12",
          "role_start_date":"2018-12-12"
       }
    ]
  }
}

# simulate `params` value in the controller
params = ActionController::Parameters.new(example_json_hash_request)

modified_params = params_with_deep_appended_nested_attributes(params)

pp modified_params.permit!.to_hash
# {"project_attributes"=>
#   {"project_name"=>"test",
#    "tentative_start_date"=>"2018-12-12",
#    "tentative_end_date"=>"2019-12-12",
#    "project_roles_attributes"=>
#     [{"role_id"=>1,
#       "role_end_date"=>"2018-12-12",
#       "role_start_date"=>"2018-12-12",
#       "project_role_skills_attributes"=>
#        [{"skill_attributes"=>{"skill_type"=>"C++", "id"=>2}}],
#       "project_role_users_attributes"=>[]}]}}

Solution:

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  # ...
  # I added `= params` to default to the `params` value here in the controller
  def params_with_deep_appended_nested_attributes(_params = params)
    modified_params = _params.clone

    _params.each do |k, v|
      if v.is_a?(Array)
        modified_params["#{k}_attributes"] = []

        v.each_with_index do |vv, index|
          if vv.is_a?(ActionController::Parameters)
            modified_params["#{k}_attributes"][index] = params_with_deep_appended_nested_attributes(vv)
          end
        end

        modified_params.delete(k)

      elsif v.is_a?(ActionController::Parameters)
        modified_params["#{k}_attributes"] = params_with_deep_appended_nested_attributes(v)
        modified_params.delete(k)
      end
    end

    modified_params
  end
  # ...
end

# app/controllers/projects_controller.rb
class ProjectsController < ApplicationController
  def create
    @project = Project.new(project_params)

    if @project.save
      # ...
    else
      # ...
    end
  end

  def project_params
    params_with_deep_appended_nested_attributes.require(:project_attributes).permit(
      :project_name, :tentative_start_date, :tentative_end_date,
      project_roles_attributes: [
        :role_id, :role_end_date, :role_start_date,
        project_role_skills_attributes: [
          skill_attributes: [
            :skill_type, :id
          ]
        ],
        project_role_users_attributes: []
      ]
    )
  end
end

Don't forget to define the "nested attributes" in the models:

# app/models/project.rb
class Project < ApplicationRecord
  has_many :project_roles
  accepts_nested_attributes_for :project_roles
end

# app/models/project_role.rb
class ProjectRole < ApplicationRecord
  belongs_to :project
  has_many :project_role_skills
  has_many :project_role_users
  accepts_nested_attributes_for :project_role_skills
  accepts_nested_attributes_for :project_role_users
end

# app/models/project_role_skill.rb
class ProjectRoleSkill < ApplicationRecord
  belongs_to :project_role
  belongs_to :skill
  accepts_nested_attributes_for :skill
end

TODOs:

  • add cahing of the params_with_deep_appended_nested_attributes return value, to avoid running the code each time params_with_deep_appended_nested_attributes is going to be called.

This question interests me. I might find use to this code of mine in the future.

Jay-Ar Polidario
  • 6,463
  • 14
  • 28