How can I access these nested attributes in parameters? - ruby-on-rails

I'm trying to get the value of one of the parameters sent, but I'm using nested attributes and when creating an object, the nested attributes have a kind of token, which prevents me from accessing the attributes through the key.
How can I access these nested attributes in parameters?
Parameters: {"utf8"=>"✓", "authenticity_token"=>"Xodtke+W96O+hiEH8AvnK4F5XZF5U9b8148YyVTsZX5XNlkdOxmv4RTge+9MmOtfoNLOaUZbcpMcTVxRFSwRUQ==", "comanda"=>{"cliente_id"=>"1", "forma_pagamento_id"=>"1", "R$valor_total"=>"", "status"=>"false", "itens_comandas_attributes"=>{"1664413214142"=>{"produto_id"=>"1", "valor"=>"", "quantidade"=>"", "_destroy"=>"false"}, "1664413217936"=>{"produto_id"=>"2", "valor"=>"", "quantidade"=>"", "_destroy"=>"false"}}}, "commit"=>"Create Comanda"}
Here, in the nested attributes, this token "1664413214142" that contains the values, but if I try to access the "product_id" key, it doesn't return anything:
puts params[:comanda].has_key?(:itens_comandas_attributes)
true
puts params[:comanda][:itens_comandas_attributes]
it doesn't return anything

You can access the nested attributes using the following syntax:
params[:comanda][:itens_comandas_attributes]["1664413214142"][:produto_id]
You can also access the nested attributes using the following syntax:
params[:comanda][:itens_comandas_attributes].values[0][:produto_id]
# params[:comanda][:itens_comandas_attributes].values[0]["produto_id"]

Related

How can I access the data for the snake object sent through JSON in my params?

Using javascript, I make a fetch post.
const game = {name: this.player, snake: this.snake, score: this.score, apple: this.apple, skull: this.skull, completed: this.completed}
return fetch("http://localhost:3000/games", {
method: "POST",
headers: {
"Content-Type": "application/json"},
body: JSON.stringify(game)
})
.then(resp => resp.json())
.then(json => Game.appendHighScores(json));
I access the data via params on the Ruby on Rails end. The data for params[:snake][:body] are supposed to look like "body"=>[{"x"=>16, "y"=>15}, {"x"=>16, "y"=>14}, {"x"=>16, "y"=>15}]}, yet when I type them into the command line, they look like this:
[<ActionController::Parameters {"x"=>16, "y"=>15} permitted: false>, <ActionController::Parameters {"x"=>16, "y"=>14} permitted: false>, <ActionController::Parameters {"x"=>16, "y"=>15} permitted: false>]
It is accessible via indexing, but I get everything along with the data I'm looking for.
I was hoping it would look like the original params when I typed it in
<ActionController::Parameters {"name"=>"Don", "snake"=>{"x"=>16, "y"=>15, "direction"=>"down", "speed"=>0, "directions"=>{"left"=>{"x"=>-1, "y"=>0}, "up"=>{"x"=>0, "y"=>-1}, "right"=>{"x"=>1, "y"=>0}, "down"=>{"x"=>0, "y"=>1}}, "image"=>{}, "body"=>[{"x"=>16, "y"=>15}, {"x"=>16, "y"=>14}, {"x"=>16, "y"=>15}]}, "score"=>0, "apple"=>{"image"=>{}, "x"=>2, "y"=>10}, "skull"=>{"image"=>{}, "x"=>12, "y"=>12}, "completed"=>true, "controller"=>"games", "action"=>"create", "game"=>{"score"=>0, "skull"=>{"image"=>{}, "x"=>12, "y"=>12}, "apple"=>{"image"=>{}, "x"=>2, "y"=>10}, "completed"=>true}} permitted: false>
Anyway to get the params as an array without it looking so messy with ActionController::Parameters inside of the element?
The reason everything is wrapped inside ActionController::Parameters is for your security (mass assignment in particular). You should never trust data send from the internet. This class allows you to permit/whitelist what properties you trust and filter out everything that you don't trust.
snake_params = params.require(:snake).permit(body: [:x, :y])
You can then convert this into a hash with a simple to_h call, which will drill down into all other nested parameters that are also permitted.
snake_data = snake_params.to_h
#=> { "body" => [{"x"=>16, "y"=>15}, {"x"=>16, "y"=>14}, {"x"=>16, "y"=>15}] }
If you'd like to include other attributes as well you can add them to the permit list.
.permit(:direction, :speed, directions: {left: [:x, :y], up: [:x, :y], ...}, body: [:x, :y])
For more info about permit I suggest checking out the guide Action Controller Overview - 4.5 Strong Parameters.
If you don't care about permitting certain parameters you can permit everything with permit!.
Note that you don't have to permit parameters if you extract the values directly. The code below would work perfectly fine without permitting anything.
body = params[:snake][:body]
body.each |body_part|
x = body_part[:x]
y = body_part[:y]
// do stuff with x and y
end
Because Rails utilize "strong parameters" you have to list and allow the parameters in the controller you want to use.
Since you have a list of parameters with dynamic keys the easiest way to do, while you are in development mode is to permit them like this:
# in your controller
def game_params
params.require(:game).tap do |permitted|
permitted[:name] = params[:name].permit!
permitted[:snake] = params[:snake].permit!
permitted[:apple] = params[:apple].permit!
permitted[:skull] = params[:skull].permit!
permitted[:completed] = params[:completed].permit!
end
end
and then you will be able to access your params:
game_params[:snake][:body]
In production tho, I will encourage you to whitelist all of the keys, for example like this.

Rails 5 allowing a JSON field through strong parameters not working

I'm trying to let through a deeply nested JSON field in my model (document => field_instance => value). I was using an empty hash due to a misunderstanding of the documentation.
permit! can do what I need but I am trying to avoid simply doing params.fetch(:document).permit! due to the massive security hole this opens. So how can I permit any structures of any type under the doubly nested JSON value 'parameter' only?
I am testing with a single string under value called text, and am getting 'Unpermitted parameter: text'
Each instance_field has a specific type which has a list of required parameters, but without a way of being specific for each instance_field in the document, I've opted just to allow all parameters under that JSON field.
Here is my document_params method:
params.fetch(:document)
.permit(:structure_id, :field_instances_attributes => [
:value,
:document_id,
:field_id,
:value_attributes => {}
])
So, what am I doing wrong here?
Or, even better: each field_instance has a type that knows the exact structure the field's value expects. Can I be specific about the fields allowed under value for each field_instance?
Related logs:
service_1 | Parameters: {"utf8"=>"Ô£ô", "authenticity_token"=>" -- censored --", "document"=>{"structure_id"=>"1", "field_instances_attributes"=>[{"document_id"=>"0", "field_id"=>"1", "value_attributes"=>{"text"=>"asdf"}}]}, "commit"=>"Create Document"}
service_1 | Unpermitted parameter: text
service_1 | Unpermitted parameter: text
service_1 | #<FieldInstance id: nil, field_id: 1, document_id: nil, value: nil, created_at: nil, updated_at: nil>
It's actually pretty simple to build a params hash in multiple steps, but it's a little non-obvious.
Here's what I do:
def document_params
#document_params ||= params.require(:document).permit(:structure_id, field_instance_atributes: %i[document_id field_id]).tap do |doc|
doc[:value] = params[:document].require(:value).permit!
end
end
If the value object sanitization is really complicated, perhaps write that as its own helper and use it here.
The important part is the use of require: it returns the inner value as a new object, and so manipulations against it don't affect params. You can go back to the well, so to speak, as many times as you need.
Caveat: require will throw ActionController::ParameterMissing if the key is not found.

Is it possible to whitelist JSON (as text) in Rails' strong parameters?

Let's say I have this JSON:
{
name: 'David',
tags: {
is_confused: true
}
}
and in a certain model (i.e. User), I can save this directly... but my tags object needs to be saved in a Text column which will later serialize to JSON. Is there any way to do this with strong parameters?
You can force tags to json before permitting it as a string with strong parameters.
Assuming your object is named "params":
params[:tags] = params[:tags].permit(:is_confused).to_json
final_data = params.permit(:name, :tags)
This uses strong parameters to check the tags field before turning it to a json string so that you can save it as text (and use strong parameters to ensure that it's a string).

rails controller invoking params of nested attributes

Submitting the following parameters
Parameters: {[...] "physicalinventario"=>{[...] "physicalinventarioitems_attributes"=>{"0"=>{"quantity"=>",85"}}}, "commit"
The goal is to intercept the quantity parameter at the physicalinventarioitem controller create action, and sanitize it for possible comma as decimal value being input
if params[:physicalinventario][:physicalinventarioitems_attributes][:quantity].include? ","
params[:physicalinventarioitem][:quantity] = params[:physicalinventario][:physicalinventarioitems_attributes][:quantity].tr!(',', '.').to_d
end
However, the syntax is wrong as no value after the comma is being handled.
#Alex answer is fine if you have only one quantity.
but what if you have multiple quantites,
eg: {"0"=>{"quantity"=>",85"},"1"=>{"quantity"=>",90"}}
So, here is the answer which also achieves that requirement for multiple nested attributes.
hash = {"physicalinventario"=>{"physicalinventarioitems_attributes"=>{"0"=>{"quantity"=>",85"},"1"=>{"quantity"=>",90"}}}}
The code that you require,
hash["physicalinventario"]["physicalinventarioitems_attributes"].each do |key, value|
if value["quantity"].include? ","
value["quantity"] = value["quantity"].tr!(',', '.').to_f
end
end
Here is the resultant hash,
`{"physicalinventario"=>{"physicalinventarioitems_attributes"=>{"0"=>{"quantity"=>0.85}, "1"=>{"quantity"=>0.9}}}}`
Looks like you've missed ["0"] in the chain to get :quantity.
Should be
params[:physicalinventario][:physicalinventarioitems_attribu‌tes]["0"][:quantity]
Most convenient Rails way to sanitize(normalize) data in a model.
To don't create duplicates, more here How best to sanitize fields in ruby on rails

Why Strong Params contains permitted: false

I put in a binding.pry at the top of my controller's update action. Once at that break point, I put in params[:foo_bar] to examine the params hash. Here is what I get:
<ActionController::Parameters {"utf8"=>"✓", "_method"=>"patch", "authenticity_token"=>"123==", "foobar"=><ActionController::Parameters {"barbazz_attributes"=>{"start_date"=>"08/27/2016", "end_date"=>"08/29/2016", "id"=>"89"}, "bazz_id"=>"3", "abc_id"=>"330", "bazzbazz_attributes"=>{"0"=>{"_destroy"=>"1", "city_id"=>"1669", "id"=>"26"}, "1"=>{"city_id"=>"1681", "id"=>"27"}, "2"=>{"city_id"=>"1672"}}} permitted: false>, "cat_id"=>["1", "1", "1"], "commit"=>"Update FooBar", "controller"=>"foo_bars", "action"=>"update", "id"=>"52"} permitted: false>
I assumed permitted: false is there because I did not whitelist some attributes. I looked over the attributes and it appears to me that I did whitelist everything.
I am using Rails 5 if that happens to make any difference.
Question: What is an easy way to find out why the strong parameters are returning params: false.
Don't access params directly with params instead use the name you gave your permitted params, for example: foobar_params.
If foobar_params is defined:
def foobar_params
params.require(:foobar).permit ...
end
The easiest way is to read the source code for ActionController::Parameter, permitted = false is the default unless you call permit! to allow all, but that defeats the purpose of strong parameters.

Resources