Clases de error personalizadas de Ruby: herencia del atributo de mensaje

Parece que no puedo encontrar mucha información sobre las clases de excepción personalizadas.

Lo que se

Puede declarar su clase de error personalizada y dejarla heredar de StandardError , para que pueda ser rescue d:

 class MyCustomError < StandardError end 

Esto te permite subirlo usando:

 raise MyCustomError, "A message" 

y más tarde, recibe ese mensaje al rescatar

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

Lo que no se

Quiero dar a mi excepción algunos campos personalizados, pero quiero heredar el atributo de message de la clase principal. Descubrí leyendo en este tema que @message no es una variable de instancia de la clase de excepción, por lo que me preocupa que mi herencia no funcione.

¿Alguien puede darme más detalles a esto? ¿Cómo implementaría una clase de error personalizada con un atributo de object ? Es correcto lo siguiente:

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

Y entonces:

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

Llegar:

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

¿Funcionará, y si lo hace, es esta la forma correcta de hacer las cosas?

raise ya establece el mensaje para que no tenga que pasarlo al constructor:

 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 

He reemplazado la rescue Exception de rescue MyCustomError por la de rescue MyCustomError , vea ¿Por qué es un mal estilo para rescatar a Excepción => e` en Ruby? .

Teniendo en cuenta lo que indica la documentación de Ruby Core de Exception , de la que se heredan todos los demás errores, sobre #message

Devuelve el resultado de invocar exception.to_s. Normalmente esto devuelve el mensaje o el nombre de la excepción. Al proporcionar un método to_str, las excepciones están de acuerdo en ser utilizadas donde se esperan cadenas.

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

to_s por redefinir to_s / to_str o el inicializador. Aquí hay un ejemplo en el que queremos saber, en una forma legible para los humanos, cuando un servicio externo no ha podido hacer algo.

NOTA: La segunda estrategia a continuación utiliza los métodos de cadenas de Rails, como demodualize , que pueden ser un poco complicados y, por lo tanto, potencialmente imprudentes en una excepción. También puede agregar más argumentos a la firma del método, si lo necesita.

Anulando la estrategia #to_s no #to_str, funciona de manera diferente

 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 

Salida de consola

 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 

Anulando la estrategia #inicializar

Esta es la estrategia más cercana a las implementaciones que he usado en los Rails. Como se indicó anteriormente, utiliza los demodualize , underscore y humanize ActiveSupport . Pero esto podría eliminarse fácilmente, como en la estrategia anterior.

 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 

Salida de consola

 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 

Herramienta de demostración

Esta es una demostración para mostrar el rescate y la mensajería de la implementación anterior. La clase que plantea las excepciones es una API falsa para Cloudinary. Simplemente descargue una de las estrategias anteriores en su consola de Rails, seguido de esto.

 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 

Tu idea es correcta, pero la forma en que la llamas es incorrecta. Debería ser

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

Quería hacer algo similar. Quería pasar un objeto a #nuevo y tener el conjunto de mensajes basado en algún procesamiento del objeto pasado. Los siguientes trabajos.

 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 

Tenga en cuenta que si no declara attr_accessor :message , no funcionará. Al abordar el problema del OP, también podría pasar el mensaje como un argumento adicional y almacenar lo que quiera. La parte crucial parece estar anulando #message.

Intereting Posts