Extracting hash key that may or may not be an array - ruby-on-rails

I'm making an API call that returns XML (JSON also available) and the response body will show errors if any. There may be only one error or multiple errors. When the XML (or JSON) is parsed into a hash, the key that holds the errors will be an array when multiple errors are present but will be just a standard key when only one error is present. This makes parsing difficult as I can't seem to come up with one line of code that would fit both cases
The call to the API returns this when one error
<?xml version=\"1.0\" encoding=\"utf-8\"?><response><version>1.0</version><code>6</code><message>Data validation failed</message><errors><error><parameter>rptFilterValue1</parameter><message>Parameter is too small</message></error></errors></response>
And this when multiple errors
<?xml version=\"1.0\" encoding=\"utf-8\"?><response><version>1.0</version><code>6</code><message>Data validation failed</message><errors><error><parameter>rptFilterValue1</parameter><message>Parameter is too small</message></error><error><parameter>rptFilterValue2</parameter><message>Missing required parameter</message></error></errors></response>
I use the following to convert the XML to a Hash
Hash.from_xml(response.body).deep_symbolize_keys
This returns the following hash.
When there is only one error, the hash looks like this
{:response=>{:version=>"1.0", :code=>"6", :message=>"Data validation failed", :errors=>{:error=>{:parameter=>"rptFilterValue1", :message=>"Parameter is too small"}}}}
When there are 2 errors, the hash looks like this
{:response=>{:version=>"1.0", :code=>"6", :message=>"Data validation failed", :errors=>{:error=>[{:parameter=>"rptFilterValue1", :message=>"Parameter is too small"}, {:parameter=>"rptFilterValue2", :message=>"Missing required parameter"}]}}}
When I first tested the API response, I had multiple errors so the way I went about getting the error message was like this
data = Hash.from_xml(response.body).deep_symbolize_keys
if data[:response].has_key?(:errors)
errors = data[:response][:errors][:error].map{|x| "#{x.values[0]} #{x.values[1]}"}
However when there is only one error, the code errors out with undefined method 'values' for parameter
The only actual workaround I found was to test the class of the error key. When Array I use one method for extracting and when Hash I use another method.
if data[:response][:errors][:error].class == Array
errors = data[:response][:errors][:error].map{|x| "#{x.values[0]} #{x.values[1]}"}
else
errors = data[:response][:errors][:error].map{|x| "#{x[1]}"}
end
But I just hate hate hate it. There has to be a way to extract xml/json data from a key that may or may not be an array. The solution may be in the conversion from xml to hash rather than when parsing the actual hash. I couldn't find anything online.
I'll appreciate any help or tip.

If you're using Rails, Array#wrap is available if you can do your .dig first:
single = {:response=>{:version=>"1.0", :code=>"6", :message=>"Data validation failed", :errors=>{:error=>{:parameter=>"rptFilterValue1", :message=>"Parameter is too small"}}}}
Array.wrap(single.dig(:response, :errors, :error))
This returns an Array of size 1:
[
{
:message => "Parameter is too small",
:parameter => "rptFilterValue1"
}
]
For multiples:
multiple = {:response=>{:version=>"1.0", :code=>"6", :message=>"Data validation failed", :errors=>{:error=>[{:parameter=>"rptFilterValue1", :message=>"Parameter is too small"}, {:parameter=>"rptFilterValue2", :message=>"Missing required parameter"}]}}}
Array.wrap(multiple.dig(:response, :errors, :error))
This returns an Array of size 2:
[
{
:message => "Parameter is too small",
:parameter => "rptFilterValue1"
},
{
:message => "Missing required parameter",
:parameter => "rptFilterValue2"
}
]

You can parse XML with Nokogiri and xpath, which returns array even if selector points out single element
errors = Nokogiri::XML(xml_response).xpath('//error')
errors.map { |e| e.children.each_with_object({}) { |x, h| h[x.name] = x.content } }
Your API response with single error gives
=> [{"parameter"=>"rptFilterValue1", "message"=>"Parameter is too small"}]
and API result with multiple errors
=> [{"parameter"=>"rptFilterValue1", "message"=>"Parameter is too small"}, {"parameter"=>"rptFilterValue2", "message"=>"Missing required parameter"}]
If there's no error elements you'll get an empty array.

Related

How to add Error Code to validation attributes in MVC?

I'd like to be able to specify an error code to be returned from my API along with the error message in the event that validation fails. The aim is to specify a validation attribute on a property such as:
[Range(1, int.MaxValue, ErrorMessage = "Page must be 1 or greater", ErrorCode = 1234)]
And, in the event that someone requests page 0, return a 400 Bad Request with a JSON error object in the body like this:
{
"errorCode": 1234,
"errorMessage": "Page : Page must be 1 or greater"
}
I already have a custom ModelValidationFilter which returns an array of error messages from the ModelStateDictionary, so that's all working, but I can't see a straightforward way of getting the error code included in there too without overriding all of the MVC model validation classes.
This seems like a problem which should already have been solved by someone at some point, but I can't find anything to support that theory.

Undefined method permit for string

I'm currently designing and implementing a RoR API.
I'm facing an issue concerning strong parameters.
I'm expecting to receive the following attributes:
{ analysis_data: { barcode: "some_string" } }
To ensure that, I simply use the require/permit combination:
params.require(:analysis_data).permit(:barcode)
This works well if analysis data is nil or an hash.
However, if the client submit a request where analysis_data is a string instead of an hash, the request returns an error 500.
For example:
{ analysis_data: "some_string" }
Produces the following 500 error:
NoMethodError (undefined method `permit' for "barcode":String):
Is there any clean workaround?
Try like this
params.fetch(:analysis_data, {}).permit(:barcode)
Not sure if you want to permit the string and hash or prevent the string without throwing the error. If it's the former you can use params.require(:analysis_data).permit! to allow any value as long as it's inside of analysis_data.
What about:
params[:analysis_data].is_a?(ActionController::Parameters) ? params.fetch(:analysis_data, {}).permit(:barcode) : {}
or more readable like this:
return {} unless params[:analysis_data].is_a? ActionController::Parameters
params.fetch(:analysis_data, {}).permit(:barcode)
That way, .permit will only be called if a ActionController::Parameters is submitted, and otherwise return an empty hash.

How do I inspect the full URL generated by HTTParty?

I want to look at the full URL the HTTParty gem has constructed from my parameters, either before or after it is submitted, it doesn’t matter.
I would also be happy grabbing this from the response object, but I can’t see a way to do that either.
(Bit of background)
I’m building a wrapper for an API using the HTTParty gem. It’s broadly working, but occasionally I get an unexpected response from the remote site, and I want to dig into why – is it something I’ve sent incorrectly? If so, what? Have I somehow malformed the request? Looking at the raw URL would be good for troubleshooting but I can’t see how.
For example:
HTTParty.get('http://example.com/resource', query: { foo: 'bar' })
Presumably generates:
http://example.com/resource?foo=bar
But how can I check this?
In one instance I did this:
HTTParty.get('http://example.com/resource', query: { id_numbers: [1, 2, 3] }
But it didn’t work. Through experimenting I was able to produce this which worked:
HTTParty.get('http://example.com/resource', query: { id_numbers: [1, 2, 3].join(',') }
So clearly HTTParty’s default approach to forming the query string didn’t align with the API designers’ preferred format. That’s fine, but it was awkward to figure out exactly what was needed.
You didn't pass the base URI in your example, so it wouldn't work.
Correcting that, you can get the entire URL like this:
res = HTTParty.get('http://example.com/resource', query: { foo: 'bar' })
res.request.last_uri.to_s
# => "http://example.com/resource?foo=bar"
Using a class:
class Example
include HTTParty
base_uri 'example.com'
def resource
self.class.get("/resource", query: { foo: 'bar' })
end
end
example = Example.new
res = example.resource
res.request.last_uri.to_s
# => "http://example.com/resource?foo=bar"
You can see all of the information of the requests HTTParty sends by first setting:
class Example
include HTTParty
debug_output STDOUT
end
Then it will print the request info, including URL, to the console.
As explained here, if you need to get the URL before making the request, you can do
HTTParty::Request.new(:get, '/my-resources/1', query: { thing: 3 }).uri.to_s

First Google Drive API files.list request returning an array of Hashes, after that, subsequent requests returning an array of File Resources. Why?

I'm querying the Google API to list all files in the drive using the Google API official gem for ruby. I'm using the example given in the Google developers page - https://developers.google.com/drive/v2/reference/files/list
The first request I made returns in the "items" an array of ruby "Hashes". The next requests return in the "items" an array of either "Google::APIClient::Schema::Drive::V2::File" or "Google::APIClient::Schema::Drive::V2::ParentReference" (the reason behind each type also buggs me).
Does anyone know why this happens? At the reference page of "files.list" none is said about changing the type of the results.
def self.retrieve_all_files(client)
drive = client.discovered_api('drive', 'v2')
result = Array.new
page_token = nil
begin
parameters = {}
if page_token.to_s != ''
parameters['pageToken'] = page_token
end
api_result = client.execute(
:api_method => drive.files.list,
:parameters => parameters)
if api_result.status == 200
files = api_result.data
result.concat(files.items)
page_token = files.next_page_token
else
puts "An error occurred: #{result.data['error']['message']}"
page_token = nil
end
end while page_token.to_s != ''
result
end
EDIT:
I couldn't solve the problem yet, but I manage to understand it better:
When the first request to the API is made, after the authorization is granted by the user, the "file.list" returns an array of Hashes at "Items" attribute of the File resource. Each of this Hashes is like a File resource, with all the attributes of the File, the difference is just in the type of the access. For example: the title of the file can be accessed like this "File['title']".
After the first request is made, all the subsequent requests return an array of File resources, that can be accessed like this "File.title".
FYI, this was a bug in the client lib. Using the latest version should fix it.

form serialize problem

I have a form. I am trying to validate it through AJAX GET requests.
So i am trying to send the field values in the GET request data.
$('#uxMyForm').serialize();
the problem it is returning something undecipherable. I have used serialize before. This is totally bizzare.
the return value of serialize is
actionsign_upcontrollersitedataauthenticity_token=oRKIDOlPRqfnRehedcRRD7WXt6%2FQ0zLeQqwIahJZJfE%3D&customer%5BuxName%5D=&customer%5BuxEmail%5D=&customer%5BuxResidentialPhone%5D=&customer%5BuxMobilePhone%5D=&customer%5BuxDateOfBirth%5D=&customer%5BuxAddress%5D=&customer%5BuxResidentialStatus%5D=
i have no idea how to use this.
Thanks
update:
My question is how do i process such a request? like this?
puts params[:data][:customer][:uxName]
my GET request trigger looks like this
$.get('/site/sign_up',{data : $('#uxMyForm').serialize() }, function(data){
alert(data);
});
The above jquery lines generate the request.. on the action method i do the following
render :text => params
when i observe what is sent in the GET,in firebug PARAMS
**data** authenticity_token=oRKIDOlPRqfnRehedcRRD7WXt6%2FQ0zLeQqwIahJZJfE%3D&direct_customer%5BuxName%5D=&direct_customer%5BuxEmail%5D=&direct_customer%5BuxResidentialPhone%5D=&direct_customer%5BuxMobilePhone%5D=&direct_customer%5BuxDateOfBirth%5D=&direct_customer%5BuxAddress%5D=&direct_customer%5BuxResidentialStatus%5D=
the return value that i print in alert has
actionsign_upcontrollersitedataauthenticity_token=oRKIDOlPRqfnRehedcRRD7WXt6%2FQ0zLeQqwIahJZJfE%3D&direct_customer%5BuxName%5D=&direct_customer%5BuxEmail%5D=&direct_customer%5BuxResidentialPhone%5D=&direct_customer%5BuxMobilePhone%5D=&direct_customer%5BuxDateOfBirth%5D=&direct_customer%5BuxAddress%5D=&direct_customer%5BuxResidentialStatus%5D=
How does the form itself look. I have no experience with Ruby on rails - and if it builds the form in a new exciting way - but it looks as if there's only two form elements: authenticity_token and customer - where customer is an array of items. This is the data you posted, but I urldecoded it and put in some linebreaks:
authenticity_token=oRKIDOlPRqfnRehedcRRD7WXt6/Q0zLeQqwIahJZJfE=
&customer[uxName]=
&customer[uxEmail]=
&customer[uxResidentialPhone]=
&customer[uxMobilePhone]=
&customer[uxDateOfBirth]=
&customer[uxAddress]=
&customer[uxResidentialStatus]=
What you could do is to serialize the form to an array and clean it up before sending it using jQuery ajax request. I did something similar once when I had to serialize .net runat-server form elements:
var serializedData = $(form).serializeArray();
for( i=0; i < serializedData.length; i++)
{
// For each item in the array I get the input-field name after the last $ character
var name = serializedData[i].name;
var value = serializedData[i].value;
if( name.indexOf('$') != -1)
name = name.substr( name.lastIndexOf('$')+1 );
serializedData[i].name = name;
}
var ajaxPostData = $.param(serializedData);
So instad of blabla$ABCPlaceHolder$tbxEmail I got tbxEmail in the array. You could do the same to get uxName, uxEmail etc instead of the customer array.
Note then again, however, due to my inexperience with ruby that this may not be the best solution - maybe there's a setting you can change to build the HTML form differently?
Updated:
I'm not sure how ruby works, but after a googling I found you should be able to receive your values using params:customer as an array.
params:customer should contain an array of the values
{:uxName => "", :uxEmail => "" }
I hope that tells you something on how to receive the data. Maybe (warning - wild guess!) like this?
params[:customer][:uxName]

Resources