How do I add information to an exception message in Ruby?
To reraise the exception and modify the message, while preserving the exception class and its backtrace, simply do:
strings.each_with_index do |string, i|
begin
do_risky_operation(string)
rescue Exception => e
raise $!, "Problem with string number #{i}: #{$!}", $!.backtrace
end
end
Which will yield:
# RuntimeError: Problem with string number 0: Original error message here
# backtrace...
How to display error type in ruby?
begin
raise ArgumentError, "I'm a description"
rescue => e
puts "An error of type #{e.class} happened, message is #{e.message}"
end
Prints: An error of type ArgumentError happened, message is I'm a description
How to use custom error messages
Simply modify your script like this.
class MyCustomError < StandardError
def message
"oops... That was not a number"
end
end
def print_a_number
begin
puts "chose a number"
number = Float(gets) rescue false
raise MyCustomError unless number.is_a? Numeric
puts "The number you chose is #{number}"
rescue MyCustomError => err
puts err.message
puts err.backtrace.inspect
end
end
How do I modify the exception message in a rescue clause?
Answer based on @fl00r answer, but slightly more 'fancy':
class ProxyException < BasicObject
def initialize(exception, message=nil)
@exception, @message = exception, message
end
def message
@message || @exception.message
end
def method_missing(method_name, *args, &block)
@exception.public_send(method_name, *args, &block)
end
end
Then simply:
number = 0
begin
['a', nil, 'c'].each_with_index do |entry, i|
entry + 'd'
number = i
end
rescue => e
e = ProxyException.new e, "#{e.message} (happened at entry: #{number})"
store_exception(e)
end
ProxyException is completely invisible as it delegates all the methods, including class
and object_id
to the exception object and hence it retain exception class and backtrace.
Ruby: Prepend info to exception messages without touching stacktrace
If you look at the Kernel#raise method, it can take three parameters:
raise(exception [, string [, array]])
If you want to keep the backtrace, you should specify the array
parameter, which is the callback information.
Example:
Say you originally had:
def some_method()
raise('original message')
end
some_method
#=> scratch.rb:10:in `some_method': original message (RuntimeError)
# from scratch.rb:16:in `<main>'
You can use the third parameter of the exception to raise a new exception with updated message and the same backtrace:
def some_method()
begin
raise('error message')
rescue
raise $!, 'new message', $!.backtrace
end
end
some_method
#=> scratch.rb:10:in `some_method': new message (RuntimeError)
# from scratch.rb:16:in `<main>'
As you can see, the new exception is the same as the original exception, except with the updated message.
Rails API How to add errors in exception and continue
Unsure what are you trying to achieve but I assume you've just removed all the business logic from the model and left only the bare minimum in that code sample.
I'd suggest instead of calling save!
just call save
and check if the object is valid. If it's not continue to do what you want to do or add additional errors, if it's valid then do something else.
Example of how that might look like:
def save_model
@model ||= Model.new(params)
@model.name = nil
@model.save
return true if @model.valid?
@model.add(:name, :invalid, message: "can't be nil")
... some code
end
But instead of that, I'd suggest adding a custom validator instead of trying to reinvent the wheel here you have a guide on how to use it: https://guides.rubyonrails.org/active_record_validations.html#performing-custom-validations
Raise custom Exception with arguments
Solution:
class FooError < StandardError
attr_reader :foo
def initialize(foo)
super
@foo = foo
end
end
This is the best way if you follow the Rubocop Style Guide and always pass your message as the second argument to raise
:
raise FooError.new('foo'), 'bar'
You can get foo
like this:
rescue FooError => error
error.foo # => 'foo'
error.message # => 'bar'
If you want to customize the error message then write:
class FooError < StandardError
attr_reader :foo
def initialize(foo)
super
@foo = foo
end
def message
"The foo is: #{foo}"
end
end
This works well if foo
is required. If you want foo
to be an optional argument, then keep reading.
Explanation:
Pass your message as the second argument to raise
As the Rubocop Style Guide says, the message and the exception class should be provided as separate arguments because if you write:
raise FooError.new('bar')
And want to pass a backtrace to raise
, there is no way to do it without passing the message twice:
raise FooError.new('bar'), 'bar', other_error.backtrace
As this answer says, you will need to pass a backtrace if you want to re-raise an exception as a new instance with the same backtrace and a different message or data.
Implementing FooError
The crux of the problem is that if foo
is an optional argument, there are two different ways of raising exceptions:
raise FooError.new('foo'), 'bar', backtrace # case 1
and
raise FooError, 'bar', backtrace # case 2
and we want FooError
to work with both.
In case 1, since you've provided an error instance rather than a class, raise
sets 'bar'
as the message of the error instance.
In case 2, raise
instantiates FooError
for you and passes 'bar'
as the only argument, but it does not set the message after initialization like in case 1. To set the message, you have to call super
in FooError#initialize
with the message as the only argument.
So in case 1, FooError#initialize
receives 'foo'
, and in case 2, it receives 'bar'
. It's overloaded and there is no way in general to differentiate between these cases. This is a design flaw in Ruby. So if foo
is an optional argument, you have three choices:
(a) accept that the value passed to FooError#initialize
may be either foo
or a message.
(b) Use only case 1 or case 2 style with raise
but not both.
(c) Make foo
a keyword argument.
If you don't want foo
to be a keyword argument, I recommend (a) and my implementation of FooError
above is designed to work that way.
If you raise
a FooError
using case 2 style, the value of foo
is the message, which gets implicitly passed to super
. You will need an explicit super(foo)
if you add more arguments to FooError#initialize
.
If you use a keyword argument (h/t Lemon Cat's answer) then the code looks like:
class FooError < StandardError
attr_reader :foo
def initialize(message, foo: nil)
super(message)
@foo = foo
end
end
And raising looks like:
raise FooError, 'bar', backtrace
raise FooError(foo: 'foo'), 'bar', backtrace
How to add attributes to json exception response in rails 5
You can handle if the record isn't found through your own validation, if you're trying to get a record in your database through the id or any other maybe as:
record = Record.find(params[:id])
Then you can check if that record is nil
, because it couldn't be found, maybe a bad request, then render the json as you want, something like:
if !record.nil?
render json: record, status: 200
else
render json: bad_request
end
And the bad_request method is defined within the ApplicationController
like:
def bad_request
{
error: 'Record not found, maybe a bad request',
status: 400
}
end
Or if in the other hand, you want to handle and set your own response to that behavior directly on your method which is being fired then you can rescue
the ActiveRecord::RecordNotFound
exception, like:
def show
box = Box.find(params[:id])
render json: box, status: 200
rescue ActiveRecord::RecordNotFound
render json: { error: 'Baaaaaad' }
end
Also if you want to make this action available for all your models you can use the rescue_from
method within your ApplicationController
and to set the exception to "catch" and then the method which will respond with, like:
class ApplicationController < ActionController::Base
...
rescue_from ActiveRecord::RecordNotFound, with: :damn_nothing_found
def damn_nothing_found
render json: { error: 'nothing found :c' }, status: :not_found
end
end
Related Topics
Best Solution for Authentication in Ruby on Rails
How to Use Rspec's Should_Raise with Any Kind of Exception
Is There a Natural_Sort_By Method for Ruby
"Stack Level Too Deep" Running Rake Db:Create:All
How to Install Ruby 2 on Ubuntu Without Rvm
Passing Multiple Error Classes to Ruby's Rescue Clause in a Dry Fashion
Bundle Command Not Found. Bad Interpreter
Ruby: Accessing Rake Task from a Gem Without Rails
Always Getting 401 Unauthorized with New Install of Rails + Devise
What Is an Illegal Octal Digit
Tell Ruby Program to Wait Some Amount of Time
How to Invoke an Instance Method on a Ruby Module Without Including It
Convert Time to Other Timezone
Can Rails Migrations Be Used to Convert Data
Ruby Gemspec Dependency: Is Possible Have a Git Branch Dependency