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.
Related
I am trying to POST(create) an event using EventBrite's API.
I have the auth key but am having trouble formatting the request.
Here is what I have.
def self.syndicate_event
event = Event.first
body_string =
"{
'event': {
'name': {
'html': #{event.name}
},
'description': {
'html': #{event.description}
},
'start': {
'utc': #{event.start},
'timezone': #{event.start_timezone},
},
'end': {
'utc': #{event.end},
'timezone': #{event.end_timezone},
},
'currency':#{event.currency}
}
}"
json_body = body_string.to_json
respo = HTTP.auth("Bearer mytoken")
.post("https://www.eventbriteapi.com/v3/events/",
params: json_body
)
end
It gives me : 'Can't convert String into Array.' error. Any ideas what is going on ? And if someone has used the API for EventBrite, is there a better way other than formatting my string like that and then making it into JSON.
Thanks
I'm not familiar with the EventBrite API, but it looks like the json_body string is malformed. You can verify that your json is valid by doing JSON.parse(json_body) and that should return a hash representation of your json. If it's malformed, it will raise an error.
I would opt to use the to_json method on a hash instance to guarantee the json will not be malformed.
Something like:
body_object =
{ event: {
name: {
html: event.name
},
description: {
html: event.description
}...,
currency: event.currency }
}
json_body = body_object.to_json
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.
I've been trying for quite some time now to figure out why the JSON object I'm passing through AJAX to Rails with Typhoeus isn't working properly. Apologies if this is a newb question but I'm relatively new to web dev.
I've spent all day looking at docs, googling, and on SO but I haven't been able to figure out much for some reason.
I'm trying to pass a request to Google's QPX Express API for flight search and the docs say to send a JSON obj in the following format:
{
"request": {
"passengers": {
"kind": "qpxexpress#passengerCounts",
"adultCount": 1,
"childCount": 0,
"infantInLapCount": 0,
"infantInSeatCount": 0,
"seniorCount": 0
},
"slice": [
{
"kind": "qpxexpress#sliceInput",
"origin": "SFO",
"destination": "HNL",
"date": "2015-04-03",
"maxStops": 0,
"maxConnectionDuration": 0,
"preferredCabin": "COACH",
"permittedDepartureTime": {
"kind": "qpxexpress#timeOfDayRange",
"earliestTime": "00:00",
"latestTime": "11:59"
},
"permittedCarrier": [
"VX",
"UA"
],
"alliance": "",
"prohibitedCarrier": [
""
]
}
],
"maxPrice": "USD1000.00",
"saleCountry": "US",
"refundable": false,
"solutions": 1
}
}
I have this stored a variable which is referenced in the AJAX request below as 'reqBody':
$.ajax({
url: '/search',
dataType: 'json',
contentType: 'application/json',
method: 'POST',
// contentType: "application/json; charset=utf-8",
data: JSON.stringify(reqBody),
success: function(data) {
console.log(data);
}
});
And this is call goes to the rails controller shown here, using Typhoeus to process the request/response:
reqBody = params[:request]
flightRequest = Typhoeus::Request.new(
"https://www.googleapis.com/qpxExpress/v1/trips/search?key=APIKEY",
method: :post,
headers: {'Content-Type'=> "application/json; charset=utf-8"},
body: reqBody,
)
flightRequest.run
#results = JSON.parse(flightRequest.response.body)
respond_to do |format|
format.html
format.json { render json: {
:results => #results
}
}
end
This ends up being the response I get back:
{"results":{"error":{"errors":[{"domain":"global","reason":"parseError","message":"Parse Error"}],"code":400,"message":"Parse Error"}}}
And this is what I get when I look at the obj in pry:
=> {"passengers"=>
{"kind"=>"qpxexpress#passengerCounts",
"adultCount"=>1,
"childCount"=>0,
"infantInLapCount"=>0,
"infantInSeatCount"=>0,
"seniorCount"=>0},
"slice"=>
[{"kind"=>"qpxexpress#sliceInput",
"origin"=>"SFO",
"destination"=>"HNL",
"date"=>"2015-04-03",
"maxStops"=>0,
"maxConnectionDuration"=>0,
"preferredCabin"=>"COACH",
"permittedDepartureTime"=>
{"kind"=>"qpxexpress#timeOfDayRange", "earliestTime"=>"00:00", "latestTime"=>"11:59"},
"permittedCarrier"=>["VX", "UA"],
"alliance"=>"",
"prohibitedCarrier"=>[""]}],
"maxPrice"=>"USD1000.00",
"saleCountry"=>"US",
"refundable"=>false,
"solutions"=>1}
What's going on here? Shouldn't the object be a string since I stringified it in the AJAX request? Is this why there's a parsing error when I send the object to the QPX Express API?
Any help is highly appreciated!
Thanks!
Just a thought,
did you require 'json' in your controller?
The other thing you could try is the variant of parse
parse! which can be used for safe safe sources.
here is a link!
How do I parse JSON with Ruby on Rails?
good luck
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.
I keep getting a "No model was found for '0'" error when trying to connect an Ember.js app to a Rails 3.2 API. My setup is below. Any help will be greatly appreciated.
Items Controller (Rails)
def index
#items = Item.all
respond_to do |format|
format.html
format.json { render json: #items, root: true }
end
end
App.js (Ember.js)
App = Ember.Application.create();
App.Router.map(function() {
this.resource('items', function() {
this.route('backlog');
this.route('board');
});
});
App.ItemsRoute = Ember.Route.extend({
model: function() {
return this.store.find('item');
}
});
Server response when the Ember app makes request to /items
[
{
"item": {"id":1,"item_type":"Item","name":"Test item"}
},
{
"item": {"id":2,"item_type":"Item","name":"Test item 2"}
}
]
Assuming that you're using Ember-Data, your JSON format is incorrect. It's fairly poorly documented, so don't feel bad. The RESTAdapter has a brief example, but the JSON API goes into more details. In your case, I think you want your response to look like this:
{
"items": [
{ "id": 1, ... },
{ "id": 2, ... }
]
}