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 }
Related
I spend a time to find a solution how to convert an array came from the HTTP request to something can rails read.
Example of the request:
"line_item":[{
"item_id": 1,
"item_name": "Burger",
"item_price": 15.00,
"item_quantity": 2
},
{
"item_id": 2,
"item_name": "Soda",
"item_price": 3.00,
"item_quantity": 1
}]
and then I loop the array like so:
#not_available_items = []
#line_item = #order.line_item
#line_item.each do |i|
#item = Item.find(i.item_id)
if #item.is_active == false
#not_available_items.push(
{
item_id: i.item_id,
item_name: i.item_name
}
)
end
end
The error message:
"exception": "#<NoMethodError: undefined method `item_id' for {\"item_id\"=> 1, \"item_name\"=>\"Burger\", \"item_price\"=> 15.0, \"item_quantity\"=> 2}:Hash>"
Hope I explained the issue clear.
Thanks
The variable i is an object of type Hash (and not of type Item). In Ruby, unlike Javascript, you cannot access the key of a Hash object using the . operator.
You can use the [] operator to fetch the value for a key in a Hash.
In your code, you are using i.item_id, but i does not have an attribute or method called item_id; it has a key item_id. Since, i is a Hash, you can access the fields by calling and i["item_id"]
#not_available_items = []
#line_item = #order.line_item
#line_item.each do |i|
#item = Item.find(i["item_id"])
if #item.is_active == false
#not_available_items.push(
{
item_id: i.item_id,
item_name: i.item_name
}
)
end
end
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]...
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.
I have an API and a client app, and I am using rails with ActiveResource.
I have a Recruiter model that inherits from ActiveResource::Base
Let's say on the client side I write:
dave = Recruiter.new(email: "email#recruiter.com", password: "tyu678$--è", full_name: "David Blaine", company: "GE")
dave.save
The request I send is formatted like so:
{"recruiter":{
"email": "email#recruiter.com",
"password": "tyu678$--è",
"full_name": "David Blaine",
"company": "GE"
}
}
and the Json response I get from the API is formatted like:
{"recruiter":{
"email": "email#recruiter.com",
"password": "tyu678$--è",
"full_name": "David Blaine",
"company": "GE",
"foo": "bar"
},
"app_token":"ApfXy8YYVtsipFLvJXQ"
}
The problem is that this will let me access the app token with dave.app_token but I can't for instance write dave.foo, which raises an error.
Is there a way to flatten the response or read through it recursively si that I can access all of my instance's attributes while keeping the API response formatted as it is?
Looking through the whole ActiveResource process, you can overwrite the load method in your Recruiter model.
I just added the code in the #HACK section which "flatten" your attributes.
def load(attributes, remove_root = false, persisted = false)
raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
#prefix_options, attributes = split_options(attributes)
# HACK
if !attributes[:app_token].nil?
attributes[:recruiter]["app_token"] = attributes[:app_token]
attributes = attributes[:recruiter]
end
# /HACK
if attributes.keys.size == 1
remove_root = self.class.element_name == attributes.keys.first.to_s
end
attributes = ActiveResource::Formats.remove_root(attributes) if remove_root
attributes.each do |key, value|
#attributes[key.to_s] =
case value
when Array
resource = nil
value.map do |attrs|
if attrs.is_a?(Hash)
resource ||= find_or_create_resource_for_collection(key)
resource.new(attrs, persisted)
else
attrs.duplicable? ? attrs.dup : attrs
end
end
when Hash
resource = find_or_create_resource_for(key)
resource.new(value, persisted)
else
value.duplicable? ? value.dup : value
end
end
self
end
I am trying to restructure a named-JSON response to return a model object (some attributes only), and some associated arrays stored in local variables, however I am unsure what I'm doing incorrectly. The local variables are definitely being assigned with values, however they're not being returned in the response.
This is the structure of what I want returned...
{ name: "Dan", email: "email#email.com", id: "1", open_gifts: [ { objects }, { here }] }
Setup
#person = Person.find_by_id(params[:id])
gifts_created_open = Gift.created_gifts_open(#person)
return_object = [#person.name, #person.email, #person.id, gifts_created_open]
Now this, returns a JSON object with the details, but its wrapped in an array, and I'm trying to return just a named object, with the associated array inside it.
render :json => return_object
And this returns a named object, but its missing the array. What gives??
render :json => #person.to_json(:gifts_created_open, :only => [:name, :email, :id] )
Many thanks with this. I've already spent several hours :/
Try:
return_object = {name:#person.name, email:#person.email, id:#person.id, gifts:gifts_created_open}.to_json