How to Add Information to an Exception Message in Ruby

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



Leave a reply



Submit