Rails 6 - Strong Parameters - allowing array - ruby-on-rails

I am sending this simple hash as JSON to my controller:
{
"cars": [
{ "rego": "ABC123" }
]
}
In the controller, I am trying to allow the array of cars for further processing.
I tried the following:
params.permit(:cars)
params.permit(cars: [])
params.permit(:cars, cars: [])
In every attempt I am not getting anything in my filtered params:
DEBUG -- : Unpermitted parameters: :cars, :car, :user_username, :user_token
=> <ActionController::Parameters {} permitted: true>
I am using RoR 6.0.2.1 with Ruby 2.6.5.

Try params.permit(cars: [:rego])
params.permit(cars: []) allows cars as an array of primitive values
{
"cars": [1, 2, 3, 4]
}
"Strong Parameters" has more information.

Related

Rails: Accept 2D array of strings with strong parameters

We have a Rails controller that gets the following data:
params = ActionController::Parameters.new({
"requests": [{
"params": {
"facets": ["user.id", "user.type"],
"facetFilters": [
["user.type:Individual"]
]
}
}, {
"params": {
"facets": "user.type"
}
}]
})
We want to use strong parameters to accept this data, but I haven't yet seen a pattern that will let us accept the 2D array in facetFilters. I'm tinkering with the following:
params[:requests].each do |request|
request[:permitted] = true
request[:params].each do |o|
if ['facets', 'facetFilters'].include?(o.first)
begin
o[:permitted] = true
rescue
end
end
end
end
As one can see not all attributes are permitted (the permitted attributes don't get passed to children elements), as this returns:
[<ActionController::Parameters {"params"=><ActionController::Parameters {"facets"=>["user.id", "user.type"], "facetFilters"=>[["user.type:Individual"]]} permitted: false>, "permitted"=>true} permitted: false>, <ActionController::Parameters {"params"=><ActionController::Parameters {"facets"=>"user.type"} permitted: false>, "permitted"=>true} permitted: false>]
And there are lots of permitted: false in there...
Is it possible to accomplish this goal? Any pointers would be super helpful!
The best way I've found to get this done is to convert the 2D array to a 1D array, permit the 1D array, and then convert the array back to 2D.
nested_facet_filters = []
(params[:requests] || []).each_with_index do |r, ridx|
if r[:params].key?(:facetFilters) && r[:params][:facetFilters].kind_of?(Array) && r[:params][:facetFilters].first.kind_of?(Array)
# flatten the facet filters into a 1D array because Rails can't permit a 2D array
nested_facet_filters << ridx
r[:params][:facetFilters] = r[:params][:facetFilters].flatten
end
end
permitted = params.permit({
requests: [
:query,
:params => [
:facets => [],
:facetFilters => [],
]
]
}).to_h
# after permitting the params, we need to convert the facetFilters back into a 2D array
if nested_facet_filters.present?
nested_facet_filters.each do |idx|
# create map from facet key to array of values
d = {}
permitted[:requests][idx][:params][:facetFilters].each do |s|
split = s.split(':')
facet = split.first
value = split[1..-1].join(':')
if d.key?(facet)
d[facet] << s
else
d[facet] = [s]
end
end
# mutate facetFilters back to 2D array for posting to Algolia
permitted[:requests][idx][:params][:facetFilters] = d.values
end
end
There's been a merge request to accept 2D arrays for six years open in the Rails issue tracker [thread]...

Stringify/Parse request data through rspec

I'm submitting requests on the frontend where I stringify my data (array of objects) and then parse it in the backend.
When I run my specs, I'm getting the error no implicit conversion of Array into String
How can I stringify my data in my spec so that it's consistent with what I'm doing in the frontend? Or is there another way where I don't have to stringify/parse my data to handle all of this?
This is how my frontend data structure looks like:
"categories_and_years": JSON.stringify(
[
{"category_id": 1, "year_ids":[1, 2, 3]},
{"category_id": 2, "year_ids":[2, 3]},
]
)
In my controller, I'm validating the data is an array first:
def validate_categories_and_years_array
#cats_and_yrs = JSON.parse(params[:categories_and_years])
return unless #cats_and_yrs
if !#cats_and_yrs.is_a?(Array)
render_response(:unprocessable_entity, { description_detailed: "categories_and_years must be an array of objects"})
end
end
In my specs, I'm setting my params like this:
context "when all categories and years are valid" do
let(:params) do
{
school_id: school.id,
id: standard_group.id,
categories_and_years: [
{ category_id: category_1.id, year_ids: [ year_1.id ] }
]
}
end
it "adds standards from specific categories and years to the school" do
post :add, params: params, as: :json
expect(school.achievement_standards).to contain_exactly( std_1 )
end
end
This post explains the difference between a regular ruby hash which you have in your spec and a HashWithIndifferentAccess.
Can you also try to_json?
Params hash keys as symbols vs strings
params = ActiveSupport::HashWithIndifferentAccess.new()
params['school_id'] = school.id
params['id'] = standard_group.id
params['categories_and_years'] = [
{ category_id: category_1.id, year_ids: [ year_1.id ] }
]
params = params.to_json
let(:params) { params }

How to collect many attributes from hash in ruby

I'm wondering if it's possible to collet many attributes from a hash.
Currently using ruby 2.6.3
Something like that
hash = { name: "Foo", email: "Bar", useless: nil }
other_hash = hash[:name, :email]
The output should be another hash but without the useless key/value
You can use Ruby's built in Hash#slice:
hash = { name: "Foo", email: "Bar", useless: nil }
p hash.slice(:name, :email)
# {:name=>"Foo", :email=>"Bar"}
If using Rails you can use Hash#except which receives just the keys you want to omit:
p hash.except(:useless)
# {:name=>"Foo", :email=>"Bar"}
If useless keys are so for having nil values, you can also use Hash#compact:
h = { name: "Foo", email: "Bar", useless: nil }
h.compact #=> {:name=>"Foo", :email=>"Bar"}

Rails Strong Params, Accept Multiple Data Types

Is there any way to allow Rails strong params to permit different data types? For example, I'm using react-bootstrap-typeahead and redux-form to create a form that allows users to select from provided values or create their own value. The provided values come from the database and are handed to the controller as an object with name and id whereas the user created values are passed as a string.
Permitting both a String and Array/Object in Rails 4+
TL;DR: Simply list parameter multiple times to support different types.
String or Array: params.require(:my_model).permit(:my_attr, my_attr: [])
String or Object: params.require(:my_model).permit(:my_attr, my_attr: [:key1, :key2])
Full Example
Suppose we have a model MyModel which has an attribute my_attr.
my_attr can be a string or array:
# example requests:
# - {"my_model": {"my_attr": "hello" }
# - {"my_model": {"my_attr": ["hello", "world"] }
params.require(:my_model).permit(:my_attr, my_attr: [])
my_attr can be a string or object:
# my_attr can be a string or an object with keys key1, key2
# example requests:
# - {"my_model": {"my_attr": "hello" }
# - {"my_model": {"my_attr": { "key1": "val1", "key2": "val2" }
params.require(:my_model).permit(:my_attr, my_attr: [:key1, :key2])
The first argument to permit allows my_attr to be a string, the second allows it to be an array or object (see documentation for Rails parameters).
Testing In Rails Console
Copy/Paste the following code to experiment with params directly:
# Testing with String
params = ActionController::Parameters.new(my_model: { my_attr: "My String" })
permitted = params.require(:my_model).permit(:my_attr, my_attr: [])
permitted.permitted? # => true
# Testing with Array
params = ActionController::Parameters.new(my_model: { my_attr: ["Hello", "World"] })
permitted = params.require(:my_model).permit(:my_attr, my_attr: [])
permitted.permitted? # => true
# Testing with Object
params = ActionController::Parameters.new(my_model: { my_attr: {key1: 'attr1', key2: 'attr2' }})
permitted = params.require(:my_model).permit(:my_attr, my_attr: [:key1, :key2])
permitted.permitted? # => true
I recently did something like
params.require(:note).permit(
fields: [:value, value: []]
)
which allowed me to submit string and array params.
Sorry I don't have the docs to back me up on that, I'm just learning ruby myself. Hope this helps.
A form's submitted values will be stored in params[:object] regardless. I'm still not sure what you're asking - can you post your create controller actions if that's what being called on submit?

Mongoid or/any_of unexpected behaviour

I'm having an issue with mongoid any_of. I'm trying to find objects that have either one field > 0, or another one > 0. My query is :
Model.any_of(best_friend_method.gt => 0, method.gt => 0).desc(best_friend_method, method)
It is "translated" in :
#<Mongoid::Criteria
selector: {"$or"=>[{:best_friends_lc_sum=>{"$gt"=>0}, :lc_sum=>{"$gt"=>0}}]},
options: {:sort=>[[:best_friends_lc_sum, :desc], [:lc_sum, :desc]]},
class: FbAlbum,
embedded: false>
As I understand it, this is what I want. But it only returns me 6 results. Model.where(:best_friends_lc_sum.gt => 0).count returns me 6 results too, but Model.where(:lc_sum.gt => 0).count returns me ~850 objects.
I expect my query to return the union of those two : is a mongoid/mongodb error, or am I doing something wrong ?
FYI : mongoid 2.4.5, mongodb 2.0.2, rails 3.1.3
Thanks for your time!
It's because you pass only one args and not 2 args. So it's like you have no $or usage.
Try :
Model.any_of({best_friend_method.gt => 0}, {method.gt => 0}).desc(best_friend_method, method)
In this case the Criteria become :
#<Mongoid::Criteria
selector: {"$or"=>[{:best_friends_lc_sum=>{"$gt"=>0}}, {:lc_sum=>{"$gt"=>0}}]},
options: {:sort=>[[:best_friends_lc_sum, :desc], [:lc_sum, :desc]]},
class: FbAlbum,
embedded: false>
Sometime the usage of {} is mandatory to separate different hash.
In case this helps someone...In Mongoid 3, the Origin gem provides the syntax for querying. Here is the list of methods that you can use to write your Mongoid 3 queries. Among those methods is the or method which allows you to perform an $or query:
# Mongoid query:
Model.or(
{ name: "Martin" }, { name: "Dave" }
)
# resulting MongoDB query:
{
"$or" => [
{ "name" => "Martin" }, { "name" => "Dave" }
]
}
Using the OP's original example, it can be rewritten as:
Model.or(
{ best_friend_method.gt => 0 },
{ method.gt => 0 }
).order_by(
best_friend_method,
method
)
At least one of the hashes passed to the or method must match in order for a record to be returned.

Resources