How to handle bad requests / wrong parameters in a rails api? - ruby-on-rails

For example, an api consumer sends:
"ticket": [{ "param": "value"}]
The controller does:
params.require(:ticket).permit(:name)
This would return a 500 error: "Undefined method permit for array"
Is there a DRY / best practice way to handle this? I think a status 400 should be returned instead.

I think the strong parameters gem should handle this internally, but they don't, so here is my solution
rescue_from NoMethodError do |exception|
if exception.message =~ /undefined method `permit' for/
render_error(message: 'Invalid parameter format.', type: :invalid_parameters, status: :bad_request)
else
raise
end
end

Based on your solution .. you would want to handle all the possibilities verse a specific error response. You can set in a rescue in your render method like this and set unauthorized if there is a method error or just something else going wrong :
begin
render json: json, status: 200
rescue_from NoMethodError do |exception|
render :unauthorized
end
rescue => e
render :unauthorized
end

Related

rails how add a response when raising a custom exception

When raising an exception in rails, I would like to add a custom response as well.
For example if Im making a custom 404 exception, then i would like the response to be something like msg: "no record found. I was thinking of doing something like this:
raise customError, "msg: no record found"
but that doesnt seem to work. Is there another way I can go about this?
You could use rescue_from to rescue all your customErrors in your controller, and then render the response
class ApplicationController
rescue_from CustomError do |exception|
render_json json: { msg: exception.message }, status: 404
end
end

Rails: send a 401 error without rendering a page

I'm trying to send a 401 authorized from a Rails controller for webhooks, but I can't seem to the right way to do it since I'm not rendering a page.
render plain: "Unauthorized", status: :unauthorized
throws this error:
Read error: #<NoMethodError: undefined method `bytesize' for [:error, "Unauthorized"]:Array>
I don't understand why because
render plain: "Unauthorized", status: 400
works fine.
head(:unauthorized)
Returns a response that has no content (merely a status code and headers).
See ActionController::Head.
Are you, by any chance, using this snippet from rails_warden or equivalent middleware?
If so, the correct stanza is
manager.failure_app = Proc.new { |_env|
['401', {'Content-Type' => 'application/json'}, [{ error: 'Unauthorized' }.to_json]]
}

How to test with rspec a returned exception?

def show
begin
#cart = Cart.find(params[:id])
authorize(#cart)
#cart_entries = CartEntry.where(:cart_id => #cart.id)
#products = {}
#pr_references = {}
#cart_entries.each do |cart_entry|
#pr_references[cart_entry.id] = Reference.find(cart_entry.reference_id)
#products[cart_entry.id] = Product.find(#pr_references[cart_entry.id].product_id)
end
rescue ActiveRecord::RecordNotFound => e
respond_to do |format|
format.json {render json: {'error': e}, status: :not_found}
end
end
I want to test when Cart.find() doesn't find the cart and I want to test the method return a 404 HTTP code with the test below.
it 'don\'t find cart, should return 404 error status' do
delete :destroy, params: {id: 123, format: 'json'}
expect(response).to have_http_status(404)
end
Have you got some indications or solution to do that ?
I'm a nooby with ruby on rails, if you have some tips with the code I posted I'll take it.
Thank you :)
It seems some other code is raising an exception before your Cart.find statement is executed. For this reason, the ActiveRecord::RecordNotFound exception is never risen, and it is never captured by the rescue block.
Based on the exception that is raising, it seems you are using Pundit gem for dealing with authorization. The authorization rules offered by this gem are surely running before your show method starts. Probably this is happening as a consequence of a before_filter statement, either in this controller or in a parent controller.
You will need to handle this kind of errors in your application. It may be handy to use a rescue_form statement in a base controller that is inherited by all other controllers, so that you don't have to deal with this kind of errors in every controller.

Rails 4.2 ActionController:BadRequest custom error message

I want to return from my controller if either a validation failed or a parameter is missing with 400 - bad request. So in my controller if have
if params["patch"].nil? then
raise ActionController::BadRequest.new( "The Json body needs to be wrapped inside a \"Patch\" key")
end
and i catch this error in my Application Controller with:
rescue_from ActionController::BadRequest, with: :bad_request
def bad_request(exception)
render status: 400, json: {:error => exception.message}.to_json
end
But it seems like i cannot add custom messages when raising ActionController::BadRequest. Because when passing an invalid body the response is only {"error":"ActionController::BadRequest"} and not the hash i provided.
In the console i get the same behaviour. raise ActiveRecord::RecordNotFound, "foo" indeed raises ActiveRecord::RecordNotFound: foo.
But raise ActionController::BadRequest, "baa" results in
ActionController::BadRequest: ActionController::BadRequest
How can i add custom messages to the BadRequest exception?
Try this:
raise ActionController::BadRequest.new(), "The Json body needs to be wrapped inside a \"Patch\" key"

Rails wont render a RestClient exception body unless I interpolate it

I'm having a really weird issue trying to write a test around a rails controller.
The controller uses RestClient to get info from another server. If that 2nd server returns a bad response, I want to forward on that error message to the client.
The controller:
def callback_submit
begin
response = RestClient.post(...)
render json: response
rescue RestClient::BadRequest => e
render status: :bad_request, json: { error: e.response.body }
end
end
Then I have an rspec test for this:
it 'returns a failure message' do
stub_request( :post, 'http://...' ).to_return( status: 400, body: 'some error' )
post :callback_submit
expect(response.body).to eq({error: 'some error'}.to_json)
end
Now the problem is that the rspec output is:
expected: "{\"error\":\"some error\"}"
got: "{\"error\":\"\"}"
Where it gets weird is that I can change my controller to puts some details...
rescue RestClient::BadRequest => e
error = e.response.body.to_s
puts error.class
puts error
render status: :bad_request, json: { error: error }
Which outputs:
String
some error
expected: "{\"error\":\"some error\"}"
got: "{\"error\":\"\"}"
So the error variable seems to be a string and the correct value, but render turns it into an empty string.
Now it gets really weird... If I interpolate the string:
rescue RestClient::BadRequest => e
error = e.response.body.to_s
puts error.class
puts error
render status: :bad_request, json: { error: "#{error}" }
Or just simply:
rescue RestClient::BadRequest => e
render status: :bad_request, json: { error: "#{e.response.body}" }
Then my test PASSES!
At first I thought e.response.body must not be a String, which is why I puts its class which prints "String" and also called .to_s on it, but that didn't seem to change anything.
Can anyone explain this weird behavior?
Asked some Ruby friends and one of them found the problem, but didn't want to come post an answer here....
The problem ends up being that we are on an older RestClient (1.8). In the RestClient 2.0 notes:
Response objects are now a subclass of String rather than a String that mixes in the response functionality.
Response#body and #to_s will now return a true String object rather than self. Previously there was no easy way to get the true String response instead of the Frankenstein response string object with AbstractResponse mixed in.
So in the older version I am using, e.response ends up being this weird String mixin mashup thing, and render doesn't seem to like it.
The interpolation turns it back into a real Ruby string again.
The problem is that response.body is looking at the response object and the body is empty because of the error.
Chances are you want e.message or just e.to_s which will be the error message (or the error class name) rather than the response body.
Excerpt:
class Exception < RuntimeError
attr_accessor :response
attr_writer :message
#.......
def to_s
message
end
def message
#message || default_message
end
def default_message
self.class.name
end
end
Source

Resources