使用参数提升自定义异常

时间:2012-07-24 18:15:52

标签: ruby-on-rails ruby exception exception-handling

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

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

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

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

6 个答案:

答案 0 :(得分:65)

使用new:

创建例外实例
class CustomException < StandardError
  def initialize(data)
    @data = data
  end
end
# => nil 
raise CustomException.new(bla: "blupp")
# CustomException: CustomException

答案 1 :(得分:13)

解决方案:

class FooError < StandardError
  attr_reader :foo

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

如果您按照Rubocop Style Guide并且始终将您的消息作为第二个参数传递给raise,这是最好的方式:

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

你可以这样得到foo

rescue FooError => error
  error.foo     # => 'foo'
  error.message # => 'bar'

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

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 Style Guide所说,消息和异常类应作为单独的参数提供,因为如果你写:

raise FooError.new('bar')

并希望将回溯传递给raise,如果不传递消息两次就无法做到:

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

this answer所述,如果您想将异常重新引发为具有相同回溯和不同消息或数据的新实例,则需要传递回溯。

实施FooError

问题的症结在于,如果foo是一个可选参数,则有两种不同的方式来引发异常:

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

raise FooError, 'bar', backtrace # case 2

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

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

在案例2 中,raise为您实例化FooError并将'bar'作为唯一参数传递,但它不会在初始化后设置消息,如案例1.要设置消息,您必须在super中调用FooError#initialize,并将消息作为唯一参数。

因此,在案例1中,FooError#initialize收到'foo',而在案例2中收到'bar'。它超载了,一般无法区分这些情况。这是Ruby中的一个设计缺陷。因此,如果foo是可选参数,则有三种选择:

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

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

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

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

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

如果您使用关键字参数(h / t Lemon Cat's answer),则代码如下所示:

class FooError < StandardError
  attr_reader :foo

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

提升看起来像:

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

答案 2 :(得分:8)

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

class MyCustomError < StandardError
    attr_reader :code

    def initialize(code)
        @code = code
    end

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

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

答案 3 :(得分:1)

TL; DR在提出此问题7年后,我认为正确答案是:

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")

警告:您将获得与以下结果相同的结果:

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

但这是因为Exception#exception(string)#rb_obj_clone上进行了self,然后调用exc_initialize(不调用CustomException#initialize。来自{{3} }:

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的后一个示例中,将CustomException raise设置为message并将其设置为“邮件”,并将extra设置为“ blupp”(因为它是一个克隆),但是实际上创建了两个 CustomException个对象:第一个对象是CustomException.new,第二个对象是#raise调用{{ 1}}在#exception的第一个实例上创建了第二个克隆的CustomException

我的扩展舞蹈混音版本为什么位于:error.c

答案 4 :(得分:0)

带有附加信息的自定义错误的简单模式

如果您希望传递的额外信息只是带有消息的类型,则效果很好:

# define custom error class
class MyCustomError < StandardError; end

# raise error with extra information
raise MyCustomError, 'Extra Information'

结果(在I​​RB中):

Traceback (most recent call last):
        2: from (irb):22
        1: from (irb):22:in `rescue in irb_binding'
MyCustomError (Extra Information)

课堂上的例子

以下模式对我(对双关语有意)特别有用。它很干净,可以轻松地进行模块化,并且这些错误很容易表达。在我的课程中,我定义了从StandardError继承的新错误,并通过消息(例如,与错误关联的对象)引发了这些错误。

这是一个简单的示例,类似于OP的原始问题,它在类中引发自定义错误,并在错误消息中捕获方法名称:

class MyUser
  # class errors
  class MyUserInitializationError < StandardError; end

  # instance methods
  def simulate_failure
    raise MyUserInitializationError, "method failed: #{__method__}"
  end
end

# example usage: 
MyUser.new.simulate_failure

# => MyUser::MyUserInitializationError (method failed: simulate_failure)

答案 5 :(得分:-2)

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

begin
  # do something
rescue => e
  error = MyException.new(e, 'some info')
  raise error
end