Ruby 自定义错误类:消息属性的继承

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16106645/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-06 05:53:37  来源:igfitidea点击:

Ruby custom error classes: inheritance of the message attribute

rubyexceptioninheritanceexception-handlingcustom-exceptions

提问by MarioDS

I can't seem to find much information about custom exception classes.

我似乎找不到关于自定义异常类的很多信息。

What I do know

我所知道的

You can declare your custom error class and let it inherit from StandardError, so it can be rescued:

您可以声明您的自定义错误类并让它继承自StandardError,因此它可以是rescued:

class MyCustomError < StandardError
end

This allows you to raise it using:

这允许您使用以下方法提高它:

raise MyCustomError, "A message"

and later, get that message when rescuing

稍后,在救援时收到该消息

rescue MyCustomError => e
  puts e.message # => "A message"

What I don't know

我不知道的

I want to give my exception some custom fields, but I want to inherit the messageattribute from the parent class. I found out reading on this topicthat @messageis not an instance variable of the exception class, so I'm worried that my inheritance won't work.

我想给我的异常一些自定义字段,但我想message从父类继承该属性。我发现阅读关于这个主题@message不是异常类的实例变量,所以我担心我的产业将无法正常工作。

Can anyone give me more details to this? How would I implement a custom error class with an objectattribute? Is the following correct:

任何人都可以给我更多的细节吗?我将如何实现带有object属性的自定义错误类?以下是否正确:

class MyCustomError < StandardError
  attr_reader :object
  def initialize(message, object)
    super(message)
    @object = object
  end
end

And then:

进而:

raise MyCustomError.new(anObject), "A message"

to get:

要得到:

rescue MyCustomError => e
  puts e.message # => "A message"
  puts e.object # => anObject

will it work, and if it does, is this the correct way of doing things?

它会起作用吗,如果起作用,这是正确的做事方式吗?

回答by Stefan

raisealready sets the message so you don't have to pass it to the constructor:

raise已经设置了消息,因此您不必将其传递给构造函数:

class MyCustomError < StandardError
  attr_reader :object

  def initialize(object)
    @object = object
  end
end

begin
  raise MyCustomError.new("an object"), "a message"
rescue MyCustomError => e
  puts e.message # => "a message"
  puts e.object # => "an object"
end

I've replaced rescue Exceptionwith rescue MyCustomError, see Why is it a bad style to `rescue Exception => e` in Ruby?.

我已替换rescue Exceptionrescue MyCustomError,请参阅为什么在 Ruby 中`rescue Exception => e` 是一种糟糕的风格?.

回答by Chad M

Given what the ruby core documentation of Exception, from which all other errors inherit, states about #message

鉴于 ruby​​ 核心文档的内容Exception,所有其他错误都继承自该文档,说明了#message

Returns the result of invoking exception.to_s. Normally this returns the exception's message or name. By supplying a to_str method, exceptions are agreeing to be used where Strings are expected.

返回调用 exception.to_s 的结果。通常这会返回异常的消息或名称。通过提供 to_str 方法,同意在需要字符串的地方使用异常。

http://ruby-doc.org/core-1.9.3/Exception.html#method-i-message

http://ruby-doc.org/core-1.9.3/Exception.html#method-i-message

I would opt for redefining to_s/to_stror the initializer. Here is an example where we want to know, in a mostly human readable way, when an external service has failed to do something.

我会选择重新定义to_s/to_str或初始化程序。这是一个示例,我们想以一种大多数人类可读的方式知道外部服务何时无法执行某些操作。

NOTE: The second strategy below uses the rails pretty string methods, such as demodualize, which may be a little complicated and therefore potentially unwise to do in an exception. You could also add more arguments to the method signature, should you need.

注意:下面的第二个策略使用 rails 漂亮的字符串方法,例如demodualize,这可能有点复杂,因此在异常中可能不明智。如果需要,您还可以向方法签名添加更多参数。

Overriding #to_s Strategynot #to_str, it works differently

覆盖 #to_s 策略而不是 #to_str,它的工作方式不同

module ExternalService

  class FailedCRUDError < ::StandardError
    def to_s
      'failed to crud with external service'
    end
  end

  class FailedToCreateError < FailedCRUDError; end
  class FailedToReadError < FailedCRUDError; end
  class FailedToUpdateError < FailedCRUDError; end
  class FailedToDeleteError < FailedCRUDError; end
end

Console Output

控制台输出

begin; raise ExternalService::FailedToCreateError; rescue => e; e.message; end
# => "failed to crud with external service"

begin; raise ExternalService::FailedToCreateError, 'custom message'; rescue => e; e.message; end
# => "failed to crud with external service"

begin; raise ExternalService::FailedToCreateError.new('custom message'); rescue => e; e.message; end
# => "failed to crud with external service"

raise ExternalService::FailedToCreateError
# ExternalService::FailedToCreateError: failed to crud with external service

Overriding #initialize Strategy

覆盖#initialize 策略

This is the strategy closest to implementations I've used in rails. As noted above, it uses the demodualize, underscore, and humanizeActiveSupportmethods. But this could be easily removed, as in the previous strategy.

这是最接近我在 rails 中使用的实现的策略。如上所述,它使用demodualizeunderscore以及humanizeActiveSupport方法。但这可以很容易地删除,就像之前的策略一样。

module ExternalService
  class FailedCRUDError < ::StandardError
    def initialize(service_model=nil)
      super("#{self.class.name.demodulize.underscore.humanize} using #{service_model.class}")
    end
  end

  class FailedToCreateError < FailedCRUDError; end
  class FailedToReadError < FailedCRUDError; end
  class FailedToUpdateError < FailedCRUDError; end
  class FailedToDeleteError < FailedCRUDError; end
end

Console Output

控制台输出

begin; raise ExternalService::FailedToCreateError; rescue => e; e.message; end
# => "Failed to create error using NilClass"

begin; raise ExternalService::FailedToCreateError, Object.new; rescue => e; e.message; end
# => "Failed to create error using Object"

begin; raise ExternalService::FailedToCreateError.new(Object.new); rescue => e; e.message; end
# => "Failed to create error using Object"

raise ExternalService::FailedCRUDError
# ExternalService::FailedCRUDError: Failed crud error using NilClass

raise ExternalService::FailedCRUDError.new(Object.new)
# RuntimeError: ExternalService::FailedCRUDError using Object

Demo Tool

演示工具

This is a demo to show rescuing and messaging of the above implementation. The class raising the exceptions is a fake API to Cloudinary. Just dump one of the above strategies into your rails console, followed by this.

这是一个演示,用于展示上述实现的救援和消息传递。引发异常的类是 Cloudinary 的虚假 API。只需将上述策略之一转储到您的 rails 控制台中,然后执行此操作。

require 'rails' # only needed for second strategy 

module ExternalService
  class FailedCRUDError < ::StandardError
    def initialize(service_model=nil)
      @service_model = service_model
      super("#{self.class.name.demodulize.underscore.humanize} using #{@service_model.class}")
    end
  end

  class FailedToCreateError < FailedCRUDError; end
  class FailedToReadError < FailedCRUDError; end
  class FailedToUpdateError < FailedCRUDError; end
  class FailedToDeleteError < FailedCRUDError; end
end

# Stub service representing 3rd party cloud storage
class Cloudinary

  def initialize(*error_args)
    @error_args = error_args.flatten
  end

  def create_read_update_or_delete
    begin
      try_and_fail
    rescue ExternalService::FailedCRUDError => e
      e.message
    end
  end

  private def try_and_fail
    raise *@error_args
  end
end

errors_map = [
  # Without an arg
  ExternalService::FailedCRUDError,
  ExternalService::FailedToCreateError,
  ExternalService::FailedToReadError,
  ExternalService::FailedToUpdateError,
  ExternalService::FailedToDeleteError,
  # Instantiated without an arg
  ExternalService::FailedCRUDError.new,
  ExternalService::FailedToCreateError.new,
  ExternalService::FailedToReadError.new,
  ExternalService::FailedToUpdateError.new,
  ExternalService::FailedToDeleteError.new,
  # With an arg
  [ExternalService::FailedCRUDError, Object.new],
  [ExternalService::FailedToCreateError, Object.new],
  [ExternalService::FailedToReadError, Object.new],
  [ExternalService::FailedToUpdateError, Object.new],
  [ExternalService::FailedToDeleteError, Object.new],
  # Instantiated with an arg
  ExternalService::FailedCRUDError.new(Object.new),
  ExternalService::FailedToCreateError.new(Object.new),
  ExternalService::FailedToReadError.new(Object.new),
  ExternalService::FailedToUpdateError.new(Object.new),
  ExternalService::FailedToDeleteError.new(Object.new),
].inject({}) do |errors, args|
  begin 
    errors.merge!( args => Cloudinary.new(args).create_read_update_or_delete)
  rescue => e
    binding.pry
  end
end

if defined?(pp) || require('pp')
  pp errors_map
else
  errors_map.each{ |set| puts set.inspect }
end

回答by sawa

Your idea is right, but the way you call it is wrong. It should be

你的想法是对的,但你称呼它的方式是错误的。它应该是

raise MyCustomError.new(an_object, "A message")

回答by Huliax

I wanted to do something similar. I wanted to pass an object to #new and have the message set based on some processing of the passed object. The following works.

我想做类似的事情。我想将一个对象传递给 #new 并根据传递的对象的一些处理设置消息。以下作品。

class FooError < StandardError
  attr_accessor :message # this is critical!
  def initialize(stuff)
    @message = stuff.reverse
  end
end

begin
  raise FooError.new("!dlroW olleH")
rescue FooError => e
  puts e.message #=> Hello World!
end

Note that if you don't declare attr_accessor :messagethen it will not work. Addressing the OP's issue, you could also pass the message as an additional argument and store anything you like. The crucial part appears to be overriding #message.

请注意,如果您不声明,attr_accessor :message则它将不起作用。解决 OP 的问题,您还可以将消息作为附加参数传递并存储您喜欢的任何内容。关键部分似乎是覆盖#message。