Rails4: How to permit a hash with dynamic keys in params?

Ruby on-RailsStrong Parameters

Ruby on-Rails Problem Overview


I make a http put request with following parameters:

> {"post"=>{"files"=>{"file1"=>"file_content_1", > "file2"=>"file_content_2"}}, "id"=>"4"}

and i need to permit hash array in my code. based on manuals I've tried like these:

> params.require(:post).permit(:files) # does not work
> params.require(:post).permit(:files => {}) # does not work, empty hash as result
> params.require(:post).permit! # works, but all params are enabled

How to make it correctly?

UPD1: file1, file2 - are dynamic keys

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

Rails 5.1+

params.require(:post).permit(:files => {})

Rails 5

params.require(:post).tap do |whitelisted|
  whitelisted[:files] = params[:post][:files].permit!
end

Rails 4 and below

params.require(:post).tap do |whitelisted|
  whitelisted[:files] = params[:post][:files]
end

Solution 2 - Ruby on-Rails

In rails 5.1.2, this works now:

params.require(:post).permit(:files => {})

See https://github.com/rails/rails/commit/e86524c0c5a26ceec92895c830d1355ae47a7034

Solution 3 - Ruby on-Rails

I understand that this is an old post. However, a Google search brought me to this result, and I wanted to share my findings:

Here is an alternative solution that I have found that works (Rails 4):

params = ActionController::Parameters.new({"post"=>{"files"=>{"file1"=>"file_content_1", "file2"=>"file_content_2"}}, "id"=>"4"})
params.require(:post).permit(files: params[:post][:files].keys)
# Returns: {"files"=>{"file1"=>"file_content_1", "file2"=>"file_content_2"}}

The difference between this answer and the accepted answer, is that this solution restricts the parameter to only 1 level of dynamic keys. The accepted answer permits multiple depths.

[Edit] Useful tip from comment

"Oh, and you need to verify that params[:post][.files] exists otherwise keys will fail"

Solution 4 - Ruby on-Rails

Orlando's answer works, but the resulting parameter set returns false from the permitted? method. Also it's not clear how you would proceed if you were to later have other parameters in the post hash that you want included in the result.

Here's another way

permitted_params = params.require(:post).permit(:other, :parameters)
permitted_params.merge(params[:post][:files])

Solution 5 - Ruby on-Rails

Here's what we had to do in Rails 5.0.0, hope this helps someone.

files = params[:post].delete(:files) if params[:post][:files]
params.require(:post).permit(:id).tap do |whitelisted|
  whitelisted[:files] = files.permit!
end

Solution 6 - Ruby on-Rails

In my case, there was just one attribute which had dynamic keys,

def post_params
  marking_keys = Set.new
  params[:post][:marking].keys.collect {|ii| marking_keys.add(ii)}
  params.require(:post).permit(:name, marking: marking_keys.to_a)
end

Solution 7 - Ruby on-Rails

Here is another way to get around this:

  def post_params
    permit_key_params(params[:post]) do
      params.require(:post)
    end
  end

  def permit_key_params(hash)
    permitted_params = yield
    dynamic_keys = hash.keys
    dynamic_keys.each do |key|
      values = hash.delete(key)
      permitted_params[key] = values if values
    end
    permitted_params
  end

This should work for post: { something: {...}, something_else: {...} }

Solution 8 - Ruby on-Rails

You can use a temporary variable to build your permitted list like so:

permitted = params.require(:post).permit(:id)
permitted[:post][:files] = params[:post][:files].permit!

Solution 9 - Ruby on-Rails

Here's a simple way to do it (works for rails 5):

  def my_params
    data_params = preset_data_params

    params.require(:my_stuff).permit(
      :some,
      :stuff,
      data: data_params
    )
  end

  def preset_data_params
    return {} unless params[:my_stuff]
    return {} unless params[:my_stuff][:data]

    params[:my_stuff][:data].keys
  end

Solution 10 - Ruby on-Rails

    Send params as array type like name=date[]**strong text**
      def user_post
        dates = params[:date]
        #render json: { 'response' => params }
        i = 0
        dates.each do |date|
          locations = params['location_'+"#{i}"]
          user_names = params['user_'+"#{i}"]
          currency_rates = params['currency_'+"#{i}"]
          flags = params['flag_'+"#{i}"]
          j = 0
          locations.each do |location|
             User.new(user_name: user_names[j], currency_name: flags[j],
             currency_rate: currency_rates[j], currency_flag: flags[j], location: location).save
            j =+ 1
          end
          i =+ 1
        end
   def

Solution 11 - Ruby on-Rails

I could not get any of the many proposed answers to work (Rails 5) without either:

  1. knowing all the hash keys in advance, or
  2. virtually negating the value of strong parameters by allowing arbitrary params.

I'm using this solution.
It uses the standard strong parameters rig to clean up most of the params, and the Hash attribute is added back in explicitly.

# Assuming:
class MyObject < ApplicationRecord
  serialize :hash_attr as: Hash
  #...
end

# MyObjectsController method to filter params:
def my_object_params
  # capture the hashed attribute value, as a Hash
  hash_attr = params[:my_object] && params[:my_object][:hash_attr] ?
      params[my_object][:hash_attr].to_unsafe_h : {}
  # clean up the params
  safe_params = params.require(:my_object).permit(:attr1, :attr2) # ... etc
  # and add the hashed value back in
  safe_params.to_unsafe_h.merge hash_attr: hash_attr
end

Solution 12 - Ruby on-Rails

Let's use a more complicated subset of data:

task: {
  code: "Some Task",
  enabled: '1',
  subtask_attributes: { 
    '1' => { field: 'something', rules: {length_10: true, phone: false, presence: false }} ,
    '2' => { field: 'another', rules: {length_10: true, phone: false, presence: false }} 
  }
}

So we send it to Strong Parameters for processing:

params = ActionController::Parameters.new({
  task: {
    code: "Some Task",
    enabled: '1',
    subtask_attributes: { 
     '1' => { field: 'something', rules: {length_10: true, phone: false, presence: false }} ,
     '2' => { field: 'another', rules: {length_10: true, phone: false, presence: false }} 
    }
  }
})

We will not be able to specify :rules in Strong Params in Rails 4 because it is a hash of data:

permitted = params.require(:task).permit(:code, :enabled, subtask_attributes: [:field, :rules])
Unpermitted parameter: rules
Unpermitted parameter: rules

So what if you want to whitelist specific attributes AND a COLLECTION of hashes of data. The accepted answer does not whitelist specified attributes. You have to do this:

params.require(:task).permit(
    :code, :enabled,
    subtask_attributes: [:field, :rules],
)

# whitelist the validation rules hash
params.require(:task).tap do |whitelisted|
  params[:task][:subtask_attributes].each do |k,v|
    whitelisted[:subtask_attributes][k] = params[:task][:subtask_attributes][k]
    whitelisted.permit!
  end
end

After trying several of the solutions here, none worked. Only aboved worked for nested attributes in a has_many association which contains arbitrary hash data.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionrdoView Question on Stackoverflow
Solution 1 - Ruby on-RailsOrlandoView Answer on Stackoverflow
Solution 2 - Ruby on-RailsDerekView Answer on Stackoverflow
Solution 3 - Ruby on-Railswes.hysellView Answer on Stackoverflow
Solution 4 - Ruby on-RailsToby 1 KenobiView Answer on Stackoverflow
Solution 5 - Ruby on-RailsDamion GomezView Answer on Stackoverflow
Solution 6 - Ruby on-RailsMukarram AliView Answer on Stackoverflow
Solution 7 - Ruby on-RailsSashView Answer on Stackoverflow
Solution 8 - Ruby on-RailsCamView Answer on Stackoverflow
Solution 9 - Ruby on-RailsKrzysztof KarskiView Answer on Stackoverflow
Solution 10 - Ruby on-Railsvikas palView Answer on Stackoverflow
Solution 11 - Ruby on-RailsTom WilsonView Answer on Stackoverflow
Solution 12 - Ruby on-RailsDaniel ViglioneView Answer on Stackoverflow