Rails - Strong Parameters - Nested Objects - ruby-on-rails

I've got a pretty simple question. But haven't found a solution so far.
So here's the JSON string I send to the server:
{
"name" : "abc",
"groundtruth" : {
"type" : "Point",
"coordinates" : [ 2.4, 6 ]
}
}
Using the new permit method, I've got:
params.require(:measurement).permit(:name, :groundtruth)
This throws no errors, but the created database entry contains null instead of the groundtruth value.
If I just set:
params.require(:measurement).permit!
Everything get's saved as expected, but of course, this kills the security provided by strong parameters.
I've found solutions, how to permit arrays, but not a single example using nested objects. This must be possible somehow, since it should be a pretty common use case. So, how does it work?

As odd as it sound when you want to permit nested attributes you do specify the attributes of nested object within an array. In your case it would be
Update as suggested by #RafaelOliveira
params.require(:measurement)
.permit(:name, :groundtruth => [:type, :coordinates => []])
On the other hand if you want nested of multiple objects then you wrap it inside a hash… like this
params.require(:foo).permit(:bar, {:baz => [:x, :y]})
Rails actually have pretty good documentation on this: http://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-permit
For further clarification, you could look at the implementation of permit and strong_parameters itself: https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/metal/strong_parameters.rb#L246-L247

I found this suggestion useful in my case:
def product_params
params.require(:product).permit(:name).tap do |whitelisted|
whitelisted[:data] = params[:product][:data]
end
end
Check this link of Xavier's comment on github.
This approach whitelists the entire params[:measurement][:groundtruth] object.
Using the original questions attributes:
def product_params
params.require(:measurement).permit(:name, :groundtruth).tap do |whitelisted|
whitelisted[:groundtruth] = params[:measurement][:groundtruth]
end
end

Permitting a nested object :
params.permit( {:school => [:id , :name]},
{:student => [:id,
:name,
:address,
:city]},
{:records => [:marks, :subject]})

If it is Rails 5, because of new hash notation:
params.permit(:name, groundtruth: [:type, coordinates:[]]) will work fine.

Related

Rails Strong Params how to Permit a nested Array

I have the following params:
params={"data"=>
{"type"=>"book",
"id"=>14,
"attributes"=>
{"id"=>14,
"created_at"=>"2022-06-27 21:15:39",
"title"=>"sdfdsf",
"targeting"=> { "release_times"=>[["4:00", "5:00"], ["5:00", "6:00"]],
"days"=>["Monday", "Tuesday", "Wednesday"],
"gender"=>["male", "female"]
}
}
}
When I use this, I can get every value but release_times is always null:
When I use this:
safe_params = params.require(:data).permit( attributes: [:id, :created_at, :title, { targeting: {} }])
How can I extract the release times value?
I tried doing this
safe_params = params.require(:data).permit( attributes: [:id, :created_at, :title, { targeting: [:days, :gender, release_times:[]]}])
But I get the error:
Validation failed: Targeting gender should be a list of values, Targeting days should be a list of values
How can I extract all the values from targeting including the release_times?
As Ruby on Rails API states, when using ActionController::Parameters you want to declare that a parameter should be an array (list) by mapping it to a empty array. Like you did with release_times.
You should permit targeting params with [days: [], gender: []] instead of [:days, :gender]. This should solve the error.
But even them, release_times is an array of arrays, which I believe is not supported at the moment (there is an old issue for it).
One way you could bypass this would be by changing the way you're communicating release_times. Using an arrays of hashes instead of nested arrays.
From this:
"release_times"=>[["4:00", "5:00"], ["5:00", "6:00"]]
To this (or something similar):
"release_times"=>[{"start" => "4:00", "end"=>"5:00"}, {"start" =>"5:00", "end" => "6:00"}]
That way, you could do this:
safe_params = params.require(:data).permit(attributes: [:id, :created_at, :title, { targeting: [days: [], gender: [], release_times: [:start, :end]] }])
Exactly how you would implement that is up to you, but I hope it helps.
**Also, there was a typo with release_times.
You can do some testing yourself. Open rails c and do something like this:
param = ActionController::Parameters.new("targeting"=> { "release_times"=>[["4:00", "5:00"], ["5:00", "6:00"]]})
param.require(:targeting).permit(release_times: []) # > Doesn't return times.
new_param = ActionController::Parameters.new("targeting"=> { "release_times"=>[{"start" => "4:00", "end"=>"5:00"}, {"start" =>"5:00", "end" => "6:00"}] })
new_param.require(:targeting).permit(release_times: [:start, :end]) # > Return times.
Just an observation, using permit! would work. But as strong params doc says:
Extreme care should be taken when using permit! as it will allow all
current and future model attributes to be mass-assigned.
So you could try to slice arguments yourself and them permit! - but I can't tell you that's the way to go.
Learn more about Mass Assignment Vulnerability here.

Undefined method `with_indifferent_access' for #<Array: for nested model in rails

I have a nested form with three models vehicle, vehicle_key_feature and vehicle_detail where vehicle_key_feature and vehicle_detail has one to one relation with vehicle. It is working fine when I use strong params following way -
params.require(:vehicle).permit(:title, vehicle_key_feature_attributes: [:android_auto], vehicle_detail_attributes: [:tech_specs])
since I have lots of strong params for all three models, I would like to keep nested attributes params in a separate method and merge them with vehicle_params. But it's showing me following error
undefined method with_indifferent_access' for #Array
I have written the following codebase, I checked console and params.inspect which is in expected form.
def vehicle_params
params.require(:vehicle).permit(
:title, :category_id, :make, :model, :model_number, :mileage, :exterior, :interior, :transmission, :engine_type, :drivetrain, :fuel_efficiency, :engine, :condition, :description, :dealer_id
)
.merge(vehicle_key_feature_attributes)
.merge(vehicle_detail_attributes)
end
def vehicle_key_feature_attributes
{
vehicle_key_feature_attributes: [
:android_auto, :apple_carplay, :backup_camera, :blind_spot_monitor, :bluetooth,
:forward_collision_warning, :interior_accents, :keyless_entry, :side_impact_air_bags
]
}
end
def vehicle_detail_attributes
{
vehicle_detail_attributes: [
:exterior, :interior, :entertainment, :mechanical, :safety, :tech_specs, :details
]
}
end
What is the best solution to extract these two nested attributes in two separate methods?
Your second snippet is doing something else. To replicate what the first one does, add your hashes to permit's argument list.
params.require(:vehicle).permit(:title, ..., vehicle_key_feature_attributes.merge(vehicle_detail_attributes))

Update nested resource through API/Json

I'm building an API endpoint to update a model. i can update every column except nested resources, I've tried different approachs but nothing seems to work
This is the JSON i'm trying to send to the server
{
"reservation": {
"reservation_dates": [
{
"is_desirable": true,
"date": "5-10-2019"
}
]
}
}
I'm getting a unpermitted_param from reservation_date although i've added it to my
def permitted_attributes_for_update
params.require(:reservation).permit(:date, :time, :comment, :budget, :currency, :status,
:general_text, :idea_text, :artist_text, :desired_city,
:desired_country, :desired_googleid, :studio_id, :artist_id,
:tattoos, reservation_dates: [], general_url_array: [],idea_url_array: [],
artist_url_array: [])
end
I want to either be able to update directly from the JSON or at least permit the array so I can use later on my UpdateService
Thanks for any help
edit: this is the error I'm getting
You need to specify what is permitted in reservation_dates:
params.require(:reservation).permit(reservation_dates_attributes: [:is_desirable, : date], ...)
However, be careful that if your reservation has_many reservation_dates, then this will cause conflicts: reservation_dates are supposed to be instances of ReservationDate but you are providing hashes. The rails' way is to use reservation_dates_attributes instead of reservation_dates.

Rails 4 Unpermitted Parameters for Array

I have an array field in my model and I'm attempting to update it.
My strong parameter method is below
def post_params
params["post"]["categories"] = params["post"]["categories"].split(",")
params.require(:post).permit(:name, :email, :categories)
end
My action in my controller is as follows
def update
post = Post.find(params[:id]
if post and post.update_attributes(post_params)
redirect_to root_url
else
redirect_to posts_url
end
end
However, whenever I submit the update the post, in my development log I see
Unpermitted parameters: categories
The parameters passed through is
Parameters: {"utf8"=>"✓", "authenticity_token"=>"auth token", "id"=>"10",
"post"=>{"name"=>"Toni Mitchell", "email"=>"eileen_hansen#hayetokes.info", "categories"=>",2"}}
I want to think it has something to do with the fact that the attribute categories is an array since everything else looks fine. Then again, I could be wrong. So, what's wrong with my code and why is not letting me save the categories field when clearly it is permitted to do so? Thanks.
Try this
params.require(:post).permit(:name, :email, :categories => [])
(Disregard my comment, I don't think that matters)
in rails 4, that would be,
params.require(:post).permit(:name, :email, {:categories => []})
The permitted scalar types are String, Symbol, NilClass, Numeric, TrueClass, FalseClass, Date, Time, DateTime, StringIO, IO, ActionDispatch::Http::UploadedFile and Rack::Test::UploadedFile.
To declare that the value in params must be an array of permitted scalar values map the key to an empty array:
params.permit(:id => [])
This is what the strong parameters documentation on Github says:
params.require(:post).permit(:name, :email, :categories => [])
I hope this works out for you.
I had the same problem, but simply adding array to permit was not enough. I had to add type, too. This way:
params.require(:transaction).permit(:name, :tag_ids => [:id])
I am not sure if this is perfect solution, but after that, the 'Unpermitted parameters' log disappeared.
I found hint for that solution from this excellent post: http://patshaughnessy.net/2014/6/16/a-rule-of-thumb-for-strong-parameters
If there are multiple items and item_array inside parameters like this-
Parameters {"item_1"=>"value 1", "item_2"=> {"key_1"=> "value A1",
"key_2"=>["val B2", "val C3"]} }
There we have array inside item_2.
That can be permit as below-
params.permit(item_2: [:key_1, :key_2 => [] ])
Above saved my day, may be helpful for you too.
I had the same problem but in my case I had also to change from:
<input type="checkbox" name="photographer[attending]" value="Baku">
to:
<input type="checkbox" name="photographer[attending][]" value="Baku">
Hope this is helping someone.

rails 4 strong params + dynamic hstore keys

I'm having a problem overcoming the new strong params requirement in Rails 4 using Hstore and dynamic accessors
I have an Hstore column called :content which I want to use to store content in multiple languages, ie :en, :fr, etc. And I don't know which language upfront to set them in either the model or the controller.
store_accessor :content, [:en, :fr] #+226 random other il8n languages won't work.
How can I override strong params (or allow for dynamic hstore keys) in rails 4 for one column?
params.require(:article).permit(
:name, :content,
:en, :fr #+226 random translations
)
Short of...
params.require(:article).permit!
which of course does work.
If I understand correctly, you would like to whitelist a hash of dynamic keys. You can use some ruby code as follows to do this:
params.require(:article).permit(:name).tap do |whitelisted|
whitelisted[:content] = params[:article][:content]
end
This worked for me, hope it helps!
I'm doing something similar and found this to be a bit cleaner and work well.
Assuming a model called Article you can access your :content indexed stored_attributes like this: Article.stored_attributes[:content]
So your strong params looks like this:
params.require(:article).permit(:name, content: Article.stored_attributes[:content])
Assuming your params are structured like: { article => { name : "", content : [en, fr,..] } }
As people have said, it is not enough to permit the :content param - you need to permit the keys in the hash as well. Keeping things in the policy, I did that like so:
# in controller...
def model_params
params.permit(*#policy.permitted_params(params))
end
# in policy...
def permitted_params(in_params = {})
params = []
params << :foo
params << :bar
# ghetto hack support to get permitted params to handle hashes with keys or without
if in_params.has_key?(:content)
content = in_params[:content]
params << { :content => content.empty? ? {} : content.keys }
end
end

Resources