Rails Strong Parameters either in root or nested object - ruby-on-rails

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

Related

Grape Entity for a Hash with string keys not working

I am using 'grape-entity', '~> 0.7.1'
I have a hash in the format:
temp_data = [{sheet_index: 0, other_names: []},{'sheet_index' => 1, 'other_names': ['a']}]
And I have the following entities
class Sheet < Grape::Entity
expose :sheet_index, documentation: {type: Integer, desc: "Sheet index"}
expose :other_names, documentation: {type: Array, desc: "Other names"}
end
class Sheets < Grape::Entity
present_collection true
expose :items, as: 'sheet_history', using Entities::Sheet
end
# response from the entities
present temp_data, with: Entities::Sheets
Now I need to make sure that no matter the type of keys in my Hash it should still give me the correct output for the above case
expected_response = {"sheet_history" => [{"sheet_index"=>0, "other_names"=>[]}, {"sheet_index"=>1, "other_names"=>["a"]}]}
but the response I am getting is in the format below
actual_response = {"sheet_history" => [{"sheet_index"=>0, "other_names"=>[]}, {"sheet_index"=>nil, "other_names"=>nil}]}
so in the actual response sheet_index and other_names of the second element are nil because their keys were Strings, not Symbols. (Refer to temp_data.)
I have referred to https://github.com/ruby-grape/grape-entity/pull/85 to get the above implementation but still am not able to make it work without using HashWithIndifferentAccess or OpenStructs
You are missing a colon after using, but I wouldn't set up multiple entities like that as it's likely to result in wonky behavior. Try this:
# Dummy definition of your class
class Item
include ActiveModel::Serialization
attr_accessor :sheet_index
attr_accessor :other_names
def initialize(index, names)
#sheet_index = index
#other_names = names
end
end
items = []
items << Item.new(0, [])
items << Item.new(1, ['a'])
=> [
#<Item:0x00007f860f740e40 #other_names=[], #sheet_index=0>,
#<Item:0x00007f860f513618 #other_names=["a"], #sheet_index=1>
]
# Entity Definition
class Sheet < Grape::Entity
# The first arg here is the key to use for a collection,
# the second is the key to use for a single object
root 'sheet_history', 'sheet_history'
expose :sheet_index, documentation: {
type: Integer,
desc: "Sheet index" # Plz use locales
}
expose :other_names, documentation: {
type: Array,
desc: "Other names" # Plz use locales
}
end
# Test it
representation = Sheet.represent(items)
=> {
"sheet_history"=>[
#<Sheet:70106854276160 sheet_index=0 other_names=[]>,
#<Sheet:70106854275680 sheet_index=1 other_names=["a"]>
]
}
# This is just more a more readable, but as you can see it's
# both mapping all the attributes correctly and
# setting the root key that you wanted:
representation['sheet_history'].map do |r| r.serializable_hash end
=> [
{
:sheet_index=>0,
:other_names=>[]
},
{
:sheet_index=>1,
:other_names=>["a"]
}
]
# Endpoint
get do
items = current_user.items # or whatever
present items, with: Entities::Sheet
end
You can send your array of hashes to the represent method, but it doesn't like the stringified key. Ideally you should be passing DB objects to your entity instead of hashes but, if you for some reason cannot, I would pass temp_data.map(&:symbolize_keys) as your argument to the entity to ensure the top-level keys in the hash it's parsing are symbols.

Rails permit nested hash attributes

The controller receives JSON object
{
user: {
name: "string",
details: {
info1: "string",
info2: []
}
}
}
During permission controller knows that can permit some defined fields - as name - and hash field details with all nested attributes - also with arrays. What is the correct solution for this situation?
BAD SOLUTIONS
permit cannot be used, because I must select user permitted fields
tap do |whitelisted| cannot be used, because it doesn't make that fields "permit"
case below cannot be user, because with arrays doesn't work
details_keys = params[:user][:details].keys
params.require(:user).permit(:name, details: details_keys)
If you want to permit a key having an array of permitted scalar values, then simply map the key to an empty array:
params.permit(key: [])
The permitted scalar types are String, Symbol, NilClass, Numeric, TrueClass, FalseClass, Date, Time, DateTime, StringIO, IO, ActionDispatch::Http::UploadedFile, and Rack::Test::UploadedFile
So when an array contains some non-scalar values like a hash, then you have to go further by permitting the nested keys too in the array.
Say, you have the following structure:
{
key: [
{
attr1: 'string',
attr2: 10
},
{
attr1: 'another string',
attr2: 100
}
]
}
then the permission goes in this way:
params.permit(key: [:attr1, :attr2])
Now let's assume your case looks like:
{
user: {
name: "sting",
details: {
info1: "string",
info2: [1, true, :sym] // assume it contains only permitted scalar values
}
}
}
the permission will be:
params.require(:user).permit(:name, details: [:info1, info2: []])
To automate this, lets assume details has 5 attributes with permitted scalar values and 3 more array attributes that also have only scalar values.
First pluck the 5 non-array keys of details:
non_array_keys = params[:user][:details].reject { |_, v| v.class == Array }.keys
Next the 3 array keys inside details:
array_keys = params[:user][:details].select { |_, v| v.class == Array }.keys.map { |k| { k => [] } }
Now the details_keys will be ready by:
details_keys = non_array_keys << array_keys
details_keys.flatten!
Final permission will look like:
params.require(:user).permit(:name, details: details_keys)
If the nested arrays would contain non-scalar values, then I guess you have got enough idea by this point on how to adapt to your changes!
DISCLAIMER: this automation is not appreciated because, instead of doing all these, a simple invocation of params.require(:user).permit! would suffice. But this marks the :user parameters hash and any sub-hash of it as permitted and does not check for permitted scalars, anything is accepted. Extreme care should be taken when using permit!, as it will allow all current and future model attributes to be mass-assigned.
For details, I would strongly suggest to look at the Rails official guide covering Strong Parameters in details.
Adding empty array might work. You can switch off validation for nested attributes?! Are you creating dynamic random input fields which you do not control?
Do you want to pass dynamic fields?
If that is the case, the following may work
Configuring strong parameters for dynamic keys

Rails - permit a param of unknown type (string, hash, array, or fixnum)

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.

Rails strong params with "dot" in name

I need to permit a parameter in Rails 4 which has a dot in it's name:
My params hash looks like the following:
{
"dictionary_objects.id" => [
"102", "110", "106"
]
}
I can get param value:
>> params['dictionary_objects.id']
=> [
[0] "102",
[1] "110",
[2] "106"
]
But when I try to permit it, it returns an empty hash:
>> params.permit('dictionary_objects.id')
Unpermitted parameters: dictionary_objects.id
=> {}
Does anybody know how can I permit params with a dot in it's name?
Thanks.
I think it's just failing to permit it because you've got a collection and you're telling it to permit a single value parameter. If you use:
params.permit(:'dictionary_objects.id' => [])
then all should be well.
for edge cases I recommend a very useful workaround:
params.slice('dictionary_objects.id').permit!
So you do whitelist keys and dont become crazy because of strong params.
sidenote:
rails is builtin to receive args like dictionary_object_ids for has_many relationships, you could leverage this instead.

Allow an array of hashes with a dynamic hash (hstore) inside

I'm stuck with strong_parameters and this array of hashes with a dynamic hash (hstore) inside.
The structure is the following:
{ contact_sources: [
{ id: 1, filled_fields: { randomstuff: 'randomdata', dunno: 123 } },
{ id: 2, filled_fields: { blah: 'blabla', dunno: 9043 } }
] }
So, my main attempt is the following:
params.permit(contact_sources: [{:filled_fields => []}, 'id'])
Which doesn't return filled_fields. Any suggestion on how to deal with it?
Update 1:
I have the following model:
class ContactSource < ActiveRecord::Base
# Fields: id:integer, filled_fields:hstore
end
In my action, I'm submitting multiple records at once (mass update), so I have an array of contact_source, but actually they don't belong to anything, it's just a mass update.
Looks like it's not possible to do it with "plain" strong_parameters syntax. The only option you have is to actually, after filtering, re-add those values with a loop. I know it's terrible but it's the only way right now. I submitted a bug to Rails actually.

Resources