How to pass a Hash to Grape API method? - ruby-on-rails

I'm having problems with the Grape gem and the parameters validation.
The idea behind this is to create a complex entity using nested attributes through an API service.
I have a method to create a trip, trip have many destinations and i want to pass that destinations using a hash (using the accepts_nested_attributes_for helper).
I have this grape restriction over the parameter:
requires :destinations, type: Hash
And I'm trying to send something like this:
{ destinations => [
{ destination: { name => 'dest1'} },
{ destination: { name => 'dest2'} },
{ destination: { name => 'dest3'} }
]}
In order to build something like the structure below inside the method and get the trip created:
{ trip: {
name: 'Trip1', destinations_attributes: [
{ name: 'dest1' },
{ name: 'dest2' },
{ name: 'dest3' }
]
}}
I'm using POSTMAN chrome extension to call the API method.
Here's a screen capture:
If someone can help me i would be very grateful.

By the looks of what you are trying to send, you need to change the Grape restriction, because destinations is an Array, not a Hash:
requires :destinations, type: Array
You don't need the "destination" hash when sending the request:
{ destinations => [
{ name => 'dest1', other_attribute: 'value', etc... },
{ name => 'dest2', other_attribute: 'value', etc... },
{ name => 'dest3', other_attribute: 'value', etc... }
]}
This creates an Array of hashes.
In order to send this through POSTMAN, you'll need to modify that destinations param your sending and add multiple lines in POSTMAN. Something like:
destinations[][name] 'dest1'
destinations[][other_attribute] 'value1'
destinations[][name] 'dest2'
destinations[][other_attribute] 'value2'
destinations[][name] 'dest3'
destinations[][other_attribute] 'value3'
Hope this answers your questions. Let me know if this is what you were looking for.

Related

using react and json to upload images to rails

I have a form in a react compnent that includes an image. This component has an associated state for the form, it is quite a large and complex form with many nested arrays of sub elements that vary in length, which is why I do not want to use FormData().
state = {
recipe: {
title: "",
image: null,
ingredient_groups: [
{
title: "",
ingredients: [
{
name: "",
measurement_metric: "",
measurement_imperial: ""
}
]
}
]
}
The state is updated with an onChange handler for the input field like the following
let {recipe} = this.state;
recipe.image = event.target.files[0];
this.setState({recipe});
This is then sent via an axios post request,
let {recipe} = this.state;
axios.post('/api/v1/recipe', {recipe: recipe}).then((result) => {
console.log(result.status)
})
in rails I have a params method set up for the allowed params
def recipe_params
params.require(:recipe).permit(:title,
:image,
{ingredient_groups: [
:id,
:title,
{ingredients: [
:id,
:name,
:measurement_metric,
:measurement_imperial
]}
]})
end
however I am getting nil for recipe_params[:image] when I try to save it into active storage like this:
#recipe.image.attach(recipe_params[:image])
Is what Im doing even possible? Or is the only way to transmit this via a FormData object on the post. I would really prefer this to be done via a pure json method.
using https://www.npmjs.com/package/object-to-formdata I was able to successfully change my react state object into a FormData Object, and then post this to my endpoint and it has worked as expected

Angular 4 & Rails 5 Post Request JSON Formatting

I'm developing an angular app using a rails backend. I'm having problems formatting the parameters hash so rails can use it. The data is a many to many relationship, and the form contains nested attributes. In Rails, my models utilize the accepts_nested_attributes_for helper. I know exactly what format rails expects, but when I make a POST request, there is one minor detail that's off. I'm going to list below two param hashes. One is what Angular produces, and the other is what Rails expects.
What's off about the Angular request is rails expects a deeper layer of nesting in the expense_expense_categories attributes. I've never understood why rails requires it. What angular produces looks logical to me. My question is.. What do I need to do to format the parameters in Angular? Looking at what I have so far, am I doing this in a way that satisfies Angular best practices?
Angular:
{
"expense": {
"date": "2017/4/13",
"check_number": "132",
"debit": "0",
"notes": "har",
"amount": "24",
"payee_id": "334"
},
"expense_expense_categories_attributes": [{
"expense_category_id": "59",
"amount": 12
},
{
"expense_category_id": "62",
"amount": 11
}
]
}
What Rails expects:
{
"expense": {
"date": "2017/12/12",
"check_number": "122",
"debit": "0",
"notes": "har",
"amount": "24",
"payee_id": "334",
"expense_expense_categories_attributes": {
"210212312": {
"expense_category_id": "72",
"amount": "12"
},
"432323432": {
"expense_category_id": "73",
"amount": "12"
}
}
}
}
My code in angular is as follows.
onSubmit() method in component:
onSubmit() {
this.expenseService.addExpense(this.expenseForm.value)
.subscribe(
() => {
this.errorMessage = '';
},
error => {
this.errorMessage = <any>error;
}
);
this.expenseForm.reset();
}
addExpense in my service file:
addExpense(expense: Expense): Observable<any> {
let headers = new Headers({'Content-Type': 'application/json'});
let options = new RequestOptions({headers: headers});
return this.http.post('http://localhost:3000/expenses', expense, options)
.map(
(res: Response) => {
const expenseNew: Expense = res.json();
this.expenses.push(expenseNew);
this.expensesChanged.next(this.expenses.slice());
})
.catch(this.handleError);
}
my main form:
private initForm() {
let expense_expense_categories_attributes = new FormArray([]);
this.expenseForm = this.fb.group({
id: '',
date: '',
amount: '',
check_number: '',
debit: '',
payee_id: '',
notes: '',
expense_expense_categories_attributes: expense_expense_categories_attributes
});
}
My FormArray for nested attributes:
onAddExpenseCategories() {
(<FormArray>this.expenseForm.get('expense_expense_categories_attributes')).push(
new FormGroup({
'expense_category_id': new FormControl(null, Validators.required),
'amount': new FormControl(null, [
Validators.required
])
})
);
}
UPDATE: I was able to get it working, but I had to use a god awful regex to manipulate the request to what I wanted. It was an extremely ugly option so I still need to find a better option. Is there a better way to format JSON Objects and replace the contents? I'm not sure the correct way to do it. Need help.
You need to add the expense_expense_categories to the wrap_parameters like this:
wrap_parameters :expense, include: [:expense_expense_categories_attributes]
Additional attributes must be explicitly added to wrap_parameters as it only wraps attributes of the model itself by default.

Custom envelope fields using Docusign REST API

I'm using the docusign_rest gem to integrate with the Docusign REST API in my rails application. I have created a custom envelope field in the Docusign admin called SFID. I need to pass an ID into SFID inside of the envelope. I'm getting the following error with my JSON code:
{"errorCode"=>"INVALID_REQUEST_BODY", "message"=>"The request body is missing or improperly formatted. Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'API_REST.Models.v2.customFields' because the type requires a JSON object (e.g. {\"name\":\"value\"}) to deserialize correctly.\r\nTo fix this error either change the JSON to a JSON object (e.g. {\"name\":\"value\"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List<T> that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.\r\nPath 'customFields', line 1, position 1073."}
My controller code:
#envelope_response = client.create_envelope_from_template(
status: 'sent',
email: {
subject: "The test email subject envelope",
body: ""
},
template_id: '90B58E8F-xxxxx',
custom_fields: [
{
textCustomFields: [
{
name: 'SFID',
value:'12345',
required: 'false',
show: 'true'
}
]
}
],
signers: [
...
The Docusign API explorer says the following is the correct way to push an envelope custom field:
{
"customFields": {
"textCustomFields": [
{
"value": "0101010101",
"required": "true",
"show": "true",
"name": "SFID"
},
{
"required": "true",
"show": "true"
}
]
}
}
The Docusign_rest gem says the following on custom envelope fields:
customFields - (Optional) A hash of listCustomFields and textCustomFields.
# Each contains an array of corresponding customField hashes.
# For details, please see: http://bit.ly/1FnmRJx
What formatting changes to I need to make to my controller code to get it to successfully push a custom envelope field?
You have an extra array in your customFields node.
Remove the [] array from your custom_fields:
#envelope_response = client.create_envelope_from_template(
status: 'sent',
email: {
subject: "The test email subject envelope",
body: ""
},
template_id: '90B58E8F-xxxxx',
custom_fields:
{
textCustomFields: [
{
name: 'SFID',
value:'12345',
required: 'false',
show: 'true'
}
]
},
signers: [
...
Also I'm assuming that your client.create_envelope_from_template is converting your _'s into a camelCased string. if that is not happening, then that also needs to change.

Best way to reformat json in Rails?

An app I am building receives some information in JSON, however the data is very poorly organized. I would like to rebuild the JSON output. The JSON I'm receiving is completely flat, and certain things should be nested. To illustrate what I mean:
I'm getting something like this:
{[
{fullname: 'Joe', session: 'A', time: '5:00', room: 'Ballroom'},
{fullname: 'Abe', session: 'B', time: '5:00', room: 'Bathroom'},
{fullname: 'Mike', session: 'C', time: '6:00', room: 'Bathroom'},
]}
I want something like this:
{
rooms: [
{
name: 'Ballroom',
sessions: [
{
title: 'A',
speakers: [{name: 'Joe'}]
}
]
},
{
name: 'Bathroom',
sessions: [
{
title: 'B',
speakers: [{name: 'Abe'}]
},
{
title: 'C',
speakers : [{name: 'Mike'}]
}
]
}
]
}
Are there any gems that are well equipped for doing something like this? Is there a specific part of the application this manipulation should be done in to follow MVC?
I should note that all this app does is receive this JSON and then makes API calls to another application to create/update information in that app's DB to reflect what's in the JSON.
Use:
JSON.pretty_generate your_hash
For example:
require 'json'
my_json = { :array => [1, 2, 3, { :sample => "hash"} ], :foo => "bar" }
puts JSON.pretty_generate(my_json)
refer to: How can I "pretty" format my JSON output in Ruby on Rails?
You can re-format using jbuilder or rabl gems. Usage and examples are pretty strait forward in their readme

RSpec request test merges hashes in array in POST JSON params

Looks like a bug in RSpec but maybe I'm missing something.
I have a request spec where I post a JSON that contains an array of hashes:
spec/requests/dummy_request_spec.rb:
post "http://my.server.com/some/route", {
format: :json,
data: [
{
details: {
param1: 1
},
},
{
details: {
param2: 1
}
}
]
}
For some odd reason, RSpec merges the hashes into one element and then sends them to server.
print out of params received in controller:
data: [
{
details: {
param1: 1,
param2: 2
},
},
]
versions:
rspec-2.13.0
rails-3.2.10
Very strange!!
Thanks
Got it! array of hashes is not supported for form-data
RSpec by default posts it as form-data. Solution:
post '...', {...}.to_json, {'CONTENT_TYPE' => "application/json", 'ACCEPT' => 'application/json'}
Also, be aware that you have an extra comma:
data: [
{
details: {
param1: 1
}**,**
},
{
details: {
param2: 1
}
}
]
I faced the same problem reported in the question post while using following versions
ruby 2.3.2
rails (5.0.0.1)
rspec-rails (3.5.2)
Searching for the problem on web I found a related issue at https://github.com/rails/rails/issues/26069 and the solution suggested by it is to pass as: :json option to the post, get etc methods while using them in the controller tests (refer the PR referenced in comment https://github.com/rails/rails/issues/26069#issuecomment-240916233 for more details). Using that solution didn't solved the params mingling issue I was encountering. Following were the results found for different types of data I used with the recommended solution:
In my controller spec I have following
before(:each) do
request.accept = "application/json"
end
and in the test logs I do see that the request is being served
Processing by Api::V1::MyController#my_action as JSON
Data-1
data = [
{
param_1: "param_1_value",
},
{
param_2: "param_2_value",
}
]
params.merge!(my_data: data)
post :my_action, params: params, as: :json
That ends-up in request params as following
{ "my_data"=> [ {"param_1"=>"param_1_value", "param_2"=>"param_2_value"} ] }
which is wrong.
Data-2
data = [
{
param_1: "param_1_value",
something_else: ""
},
{
param_2: "param_2_value",
another_thing: ""
}
]
params.merge!(my_data: data)
post :my_action, params: params, as: :json
That ends-up in request params as following
{ "my_data"=> [ {"param_1"=>"param_1_value", "something_else"=>"", "another_thing"=>"", "param_2"=>"param_2_value"} ] }
which is wrong.
Data-3
data = [
{
param_1: "param_1_value",
param_2: ""
},
{
param_1: ""
param_2: "param_2_value",
}
]
params.merge!(my_data: data)
post :my_action, params: params, as: :json
That ends-up in request params as following
{ "my_data"=>[ {"param_1"=>"param_1_value", "param_2"=>""}, {"param_1"=>"", "param_2"=>"param_2_value"} ] }
which is correct.
It should be noted that for Data-3 without the as: :json option also I receive the correct data in request params.
One more thing: In comment https://github.com/rails/rails/issues/26069#issuecomment-240358290 an alternate solution suggested to deal with the problem narrated above is following
another fix would be to specify nested attributes not as array but as
hash:
params = {
id: #book.id,
book: {
title: 'Cool',
pages_params: {
"0" => { id: #page.id, content: 'another content' },
"1" => { id: #delete_page.id, _destroy: 1 },
"2" => { content: 'another new page' }
}
},
format: :json
}
Unfortunately this was removed from the documentation of nested
attributes so I don't know if this is going to stay valid.
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
But this solution has a drawback is that we need to manually sanitize the data on controller-end to bring it back to expected structure i.e. Array of Hashes.
Finally I am sharing below what worked for me
spec/shared_contexts.rb
RSpec.shared_context "common helpers" do
def set_request_header(request_obj:, request_header_name:, request_header_value:)
request_obj.headers[request_header_name] = request_header_value
end
def set_request_header_content_type_as_json(request_obj:)
set_request_header(request_obj: request_obj, request_header_name: 'CONTENT_TYPE', request_header_value: 'application/json')
end
end
Then in my spec file
require 'shared_contexts'
RSpec.describe Api::V1::MyController, :type => :controller do
include_context "common helpers"
context "POST #my_action" do
it "my example" do
data = [
{
param_1: "param_1_value",
},
{
param_2: "param_2_value",
}
]
params.merge!(my_data: data)
set_request_header_content_type_as_json(request_obj: request)
post :my_action, params: params
end
end
end
As can be seen setting the request header CONTENT_TYPE was what was missing to make the request params to be received in expected structure.

Resources