80

Tôi dường như không tìm thấy nhiều thông tin về các lớp ngoại lệ tùy chỉnh.Các lớp lỗi tùy chỉnh Ruby: thừa kế thuộc tính thư

Những gì tôi biết

Bạn có thể khai báo các lớp lỗi tùy chỉnh của bạn và để cho nó kế thừa từ StandardError, vì vậy nó có thể được rescue d:

class MyCustomError < StandardError 
end 

Điều này cho phép bạn tăng nó bằng cách sử:

raise MyCustomError, "A message" 

và sau đó, nhận được thông báo đó khi cứu

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

Những gì tôi không biết

tôi muốn cung cấp ngoại lệ của tôi một số lĩnh vực tùy chỉnh, nhưng tôi muốn kế thừa message thuộc tính từ lớp cha mẹ. Tôi phát hiện ra đọc on this topic rằng @message không phải là một biến mẫu của lớp ngoại lệ, vì vậy tôi lo lắng rằng kế thừa của tôi sẽ không hoạt động.

Có ai cho tôi biết thêm chi tiết về điều này không? Làm cách nào để triển khai lớp lỗi tùy chỉnh với thuộc tính object? Sản phẩm sau đây đúng:

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

Và sau đó:

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

để có được:

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

nó sẽ làm việc, và nếu có, đây là cách chính xác làm việc?

+3

Không 'giải cứu Ngoại lệ => e'. Nó rộng hơn so với 'rescue => e' mặc định mở rộng từ' StandardError' và bắt tất cả mọi thứ bao gồm Ctrl + C. Tôi sẽ làm 'giải cứu MyCustomError => e'. –

+1

@RyanTaylor Tôi đã chỉnh sửa câu hỏi của mình để có cách tiếp cận phù hợp hơn. – MarioDS

Trả lời

106

raise đã đặt thông điệp, do đó bạn không cần phải vượt qua nó để các nhà xây dựng:

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 

tôi đã thay thế rescue Exception với rescue MyCustomError, xem Why is it a bad style to `rescue Exception => e` in Ruby?.

+0

Tôi sẽ chấp nhận câu trả lời của bạn vì bạn đã cho tôi xem toàn bộ cú pháp. Cảm ơn! – MarioDS

+1

Ở đây chúng tôi thực hiện 'rescue Exception', nhưng tại sao không' giải cứu MyCustomError'? – Dfr

+0

@Dfr Tôi đã cập nhật mã – Stefan

6

Ý tưởng của bạn là đúng, nhưng cách bạn gọi nó là sai. Nó phải là

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

Được rồi, tôi nghĩ rằng thông điệp bạn đưa ra là tham số thứ hai cho từ khóa 'nâng cao 'hoặc cái gì đó. – MarioDS

+0

Bạn đã xác định lại 'initialize' để lấy hai đối số. 'new' chuyển các đối số tới' initialize'. – sawa

+0

Hoặc, bạn có thể bỏ qua các dấu ngoặc đơn. – sawa

2

Tôi muốn làm điều tương tự. Tôi muốn truyền một đối tượng tới #new và có thông điệp được thiết lập dựa trên một số xử lý của đối tượng đã truyền. Các công trình sau đây.

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 

Lưu ý rằng nếu bạn không khai báo attr_accessor :message thì nó sẽ không hoạt động. Giải quyết vấn đề của OP, bạn cũng có thể truyền thông điệp như một đối số bổ sung và lưu trữ bất cứ thứ gì bạn thích. Phần quan trọng xuất hiện để được ghi đè #message.

6

Với những gì tài liệu lõi ruby ​​của Exception, từ đó tất cả các lỗi khác kế thừa, nói về #message

Trả về kết quả của cách gọi exception.to_s. Thông thường, điều này trả lại thông báo hoặc tên của ngoại lệ. Bằng cách cung cấp phương thức to_str, ngoại lệ đồng ý được sử dụng trong đó các chuỗi được mong đợi.

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

tôi sẽ lựa chọn xác định lại to_s/to_str hoặc initializer. Đây là một ví dụ mà chúng ta muốn biết, theo một cách có thể đọc được chủ yếu là con người, khi một dịch vụ bên ngoài không làm được điều gì đó.

LƯU Ý: Chiến lược thứ hai bên dưới sử dụng các phương pháp chuỗi khá đẹp, chẳng hạn như demodualize, có thể hơi phức tạp và do đó có khả năng không thực hiện trong một ngoại lệ. Bạn cũng có thể thêm nhiều đối số vào chữ ký phương thức, nếu bạn cần.

Overriding #to_s Chiến lượckhông #to_str, nó hoạt động khác nhau

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 

điều khiển 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 Chiến lược

Đây là chiến lược gần với triển khai nhất mà tôi đã sử dụng trong đường ray. Như đã nói ở trên, nó sử dụng các phương pháp demodualize, underscorehumanizeActiveSupport. Nhưng điều này có thể dễ dàng loại bỏ, như trong chiến lược trước.

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 

điều khiển 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

Đây là một bản demo để hiển thị cứu và nhắn tin tình hình thực hiện ở trên. Lớp nâng cao các ngoại lệ là một API giả để Cloudinary. Chỉ cần đổ một trong các chiến lược trên vào bảng điều khiển đường ray của bạn, tiếp theo là điều này.

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