Ruby özel hata sınıfları: mesaj özniteliğinin kalıtımı


95

Özel istisna sınıfları hakkında fazla bilgi bulamıyorum.

Ne biliyorum

Özel hata sınıfınızı bildirebilir ve miras almasına izin StandardErrorverebilirsiniz, böylece rescued olabilir :

class MyCustomError < StandardError
end

Bu, aşağıdakileri kullanarak yükseltmenizi sağlar:

raise MyCustomError, "A message"

ve daha sonra, kurtarırken bu mesajı alın

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

Ne bilmiyorum

İstisnama bazı özel alanlar vermek istiyorum, ancak messageözniteliği üst sınıftan devralmak istiyorum . Okumayı öğrendim bu konuda@message ben endişeliyim, böylece istisna sınıfının bir örneğidir değişken değildir benim miras işi olmaz o.

Biri bana bununla ilgili daha fazla ayrıntı verebilir mi? Bir objectöznitelikle özel bir hata sınıfını nasıl uygularım ? Aşağıdaki doğru mu:

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

Ve sonra:

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

almak:

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

işe yarayacak mı ve işe yararsa, işleri yapmanın doğru yolu bu mu?


3
Yapma rescue Exception => e. Varsayılan daha geniş olduğunu rescue => euzanır StandardErrorve Ctrl + C dahil olmak üzere her şeyi yakalar. Yapardım rescue MyCustomError => e.
Ryan Taylor

1
@RyanTaylor Daha doğru yaklaşım için sorumu düzenledim.
MarioDS

Yanıtlar:


122

raise zaten mesajı kurucuya iletmeniz gerekmeyecek şekilde ayarlar:

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

Ben yerini ettik rescue Exceptionile rescue MyCustomError, bakınız o `kurtarma İstisna => e` Ruby için kötü stil Neden? .


Cevabınızı kabul edeceğim çünkü bana tüm sözdizimini gösterdiniz. Teşekkürler!
MarioDS

1
İşte yapıyoruz rescue Exception, ama neden olmasın rescue MyCustomError?
Dfr

Bilginize, ilk bağımsız değişken olan nesne bir seçenekse ve "mesaj" raise MyCustomError, "a message"olmadan new"mesaj" ayarlanmayacaktır.
hiroshi

Özel istisna sınıfımızda dile getirilen mesajı bir şekilde almanın bir yolu var mı?
CyberMew

@CyberMew ne demek istiyorsun? Ne yapmak istiyorsun?
Stefan

10

ExceptionDiğer tüm hataların miras aldığı Ruby çekirdek belgesinin ne ifade ettiği göz önüne alındığında#message

İstisna.to_s çağrılmasının sonucunu döndürür. Normalde bu, istisnanın mesajını veya adını döndürür. Bir to_str yöntemi sağlayarak, dizelerin beklendiği durumlarda istisnalar kullanılmayı kabul eder.

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

Yeniden tanımlamayı to_s/ to_strveya başlatıcıyı tercih ederim . Burada, harici bir hizmetin bir şeyi yapamaması durumunda, çoğunlukla insan tarafından okunabilir bir şekilde bilmek istediğimiz bir örnek var.

NOT: Aşağıdaki ikinci strateji demodualize, biraz karmaşık olabilen ve bu nedenle bir istisna durumunda yapılması akıllıca olmayan raylar gibi oldukça dize yöntemlerini kullanır . İhtiyaç duymanız halinde yöntem imzasına daha fazla argüman da ekleyebilirsiniz.

#To_s Stratejisini #to_str yerine geçersiz kılmak , farklı şekilde çalışır

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

Konsol Çıkışı

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

#İnitialize Stratejisini Geçersiz Kılma

Raylarda kullandığım uygulamalara en yakın strateji bu. Yukarıda belirtildiği gibi, bu kullanır demodualize, underscoreve humanize ActiveSupportyöntemler. Ancak bu, önceki stratejide olduğu gibi kolayca kaldırılabilir.

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

Konsol Çıkışı

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 Aracı

Bu, yukarıdaki uygulamanın kurtarılmasını ve mesajlaşmasını gösteren bir demodur. İstisnaları yükselten sınıf, Cloudinary için sahte bir API'dir. Yukarıdaki stratejilerden birini ray konsolunuza boşaltın, ardından bunu yapın.

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

Fikriniz doğrudur, ancak onu adlandırmanız yanlıştır. Olmalı

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

Tamam, verdiğin mesajın raiseanahtar kelime için ikinci bir parametre olduğunu düşündüm .
MarioDS

initializeİki argüman almak için yeniden tanımladınız . newargümanları 'a aktarır initialize.
sawa

Veya parantezleri atlayabilirsiniz.
sawa

O biraz anlıyorum ama benim söz konusu ben bağlantılı konunun afiş bunu böyle yapar: raise(BillRowError.new(:roamingcalls, @index), "Roaming Calls field missing"). Bu yüzden raiseiki parametre ile çağırır : yeni bir BillRowErrornesne ve mesajı. Sözdizimi yüzünden kafam karıştı ... Diğer raise Error, message
derslerde

1
Sorun, kaç argüman ilettiğinizle ilgili değildir raise; bu oldukça esnektir. Sorun şu ki, initializeiki argüman almayı tanımladınız ve sadece bir tane verdiniz . Örneğinize bakın. BillRowError.new(:roamingcalls, @index)iki argüman verilir.
sawa

5

Ben de benzer bir şey yapmak istedim. Bir nesneyi #yeni'ye iletmek ve iletinin, iletilen nesnenin bazı işlemlerine göre ayarlanmasını istedim. Aşağıdaki çalışır.

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

Bildirmezseniz attr_accessor :messageişe yaramayacağını unutmayın. OP'nin sorununu ele alarak, mesajı ek bir argüman olarak da iletebilir ve istediğiniz herhangi bir şeyi saklayabilirsiniz. Önemli kısım, # mesajı geçersiz kılıyor gibi görünüyor.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.