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"
Related
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
I built an error handler that, when there's an error in any controller method in production, reroutes the user to an error page and sends me, the developer, a notification email. This works, but I want the normal error screen to appear when I'm in development. I assumed the code to produce this was just raise e, but instead in development I'm now getting the default production error page (the one that says "We're sorry, but something went wrong".), instead of the detailed error message and trace that used to appear.
class ApplicationController < ActionController::Base
rescue_from StandardError, with: :handle_error
#error handler
def handle_error(e)
if Rails.env.production?
#code to send email and redirect to error page
else
raise e
end
end
end
I also tried the following:
raise
raise StandardError.new e
raise e, e.message
raise e.message
and if I run any of those in a binding.pry console, they produce the error message I'm looking for, but the error page still just says "We're sorry, but something went wrong."
Anyone know how I can just show the default development error page?
UPDATE
This is insane...so the code to display an error normally should definitely work, but something somewhere is preventing that. If I change config.consider_all_requests_local = true on production, the errors show up on production, but then even if I copy and paste my config/environments/production.rb file into my config/environments/development.rb, the errors still don't show on development. If I enter a pry console, request.local? returns "0", signifying true, and Rails.env returns "development". I have no idea what is going on.
UPDATE 2
Apparently I'm not supposed to be rescuing exceptions on development, but even if I delete every bit of custom error handling code so my Application Controller is just empty, my errors still don't show on development. Further, I have a different app with the same exact error handling code, and for that the errors do show.
Search your code for consider_all_requests_local, it's this configuration that show the full error log.
It must be set as true on your development.rb config file. It's either missing from your configs, or other config is overwriting it
This is not "insane", this is completely expected behavior. You cannot raise from within a rescue_from handler. That would cause an infinite loop.
You also cannot rescue_from StandardError as stated specifically in the documentation:
Using rescue_from with Exception or StandardError would cause serious side-effects as it prevents Rails from handling exceptions properly. As such, it is not recommended to do so unless there is a strong reason.
Instead of conditionally handling the exception inside your rescue_fromhandler, you should conditionally bind the handler, and pick a more specific exception class to handle.
class ApplicationController < ActionController::Base
rescue_from StandardError, with: :handle_error if Rails.env.production?
#error handler
def handle_error(e)
#code to send email and redirect to error page
end
end
create ErrorsController like below
class ErrorsController < ApplicationController
skip_before_action :login
def not_found
respond_to do |format|
format.html { render status: 404 }
format.json { render json: { error: "Not found" }, status: 404 }
end
end
end
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
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
I have a Rails app for providing a Json REST-Api. In my ApplicationController, I have these lines to catch various errors:
rescue_from ActiveRecord::RecordNotFound, with: :_404
rescue_from ActiveRecord::RecordInvalid, with: :_400
rescue_from ActiveRecord::RecordNotUnique, with: :_406
....
def _404(exception)
Rails.logger.error "head 404 with params #{params}"
render status: 404, :json => {:error => exception.message}
end
In one of my controllers, I have a statement like those where I query the db multiple times for different records.
#account = Account.find_by_phone(params[:phone])
#controller = Controller.find_by_controller_id(params[:role_id])
#batlockers_rel = Batlocker.includes(:customer).where("customer_id is not null")
When I make a call into the controller hitting the specific action, I get the header and the response body
HTTP/1.1 501 NOT IMPLEMENTED
{"error":"undefined method `[]' for nil:NilClass"}
for the requested parameters which are not linked to any database objects.
How do I catch these NilErrors, and how can I then include the record which was not found?
Possibly capture NameError class or more generally StandardError:
rescue_from ::NameError, with: :_whatever
rescue_from StandardError, with: :_whatever
But this types of errors you need to handle yourself, of-cource you need to capture for the first time but you should resolve the error, your app not supposed to throw this kind of errors, check if the data is there before access elements.