I want to be able to support a set of parameters in either the root params object:
?foo=a
params.permit(:foo)
Or in a nested object:
?bar[foo]=a
params.require(:bar).permit(:foo)
I'm passing parameters into that controller from a form object, so the nested object happens naturally, but I'd like to also be able to pass the params in the root object in the query string and support both.
Is there an elegant, non-hacky way to accomplish this?
require is like [] with an exception when the value is missing (much like Hash#fetch). You can instead just use [] directly to get the nested structure if it's there, and fall back to the root params otherwise:
(params[:bar] || params).permit(:foo)
remember that require is different as permit.
The require method ensures that a specific parameter is present. is not necessary to use on nested ones, it makes sure that the required one come in the params or it will throw an error.
you can work with nested attributes like this
params = ActionController::Parameters.new({
person: {
name: "Francesco",
age: 22,
pets: [{
name: "Purplish",
category: "dogs"
}]
}
})
permitted = params.permit(person: [ :name, { pets: :name } ])
permitted.permitted? # => true
permitted[:person][:name] # => "Francesco"
permitted[:person][:age] # => nil
permitted[:person][:pets][0][:name] # => "Purplish"
permitted[:person][:pets][0][:category] # => nil
you can check the documentation on this here
My model has a custom_fields column that serializes an array of hashes. Each of these hashes has a value attribute, which can be a hash, array, string, or fixnum. What could I do to permit this value attribute regardless of its type?
My current permitted params line looks something like:
params.require(:model_name).permit([
:field_one,
:field_two,
custom_fields: [:value]
])
Is there any way I can modify this to accept when value is an unknown type?
What you want can probably be done, but will take some work. Your best bet is this post: http://blog.trackets.com/2013/08/17/strong-parameters-by-example.html
This is not my work, but I have used the technique they outline in an app I wrote. The part you are looking for is at the end:
params = ActionController::Parameters.new(user: { username: "john", data: { foo: "bar" } })
# let's assume we can't do this because the data hash can contain any kind of data
params.require(:user).permit(:username, data: [ :foo ])
# we need to use the power of ruby to do this "by hand"
params.require(:user).permit(:username).tap do |whitelisted|
whitelisted[:data] = params[:user][:data]
end
# Unpermitted parameters: data
# => { "username" => "john", "data" => {"foo"=>"bar"} }
That blog post helped me understand params and I still refer to it when I need to brush up on the details.
I've been trying to do this for the past 10 hours, but it's been useless.
For example:
Event.where(login_screen: Time.now-8.days ..Time.now)
I have an Event table and login_screen is one of the column names. I'm listing them in a drop-down menu and I'd like to take the event names as a variable. It's in the request params like this: params[:segmentation][:first_event]. When I tried to give it like:
Event.where(params[:segmentation][:first_event] Time.now-8.days ..Time.now)
...it didn't work. I tried to use to_sym but that didn't help either.
How can I use a variable as a symbol?
Another question:
What's the difference between :hello and hello: ?
It's alternative syntax for ruby hashes with symbols as keys
Event.where(login_screen: Time.now-8.days ..Time.now)
is the same as
Event.where(:login_screen => Time.now-8.days ..Time.now)
So, if you store key in variable you need use 'hash rocket' syntax:
Event.where(params[:segmentation][:first_event] => Time.now-8.days ..Time.now)
these are the different ways to pass arguments in where clause:--
User.where(["name = ? and email = ?", "Joe", "joe#example.com"])
User.where(["name = :name and email = :email", { name: "Joe", email: "joe#example.com" }])
User.where("name = :name and email = :email", { name: "Joe", email: "joe#example.com" })
using hash:-
User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
User.where({ name: ["Alice", "Bob"]})
User.where({ name: "Joe", email: "joe#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
Is there a shorter way to do the following (
#user.employees.map { |e| { id: e.id, name: e.name } }
# => [{ id: 1, name: 'Pete' }, { id: 2, name: 'Fred' }]
User has_many employees. Both classes inherit from ActiveRecord::Base.
Two things I don't like about the above
It loads employees into memory before mapping,
It's verbose (subjective I guess).
Is there a better way?
UPDATE:
see #jamesharker's solution: from ActiveRecord >= 4, pluck accepts multiple arguments:
#user.employees.pluck(:id, :name)
PREVIOUS ANSWER:
for a single column in rails >= 3.2, you can do :
#user.employees.pluck(:name)
... but as you have to pluck two attributes, you can do :
#user.employees.select([:id, :name]).map {|e| {id: e.id, name: e.name} }
# or map &:attributes, maybe
if you really need lower-level operation, just look at the source of #pluck, that uses select_all
In ActiveRecord >= 4 pluck accepts multiple arguments so this example would become:
#user.employees.pluck(:id, :name)
If you are stuck with Rails 3 you can add this .pluck_all extension : http://meltingice.net/2013/06/11/pluck-multiple-columns-rails/
Another option is to:
#user.employees.select(:id, :name).as_json
#=> [{"id" => 1, "name" => "Pete"}, {"id" => 2, "name" => "Fred"}]
I can imagine that you'd rather have symbolized keys.
If that's the case use the #symbolize_keys method.
#user.employees.select(:id, :name).as_json.map(&:symbolize_keys)
#=> [{id: 1, name: "Pete"}, {id: 2, name: "Fred"}]
See: http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html#method-i-as_json
Add this monkey patch which provides the multi columns pluck functionality in Rails 3.
# config/initializers/pluck_all.rb
if Rails.version[0] == '3'
ActiveRecord::Relation.class_eval do
def pluck(*args)
args.map! do |column_name|
if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s)
"#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(column_name)}"
else
column_name.to_s
end
end
relation = clone
relation.select_values = args
klass.connection.select_all(relation.arel).map! do |attributes|
initialized_attributes = klass.initialize_attributes(attributes)
attributes.map do |key, attr|
klass.type_cast_attribute(key, initialized_attributes)
end
end
end
end
end
Rename the method from pluck to pluck_all if you dont want to override the original pluck functionality
In terms of making a rails 3 method that behaves the same as the Rails 4 pluck with multiple columns. This outputs a similar array (rather than a hashed key value collection). This should save a bit of pain if you ever come to upgrade and want to clean up the code.
module ActiveRecord
class Relation
def pluck_all(*args)
args.map! do |column_name|
if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s)
"#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(column_name)}"
else
column_name.to_s
end
end
relation = clone
relation.select_values = args
klass.connection.select_all(relation.arel).map! do |attributes|
initialized_attributes = klass.initialize_attributes(attributes)
attributes.map do |key, attribute|
klass.type_cast_attribute(key, initialized_attributes)
end
end
end
end
end
Standing on the shoulders of giants and all
The pluck_all method worked well until I'm going to upgrade from Rails 3.2 to Rails 4.
Here is a gem pluck_all to solve this, making pluck_all method support not only in Rails 3 but in Rails 4 and Rails 5. Hope this will help those who are going to upgrade rails version.