I have an array of users I want to save in my database. An example when shown in the console after a POST is made:
"users"=>[{"name"=>"Job", "email"=>"*****"}, {"name"=>"Ed", "email"=>"****"}]
No nested attributes going on here. How to format strong params for this?
def user_params
params.fetch(:user, {}).permit(:name, :email, ...)
end
This may work for saving a single user but I'm passing an array of users. I'm currently storing the user in a "dirty" manner:
users = params[:users]
users.each do |user|
User.create(name: user[:name], email: user[:email])
end
I'd like to refactor and do something like:
User.create(user_params)
To permit a param key containing an array of hashes you pass the name as the key and an array of permitted attributes:
def user_params
params.permit(users: [:name, :email, ...])
.fetch(:users, [])
end
The output will be an array of ActionController::Parameters instances or an empty array if the key is not present:
[#<ActionController::Parameters {"name"=>"Job", "email"=>"*****"} permitted: true>,
#<ActionController::Parameters {"name"=>"Ed", "email"=>"****"} permitted: true>]
So i have the following params permitted.
p = params.permit(:a, :b, :c, :lines => [:location_id, :quantity, :product => [:id]])
In my controller action, i add to the lines param the data i've permitted.
p['lines'] << {"product"=>{"id"=>"123456"}, "quantity"=>"2", "location_id"=>"123456"}
This is how the params looked after they've been changed.
puts params['lines']
#> [<ActionController::Parameters {"product"=>{"id"=>"123456"}, "quantity"=>"2", "location_id"=>"123456"} permitted: false>]
But as you can see it's not permitted. What am i missing here? I am using Rails 5.
To get permitted (whitelisted) params, you always have to make sure that you call the permitted version, p in your case, whenever params changes.
The difference between params and p is that params.permit(...) returns a permitted copy of itself and assigns it to p. So params permission state remains unchanged.
Try with puts p['lines'] instead of puts params['lines'] to see if you get the desired result.
I defined a class for User, which takes a hash as parameter,
class User
attr_accessor :name, :email
def initialize(attributes = {})
#name = attributes[:name]
#email = attributes[:email]
end
def formatted_email
"#{#name} <#{#email}>"
end
end
While initializing in console using parenthesis and empty space syntax for parameter, the behaviours are different, the second one didn't receive the attributes, but if I define a hash first and then initialize with the hash as the parameter, it works well now, why does this happen?
2.0.0-p451 :013 > example = User.new(name:'John', email:'john#example.com')
=> #<User:0x007fbcdb57e140 #name="John", #email="john#example.com">
2.0.0-p451 :014 > example = User.new {name:'John', email:'john#example.com'}
=> #<User:0x007fbcdb599080 #name=nil, #email=nil>
2.0.0-p451 :021 > hash = {name: 'John', email: 'john#example.com'}
=> {:name=>"John", :email=>"john#example.com"}
2.0.0-p451 :022 > User.new hash
=> #<User:0x007fbcdc4af8a8 #name="John", #email="john#example.com">
Look the code :
class User
def initialize(attributes = {})
p attributes # I put it to debug the hash **attributes** after each *new* call.
#name = attributes[:name]
#email = attributes[:email]
end
def formatted_email
"#{#name} <#{#email}>"
end
end
# here you did pass the method argument as a Hash.
User.new(name:'John', email:'john#example.com')
# >> {:name=>"John", :email=>"john#example.com"} # output of attributes hash
# you got #<User:0x007fbcdb57e140 #name="John", #email="john#example.com">, as the
# hash **attributes** is, {:name=>"John", :email=>"john#example.com"}. So inside the
# initialize method
# #name = "John" as , attributes[:name] is "John"
# #email = "john#example.com" as, attributes[:email] is "john#example.com"
# here you didn't pass the method argument as a Hash, rather a block.
User.new {name:'John', email:'john#example.com'}
# >> {} # output of attributes hash is empty hash.
# you got #<User:0x007fbcdb599080 #name=nil, #email=nil>, as *attributes* is empty.
# so inside the initialize method #name = nil happened, as attributes[:name] is nil.
# #email = nil, as attributes[:email] is nil.
# here you did pass the method argument as a Hash.
hash = {name: 'John', email: 'john#example.com'}
User.new hash # output of attributes hash
# >> {:name=>"John", :email=>"john#example.com"}
In Ruby, the method calling syntax is
meth_name(argument_list_of_object/objects) { #block }
meth_name(argument_list_of_object/objects)
meth_name argument_list_of_object/objects. This is valid when you are not passing block,otherwise error will be thorwn
It means in Ruby, { .. } after method call, parsed as if, you are sending a block to the method, not as if you are passing the hash as argument.
One more simple example to illustrate this theory :
def foo(arg = 10)
p arg
end
foo 12 # => 12
foo(12) # => 12
foo { 12 } # => 10 default value
foo "arg" { 12 }
# ~> -:8: syntax error, unexpected '{', expecting end-of-input
# ~> foo "arg" { 12 }
# ~>
Summarizing :
# you are passing a 2 key/value argument, which in runtime will create a hash
# {name:'John', email:'john#example.com'}, then will be assigned to
# local variable attributes of the method initialize.
User.new name:'John', email:'john#example.com'
# => #<User:0x90f5a24 #email="john#example.com", #name="John">
# same as above
User.new(name:'John', email:'john#example.com')
# => #<User:0x90f4a5c #email="john#example.com", #name="John">
# simply passing a block to the method.
User.new {name:'John', email:'john#example.com'}
# => #<User:0x90f7450 #email=nil, #name=nil>
Hope this helps.
This is due to the empty space, following should print correct, I have removed white space and added parentheses
example = User.new({name: 'John', email: 'john#example.com'})
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
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
In rails 5.1.2, this works now:
params.require(:post).permit(:files => {})
See https://github.com/rails/rails/commit/e86524c0c5a26ceec92895c830d1355ae47a7034
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"
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])
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
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
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: {...} }
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!
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
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
I could not get any of the many proposed answers to work (Rails 5) without either:
knowing all the hash keys in advance, or
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
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.
I know this is an old post, one of many with different ways to update a serialize hash field. I thought I give my version that I accidently found by piecing together some methods. I'll just use my application. This is Rails 7.0.4 and Ruby 3.0. I also use slim templates.
I have a Taxable model that contains semi-persistent tax rates for different Departments. All items are Sales Tax taxable, but in my case, Liquor adds an additional tax. The Taxable table only has two fields with tax being a serialized JSON field.
create_table "taxables", force: :cascade do |t|
t.date "date"
t.string "tax"
...
end
If a Tax is changed or added, the I would add a new record to reflect the change that took place on some date. Any ticket that had a tax in the past would use the record that is the earliest record before the ticket date. Anything new will the new changed record
The Taxable model has a constant that names all taxes that may be used:
TaxesUsed = %w(sales county federal city liquor)
The records would be something like:
[#<Taxable:0x0000000111c7bfc0
id: 2,
date: Sun, 01 Jan 2023,
tax: {"sales"=>"8.0", "county"=>"2.0", "federal"=>"0.0", "city"=>"0.0", "liquor"=>"3.0"} ...
#<Taxable:0x0000000111c7b980
id: 3,
date: Fri, 01 Jan 2021,
tax: {"sales"=>"8.0", "county"=>"2.0", "federal"=>"0.0", "city"=>"0.0", "liquor"=>"4.0"}...
]
I initially had a kludge that worked, which was creating the hash from some un-permitted parameter and updating the record. I then found mention of using form_with to describe the Tax field and to my surprise it worked! The form:
= form_with(model: #taxable) do |form|
div
= form.label :date, style: "display: block"
= form.date_field :date
div
= form.label :tax, style: "display: block", class:"font-bold"
= form.fields_for :tax do |tax|
# #taxable.tax is the existing serialize tax hash or a new default hash
- #taxable.tax.each do |k,v|
div.flex.gap-2
div.w-36.font-bold.text-right = k
div
= tax.text_field k, value:v
div[class="#{btn_submit}"]
= form.submit
I had to define a new taxable_parmam that states that :tax is a Hash
def taxable_params
params.require(:taxable).permit(:date, :tax => {})
end
Submitting the form give me params:
Parameters: {"authenticity_token"=>"[FILTERED]",
"taxable"=>{"date"=>"2021-01-01", "tax"=>{"sales"=>"8.0",
"county"=>"2.0", "federal"=>"0.0", "city"=>"0.0",
"liquor"=>"4.0"}}, "commit"=>"Update Taxable", "id"=>"3"}
and it works! I forgot about form_with but this is about a simple as you can get just using plain ol Rails.
Update: I forgot that stuff coming from form fields is text. I had to get the params to a new hash, change the float values (percents) and update using the new hash
I have a hash in Ruby:
params={"username"=>"test"}
I want to add another associative array like:
params["user"]={"name"=>"test2"}
so params should become
params={"username"=>"test","user"=>{"name"=>"test2"}}
but when I post this params to a url, I get:
params[:user][:name] # => nil
when I dump the user data:
params[:user] # => ['name','test2']
what I want is
params[:user] # => output {'name'=>'test2'}
what am I doing wrong? thanks for help.
You're just using wrong key, you think that :user and "user" are the same, which is not.
params["user"]["name"] #=> "test2"
params["user"] #=> {"name"=>"test2"}
UPDATE from Naveed:
:user is an instance of Symbol class while "user" is instance of String
You have created a hash with keys of type string and trying to access with symbol keys. This works only with class HashWithIndifferentAccess.
If you want to achieve the same convert your hash to HashWithIndifferentAccess by using with_indifferent_access method,
> params = {"username"=>"test", "user"=>{"name"=>"test2"}}
=> {"username"=>"test", "user"=>{"name"=>"test2"}}
> params[:user][:name]
=> nil
>params = params.with_indifferent_access
> params[:user][:name]
=> "test2"
Update: request params is an instance of HashWithIndifferentAccess
The following should work:
params["user"]
params={"username"=>"test"}# params is not array nor associative array its a hash
you can add key value pair in hash by
params["key"]="value"
key and value both can be object of any class,be sure you use same object as key to access value or take a look at
HashWithIndifferentAccess
now
params["user"]={"name"=>"blah"}
params["user"] # => {"name"=>"blah"}
params["user"]["name"] # => "blah"