Rails: validation error codes in JSON - ruby-on-rails

So, I am writing Rails web application which has JSON API for mobile apps. For example, it sends POST JSON request to example.com/api/orders to create order.
{id: 1, order: { product_name: "Pizza", price: 10000}}
In case of validation errors I can response with HTTP 422 error code and order.errors.full_messages in json. But it seems better for me to have specific error code in JSON response. Unfortunately, it seems like Rails does not provide ability to set error code for validation error. How to solve this problem?

You can pass a custom status code by using the status option when rendering the response.
def create
#order = ...
if #order.save
render json: #order
else
render json: { message: "Validation failed", errors: #order.errors }, status: 400
end
end
I usually tend to return HTTP 400 on validation errors. The message is a readable status response, the errors are also attached.
This is a respons example
{
message: "Validation failed",
errors: [
...
]
}
You can also embed additional attributes.

I was after something similar, so what I did was extend String eg
class ErrorCodeString < String
def init(value, error_code)
#error_code = error_code
super(value)
end
def error_code
#error_code
end
end
Then in a custom validation (this won't work on standard validation) I'd do
errors.add(:email, ErrorCodeString.new('cannot be blank', 50)
Now when you return your JSON you can check to see if the error value is an ErrorCodeString and add the error_code value to the output. As ErrorString inherits String, you shouldn't be breaking anything else along the way.

Rails 5 has error.details that can be used for exactly that.
In the model
errors.add(:price, 1023, message: "Transaction value #{price} is above limit (#{maximum_price}).")
In the controller
format.json { render json: #order.errors.details, status: :unprocessable_entity }
error details can be anything, eg. you could also use :above_limit instead of 1023.
The API response body will then look like
pp JSON.parse(response)
{"price"=>[{"error"=>1023}]}
This feature has been backported to Rails 4, see also http://blog.bigbinary.com/2016/05/03/rails-5-adds-a-way-to-get-information-about-types-of-failed-validations.html
Also: Is there a way to return error code in addition to error message in rails active record validation?

Related

Rails API response body is string not json

I have the following REST API call in my code which if wrong returns this error
def get_token
responseObject = {
message: "Error",
errors: ["User does not exist"]
}
render status: 403, json: responseObject
end
Problem is in my response.json(), i get this instead
So when i try to do this in my code response.json().errors, i always get undefined because somehow it doesnt see my response in my body as json.
Actually not sure how to solve this mess.
Thanks

How to customize the JSON that respond_with renders in case of validation errors?

In a controller, I want to replace if..render..else..render with respond_with:
# Current implementation (unwanted)
def create
#product = Product.create(product_params)
if #product.errors.empty?
render json: #product
else
render json: { message: #product.errors.full_messages.to_sentence }
end
end
# Desired implementation (wanted!)
def create
#product = Product.create(product_params)
respond_with(#product)
end
The problem with respond_with is that, in case of a validation error, the JSON renders in a specific way that doesn't fit what the client application expects:
# What the client application expects:
{
"message": "Price must be greater than 0 and name can't be blank"
}
# What respond_with delivers (unwanted):
{
"errors": {
"price": [
"must be greater than 0"
],
"name": [
"can't be blank"
]
}
}
Product, price and name are examples. I want this behavior through the entire application.
I am using the responders gem and I've read it's possible to customize responders and serializers. But how do the pieces fit together?
How to customize the JSON that respond_with renders in case of validation errors?
A couple other ways to customize user alerts
You can just put it in line:
render json: { message: "Price must be greater than 0" }
or: You can just reference your [locale file] and put in custom messages there. 1:
t(:message)
Hope this helps :)
I found a provisory way to get the errors hash as a single sentence. But not only is it hackish, but it also does not match the desired output 100%. I still hope there is a way to do this with a custom serializer or responder.
module ActiveModel
class Errors
def as_json(*args)
full_messages.to_sentence
end
end
end
# OUTPUT
{
"errors": "Price must be greater than 0 and name can't be blank"
}

Printing error when using PARAMS in Rails

For my API in RAILS I have programmed a code that basically does the following.
class Api::V1::NameController < ApplicationController
skip_before_filter :verify_authenticity_token
def index
end
def create
# Loading data
data_1_W = params[:data1]
data_2_W = params[:data2]
while len > i
# -Here I do some calculations with data_1_W and data_2_W.
# Its not important to show the code here
end
# -Organizing outputs to obtain only one JSON-
# Its not important to show the code here
# Finally HTTP responses
if check_error == 1
render status: 200, json: {
message: "Succesful data calculation",
data_output: response_hash
}.to_json
end
end
end
To test that everything is working I use the cURL command. I notice that loading the data could be a problem and therefore the code would break.
I want to tell the user that it was an error loading the data for some reason (HTTP response), but I don't know where to put it. If I put and else under my success status it would not print it because the code breaks just starting (instead of sending the correct name - d '#data.json' of the data in cURL I send -d '#dat.json').
The data I am loading is a JSON data {"data1":[{"name1":"value1"},{"name2":number2}...],"data2":[{"name1":"value1"},{"name2":number2...}]}. (This data has 70080 rows with 2 columns if we see it as a table, which I divided into two in my CODE for calculations purposes data_1_W and data_2_W)
Could anyone help me where to put it? more or less like this:
render status: 500, json: {
message: "Error loading the data",
}.to_json
Put it in a rescue block around the code that throws the error.
E.g.
def func
# code that raises exception
rescue SomeException => e
# render 422
end
Since you are working in Rails I'd recommend going the rails way. This means that I would create some kind of service and initialize it in the create action.
Now, within the service you do all you funky stuff (which also allows you to clean this controller and make i look prettier) and the moment a condition is not fulfilled in that service return false. So...
# controllers/api/v1/name_controller.rb
...
def create
meaningful_variable_name = YourFunkyService.new(args)
if meaningful_variable_name.perform # since you are in create then I assume you're creating some kind of resource
#do something
else
render json: {
error: "Your error",
status: error_code, # I don't think you want to return 500. Since you're the one handling it
}
end
end
# services/api/v1/your_funky_service.rb
class Api::V1::YourFunkyService
def initiliaze(params)
#params = params
end
def perfom #call it save if you wish
....
return false if check_error == 1
end
end

Rails JSON API tests don't return json when exception is thrown

I'm writing a test to check for invalid DELETE requests to a Rails API using Rspec.
This is what I have:
context 'invalid id number' do
it 'returns success: false' do
xhr :delete, :destroy, id: 999999999999999999
expect(JSON.parse(response.body)['success']).to be_false
end
end
Postgres throws some kind of integer overflow exception (as it should), but in my spec I can't look at the JSON object because it's never formed. How can I make it return { success : false } instead of a blank string? How do I force the JSON object to return despite the exception?
When I use pry to look at the json object, I get this error: JSON::ParserError: A JSON text must at least contain two octets! because response.body evaluates to the empty string ""
Whoa, almost forgot to include the controller code.
def destroy
if (site == ::MyModel.find(params[:id]))
site.destroy
render :json => {success: true}
else
render :json => {success: false}
end
There are two issues here:
Depending on your database an id of "999999999999999999" is probably outside of an integer type. I recommend reducing it to below signed integer limit, like 9999.
You are trying to find a non-existent record and its raising a record_not_found exception. I recommend changing your destroy method to:
def destroy
site = ::MyModel.find_by(id:params[:id])
if (site.present?)
render :json => {success: false}
else
site.first.destroy
render :json => {success: true}
end
end
EDIT
#rafb3 is correct find_by and present? is a better choice.
By the sounds of it you will need some kind of rescue statement for the exception:
rescue ArithmeticException => ex
# We need to complete the contract and return json here
#response = { success: false }
end
If you want to learn more about contracts check out this link
Remember to stay away from returning objects in failure responses, as if you send back something like site in this case and site does not exist or the database connection is not there your exception response code may have its own exception!
Also try and stay away from rescue Exception => e explanation here: Why is it a bad style to `rescue Exception => e` in Ruby?
TLDR: Your request always expects a json response so in all places even failure it should return one.

How to handle possible params on Rails 4?

I'm doing an API for my app.
Currently, you can call api/v1/clients and get the Clients JSON back, as expected. You can also do api/v1/clients?client_id=1 and get the JSON representation of the Client object with id 1.
Here's my API::V1::ClientsController:
class API::V1::ClientsController < ApplicationController
def index
if params[:client_id]
#client = Client.find(params[:client_id])
render template: 'api/v1/clients/show'
else
#clients = Client.all
end
end
end
I want that if, for example, you have a typo on the endpoint (api/v1/clients?clent_id=1), the app returns a JSON object with an error:
{
error: {
error_code: 10,
error_description: "Bad endpoint"
}
}
Is there a way to, say, make a switch statement on the params to handle the possible cases?
My suggestion:
Make a private method in your controller, this one will check your params:
If params is empty it returns false
If params contains 'client_id' and its value is a numeric it returns the value
Otherwise it raises an exception.
Then in you action method you use this result:
If the result is false you display all results
Otherwise it display the record based on the id returned by your private method
As for the exception: you use a rescue_from to display the "Bad endpoint" JSON response

Resources