example - Clases de error personalizadas de Ruby: herencia del atributo del mensaje
java custom exception best practice (4)
Parece que no puedo encontrar mucha información sobre clases de excepciones personalizadas.
Lo que sí sé
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 luego, recibe ese mensaje cuando rescatas
rescue MyCustomError => e
puts e.message # => "A message"
Lo que no sé
Quiero dar a mi excepción algunos campos personalizados, pero quiero heredar el atributo del message
de la clase principal. Descubrí leyendo sobre este tema que @message
no es una variable de instancia de la clase de excepción, así que me preocupa que mi herencia no funcione.
¿Alguien puede darme más detalles sobre esto? ¿Cómo implementaría una clase de error personalizada con un atributo de object
? Es el siguiente correcto:
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?
Dado lo que es la documentación de Ruby core de Exception
, de la cual todos los otros errores heredan, estados sobre #message
Devuelve el resultado de invocar exception.to_s. Normalmente esto devuelve el mensaje o nombre de la excepción. Al suministrar un método to_str, las excepciones acuerdan 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 en su mayoría humanos, cuando un servicio externo no ha podido hacer algo.
NOTA: La segunda estrategia a continuación utiliza los métodos de cadenas bonitas de rieles, como demodualize
, que pueden ser un poco complicados y, por lo tanto, potencialmente imprudentes en una excepción. También podría 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
Anulación de la estrategia #initialize
Esta es la estrategia más cercana a las implementaciones que he usado en los rieles. 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 los mensajes 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 la consola de sus rieles, 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")
Yo quería hacer algo similar. Quería pasar un objeto a #new y configurar el mensaje en función de 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
entonces no funcionará. Al abordar el problema del OP, también puede pasar el mensaje como un argumento adicional y almacenar lo que quiera. La parte crucial parece ser anular el #mensaje.
raise
ya establece el mensaje para que no tengas 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
con el rescue MyCustomError
, mira ¿Por qué es un estilo malo ''rescatar Exception => e` en Ruby? .