Laurent
Laurent

Reputation: 2168

Apparent issue with dup and clone in Rails params

I'm in a Rails controller. I try to take some params and change some of the datas to update a model, but I also want to keep the original params untouched.

The logical way to go is to use clone or dup but whatever I try, it fails and changes the original hash.

# Original product_params which is set as params.require(:product)
{"name"=>"Product 2",
 "brand"=>"Brand 2",
 "desc"=>"Placeat a sunt eos incidunt temporibus.\r\n\r\nReprehenderit repudiandae amet quibusdam dolorem et. Itaque commodi at.",
 "hs_code"=>"12212121",
 "options_attributes"=>
  {"0"=>{"name"=>"hkjlVariation 4", "suboptions_attributes"=>{"0"=>{"name"=>"Chkjlhoice 0", "id"=>"582209026b710eded24ecd12"}}, "id"=>"582209026b710eded24ecd13"},
   "1"=>
    {"name"=>"hhVhariation h5kkk",
     "suboptions_attributes"=>{"0"=>{"name"=>"Choice 0kh", "id"=>"582209026b710eded24ecd14"}, "1"=>{"name"=>"hkjChoice 1", "id"=>"582209026b710eded24ecd16"}, "2"=>{"name"=>"kkk"}},
     "id"=>"582209026b710eded24ecd15"},
   "2"=>{"name"=>"lh", "suboptions_attributes"=>{"0"=>{"name"=>"klhj"}}}}}

# Method to change the `suboptions_attributes` to `nil`
def product_params_without_suboptions
  copy = product_params.dup
  copy.tap do |product_param|
    product_param[:options_attributes].each do |key, option_attribute|
      unless option_attribute[:suboptions_attributes].nil?
        option_attribute[:suboptions_attributes] = nil
      end
    end
  end
end

# We define product_params
def product_params
  params.require(:product).permit!
end

The result of product_params_without_suboptions is correct. It set all the option_attribute to nil but when I try to call params or product_params it also changed there. Why isn't dup working here ?

Upvotes: 3

Views: 1585

Answers (3)

Aleks
Aleks

Reputation: 5330

For me applying only .to_h also was causing the original parameters to change. The solution that worked for me was:

params.to_a.to_h.deep_clone

Upvotes: 0

Laurent
Laurent

Reputation: 2168

Working solution

After some investigation, I realized params and its relatives are a ActionController::Parameters class and not a simple hash output, which's logical to manipulate it with require, except, etc. I first thought it would fully act like a hash but it's not the case.

Doing a clone, dup, deep_dup on it will just copy the class and because of the magic of rails, changing anything in this copy will lead to a global change. I don't know the exact reason of this behaviour but I guess there's some class variables / singleton pattern going on.

The simplest solution I found was to convert it to a hash via to_h so it cut out this problem. You can do params_hash = params.to_h and then manipulate the hash and it won't have any consequence on the original params object.

Upvotes: 7

Eric Duminil
Eric Duminil

Reputation: 54223

If you have a nested hash and want to duplicate everything, you might be interested by deep_dup

Your product_params is an Array of Hashes of Hash, right? Your example had strings as keys, but your code had symbols. Adapt accordingly.

product_params = [{"name"=>"Product 2",
                   "brand"=>"Brand 2",
                   "desc"=>"Placeat a sunt eos incidunt temporibus.\r\n\r\nReprehenderit repudiandae amet quibusdam dolorem et. Itaque commodi at.",
                   "hs_code"=>"12212121",
                   "options_attributes"=>
{"0"=>{"name"=>"hkjlVariation 4", "suboptions_attributes"=>{"0"=>{"name"=>"Chkjlhoice 0", "id"=>"582209026b710eded24ecd12"}}, "id"=>"582209026b710eded24ecd13"},
 "1"=>
{"name"=>"hhVhariation h5kkk",
 "suboptions_attributes"=>{"0"=>{"name"=>"Choice 0kh", "id"=>"582209026b710eded24ecd14"}, "1"=>{"name"=>"hkjChoice 1", "id"=>"582209026b710eded24ecd16"}, "2"=>{"name"=>"kkk"}},
 "id"=>"582209026b710eded24ecd15"},
 "2"=>{"name"=>"lh", "suboptions_attributes"=>{"0"=>{"name"=>"klhj"}}}}}]

require 'pp'

copy = product_params.deep_dup
copy.each do |product_param|
  product_param["options_attributes"].each do |key,option_attribute|
    option_attribute.delete("suboptions_attributes")
  end
end

pp product_params
puts "--------"
pp copy

# [{"name"=>"Product 2",
#   "brand"=>"Brand 2",
#   "desc"=>
#    "Placeat a sunt eos incidunt temporibus.\r\n\r\nReprehenderit repudiandae amet quibusdam dolorem et. Itaque commodi at.",
#   "hs_code"=>"12212121",
#   "options_attributes"=>
#    {"0"=>
#      {"name"=>"hkjlVariation 4",
#       "suboptions_attributes"=>
#        {"0"=>{"name"=>"Chkjlhoice 0", "id"=>"582209026b710eded24ecd12"}},
#       "id"=>"582209026b710eded24ecd13"},
#     "1"=>
#      {"name"=>"hhVhariation h5kkk",
#       "suboptions_attributes"=>
#        {"0"=>{"name"=>"Choice 0kh", "id"=>"582209026b710eded24ecd14"},
#         "1"=>{"name"=>"hkjChoice 1", "id"=>"582209026b710eded24ecd16"},
#         "2"=>{"name"=>"kkk"}},
#       "id"=>"582209026b710eded24ecd15"},
#     "2"=>{"name"=>"lh", "suboptions_attributes"=>{"0"=>{"name"=>"klhj"}}}}}]
# --------
# [{"name"=>"Product 2",
#   "brand"=>"Brand 2",
#   "desc"=>
#    "Placeat a sunt eos incidunt temporibus.\r\n\r\nReprehenderit repudiandae amet quibusdam dolorem et. Itaque commodi at.",
#   "hs_code"=>"12212121",
#   "options_attributes"=>
#    {"0"=>{"name"=>"hkjlVariation 4", "id"=>"582209026b710eded24ecd13"},
#     "1"=>{"name"=>"hhVhariation h5kkk", "id"=>"582209026b710eded24ecd15"},
#     "2"=>{"name"=>"lh"}}}]

Upvotes: 3

Related Questions