Спеціальні класи помилок Ruby: успадкування атрибута повідомлення


95

Здається, я не можу знайти багато інформації про власні класи винятків.

Що я знаю

Ви можете оголосити свій власний клас помилок і дозволити йому успадковуватись StandardError, тому це може бути rescued:

class MyCustomError < StandardError
end

Це дозволяє підняти його за допомогою:

raise MyCustomError, "A message"

і пізніше, отримайте це повідомлення під час порятунку

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

Чого я не знаю

Я хочу надати своєму виняткові деякі власні поля, але хочу успадкувати messageатрибут від батьківського класу. Я довідався, що читав цю тему, яка @messageне є змінною екземпляра класу винятків, тому я переживаю, що мій спадок не буде працювати.

Хто-небудь може дати мені більше деталей про це? Як реалізувати власний клас помилок з objectатрибутом? Чи правильно наступне:

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

І потім:

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

отримати:

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

чи це спрацює, і якщо так, чи правильний це спосіб робити?


3
Не варто rescue Exception => e. Це ширше за замовчуванням, rescue => eяке поширюється на StandardError, і охоплює все, включаючи Ctrl + C. Я б зробив rescue MyCustomError => e.
Райан Тейлор

1
@RyanTaylor Я відредагував своє запитання для більш правильного підходу.
MarioDS

Відповіді:


121

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

Я замінив rescue Exceptionна rescue MyCustomError, див. Чому це поганий стиль `рятувати виняток => e` в Ruby? .


Я прийму вашу відповідь, тому що ви показали мені весь синтаксис. Дякую!
MarioDS

1
Тут ми робимо rescue Exception, але чому ні rescue MyCustomError?
Dfr

FYI, якщо перший аргумент, об'єкт, є опцією і raise MyCustomError, "a message"без new, "повідомлення" не буде встановлено.
хіроші

Чи є спосіб якось отримати підняте повідомлення в нашому власному класі винятків?
CyberMew

@CyberMew, що ти маєш на увазі? Що ти хочеш робити?
Стефан

10

Враховуючи те Exception, про що говориться в рубіновій основній документації , від якої успадковуються всі інші помилки#message

Повертає результат виклику exception.to_s. Зазвичай це повертає повідомлення або ім'я винятку. Надаючи метод to_str, винятки погоджуються використовувати там, де очікуються рядки.

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

Я б обрав перевизначення to_s/ to_strабо ініціалізатор. Ось приклад, коли ми хочемо знати, здебільшого зрозумілим для людей, коли зовнішня служба не змогла щось зробити.

ПРИМІТКА: Друга стратегія, наведена нижче, використовує досить рядкові методи рейок, такі як demodualize, які можуть бути дещо складними і, отже, потенційно нерозумними у винятках. Ви також можете додати більше аргументів до підпису методу, якщо вам це потрібно.

Заміна стратегії #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

Вихід консолі

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

Перевизначення # ініціалізації стратегії

Це стратегія, найближча до реалізацій, які я використовував у рейках. Як було зазначено вище, він використовує demodualize, underscoreі humanize ActiveSupportметоду. Але це можна було легко усунути, як у попередній стратегії.

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

Вихід консолі

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

Демо-інструмент

Це демонстрація, яка демонструє порятунок та обмін повідомленнями вищевказаної реалізації. Класом, що створює винятки, є підроблений API для Cloudinary. Просто скиньте одну з вищезазначених стратегій на свою консоль рейок, а потім - цю.

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

6

Ваша ідея правильна, але те, як ви її називаєте, неправильна. Вона повинна бути

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

Гаразд, я думав, що ваше повідомлення було другим параметром для raiseключового слова чи чогось іншого.
MarioDS

Ви визначили initializeдва аргументи. newпередає аргументи до initialize.
sawa

Або ви можете опустити дужки.
sawa

Я розумію , що трохи, але плакат на тему я пов'язаний в моєму питанні робить це так: raise(BillRowError.new(:roamingcalls, @index), "Roaming Calls field missing"). Тож він телефонує raiseз двома параметрами: новий BillRowErrorоб’єкт і його повідомлення. Мене просто бентежить синтаксис ... В інших навчальних посібниках я завжди бачу це так:raise Error, message
MarioDS

1
Проблема не в тому, скільки аргументів ви передаєте raise; це досить гнучко. Проблема в тому, що ви визначили initializeвзяти два аргументи і дали лише один. Подивіться на своєму прикладі. BillRowError.new(:roamingcalls, @index)наводиться два аргументи.
sawa

4

Я хотів зробити щось подібне. Я хотів передати об'єкт #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

Зверніть увагу, що якщо ви не заявите, attr_accessor :messageце не буде працювати. Вирішуючи проблему OP, ви також можете передати повідомлення як додатковий аргумент і зберегти все, що вам подобається. Вирішальною частиною, здається, є перевизначення #message.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.