关于rails上的ruby:使用参数提升自定义Exception

Raise custom Exception with arguments

我在rails中的模型上定义了一个自定义异常,作为一种包装异常:( beginrescue[raise custom exception]end)

当我提出异常时,我想传递一些关于a)内部函数引发错误的模型实例,以及b)捕获的错误的信息。

这是一个模型的自动导入方法,该方法由来自外部数据源的POST请求填充。

tldr; 如果您自己定义Exception,如何将参数传递给Exception? 我在该Exception上有一个initialize方法,但raise语法似乎只接受一个Exception类和消息,没有传递给实例化过程的可选参数。


使用new创建例外实例:

1
2
3
4
5
6
7
8
class CustomException < StandardError
  def initialize(data)
    @data = data
  end
end
# => nil
raise CustomException.new(bla:"blupp")
# CustomException: CustomException


解:

1
2
3
4
5
6
7
8
class FooError < StandardError
  attr_reader :foo

  def initialize(foo)
   super
   @foo = foo
  end
end

如果您遵循Rubocop样式指南并始终将您的消息作为第二个参数传递给raise,这是最好的方法:

1
raise FooError.new('foo'), 'bar'

你可以像这样得到foo

1
2
3
rescue FooError => error
  error.foo     # => 'foo'
  error.message # => 'bar'

如果要自定义错误消息,请写入:

1
2
3
4
5
6
7
8
9
10
11
12
class FooError < StandardError
  attr_reader :foo

  def initialize(foo)
   super
   @foo = foo
  end

  def message
   "The foo is: #{foo}"
  end
end

如果需要foo,这很有效。如果希望foo成为可选参数,请继续阅读。

说明:

将您的消息作为第二个参数传递给raise

正如Rubocop样式指南所说,消息和异常类应该作为单独的参数提供,因为如果你写:

1
raise FooError.new('bar')

并且想要将回溯传递给raise,没有两次传递消息就无法做到:

1
raise FooError.new('bar'), 'bar', other_error.backtrace

正如这个答案所说,如果你想将异常重新引发为具有相同回溯和不同消息或数据的新实例,则需要传递回溯。

实施FooError

问题的关键在于,如果foo是可选参数,则有两种不同的方法可以引发异常:

1
raise FooError.new('foo'), 'bar', backtrace # case 1

1
raise FooError, 'bar', backtrace # case 2

我们希望FooError能够同时使用它们。

在案例1中,由于您提供了错误实例而不是类,因此raise'bar'设置为错误实例的消息。

在第2种情况下,raise为您实例化FooError并传递'bar'作为唯一参数,但它不会像初始化1那样在初始化后设置消息。要设置消息,您必须调用super in FooError#initialize将消息作为唯一参数。

因此,在情况1中,FooError#initialize接收'foo',并且在情况2中,它接收'bar'。它过载了,通常无法区分这些情况。这是Ruby中的一个设计缺陷。因此,如果foo是可选参数,则有三种选择:

(a)接受传递给FooError#initialize的值可以是foo或消息。

(b)仅使用案例1或案例2样式与raise但不能同时使用两者。

(c)使foo成为关键字参数。

如果您不希望foo成为关键字参数,我建议(a)并且上面的FooError的实现旨在以这种方式工作。

如果使用case 2样式raise a FooError,则foo的值是消息,它将隐式传递给super。如果向FooError#initialize添加更多参数,则需要显式super(foo)

如果您使用关键字参数(h / t Lemon Cat的答案),那么代码如下所示:

1
2
3
4
5
6
7
8
class FooError < StandardError
  attr_reader :foo

  def initialize(message, foo: nil)
   super(message)
   @foo = foo
  end
end

提升看起来像:

1
2
raise FooError, 'bar', backtrace
raise FooError(foo: 'foo'), 'bar', backtrace


以下是向错误添加代码的示例代码:

1
2
3
4
5
6
7
8
9
10
11
class MyCustomError < StandardError
    attr_reader :code

    def initialize(code)
        @code = code
    end

    def to_s
       "[#{code}] #{super}"
    end
end

并提出它:
raise MyCustomError.new(code), message


TL; DR在这个问题之后7年,我相信正确的答案是:

1
2
3
4
5
6
7
8
9
class CustomException < StandardError
  attr_reader :extra
  def initialize(message=nil, extra: nil)
    super(message)
    @extra = extra
  end
end
# => nil
raise CustomException.new('some message', extra:"blupp")

警告:您将得到相同的结果:

1
raise CustomException.new(extra: 'blupp'), 'some message'

但那是因为Exception#exception(string)self上执行#rb_obj_clone,然后调用exc_initialize(不调用CustomException#initialize。来自error.c:

1
2
3
4
5
6
7
8
9
10
11
12
static VALUE
exc_exception(int argc, VALUE *argv, VALUE self)
{
    VALUE exc;

    if (argc == 0) return self;
    if (argc == 1 && self == argv[0]) return self;
    exc = rb_obj_clone(self);
    exc_initialize(argc, argv, exc);

    return exc;
}

#raise以上的后一个示例中,CustomExceptionraise d,message设置为"消息",extra设置为"blupp"(因为它是克隆)但是TWO <实际上创建了x40>对象:第一个是CustomException.new,第二个是#raiseCustomException的第一个实例上调用#exception,它创建了第二个克隆的CustomException

我的扩展舞蹈混音版本的原因是:https://stackoverflow.com/a/56371923/5299483


您可以创建Exception子类的新实例,然后提高它。例如:

1
2
3
4
5
6
begin
  # do something
rescue => e
  error = MyException.new(e, 'some info')
  raise error
end