Các lớp lỗi tùy chỉnh của Ruby: kế thừa thuộc tính message


95

Tôi dường như không thể tìm thấy nhiều thông tin về các lớp ngoại lệ tùy chỉnh.

Những gì tôi biết

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

class MyCustomError < StandardError
end

Điều này cho phép bạn nâng cao nó bằng cách sử dụng:

raise MyCustomError, "A message"

và sau đó, nhận được thông báo đó khi giải 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 cho ngoại lệ của mình một số trường tùy chỉnh, nhưng tôi muốn kế thừa messagethuộc tính từ lớp cha. Tôi phát hiện ra đọc về chủ đề này@messagekhông phải là một biến thể hiện của các lớp ngoại lệ, vì vậy tôi lo lắng rằng thừa kế của tôi sẽ không làm việc.

Bất cứ ai có thể cho tôi biết thêm chi tiết về điều này? Làm cách nào để triển khai một lớp lỗi tùy chỉnh với một objectthuộc tính? Điều sau có đúng khô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ẽ hoạt động, và nếu nó có, đây có phải là cách làm chính xác?


3
Đừng rescue Exception => e. Nó rộng hơn mặc định rescue => emở rộng từ StandardErrorvà bắt mọi thứ bao gồm Ctrl + C. Tôi sẽ làm rescue MyCustomError => e.
Ryan Taylor

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

Câu trả lời:


121

raise đã thiết lập thông báo để bạn không phải chuyển nó cho hàm tạo:

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 Exceptionbằng rescue MyCustomError, hãy xem Tại sao `cứu Exception => e` trong Ruby lại là một phong cách tồi? .


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

1
Ở đây chúng tôi đang làm rescue Exception, nhưng tại sao không rescue MyCustomError?
Dfr

FYI, nếu đối số đầu tiên, đối tượng, là một tùy chọn và raise MyCustomError, "a message"không có new, "một thông báo" sẽ không được đặt.
hiroshi

Có cách nào để lấy thông báo được nêu ra trong lớp ngoại lệ tùy chỉnh của chúng tôi không?
CyberMew

@CyberMew ý bạn là gì? Bạn muốn làm gì?
Stefan

10

Với tài liệu cốt lõi của ruby Exception, từ đó tất cả các lỗi khác kế thừa, cho biết về#message

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

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

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

LƯU Ý: Chiến lược thứ hai bên dưới sử dụng các phương thức chuỗi khá rails, chẳng hạn như demodualize, có thể hơi phức tạp và do đó có khả năng không khôn ngoan khi 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.

Ghi đè Chiến lược #to_s không phải #to_str, nó hoạt động theo cách khác

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

Đầu ra bảng điều khiển

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

Ghi đè #initialize Strategy

Đây là chiến lược gần nhất với các triển khai mà tôi đã sử dụng trong đường ray. Như đã đề cập ở trên, nó sử dụng demodualize, underscorehumanize ActiveSupportphương pháp. Nhưng điều này có thể dễ dàng được 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

Đầu ra bảng điều khiển

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

Công cụ Demo

Đây là một bản demo để hiển thị cách cứu và thông điệp của việc thực hiện ở trên. Lớp nâng cao các ngoại lệ là một API giả mạo đối với Cloudinary. Chỉ cần đưa một trong các chiến lược trên vào bảng điều khiển rails của bạn, sau đó là thao tác 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

6

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

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

Được rồi, tôi nghĩ rằng thông báo bạn đưa ra là tham số thứ hai cho raisetừ khóa hoặc một cái gì đó.
MarioDS

Bạn đã xác định lại initializeđể có hai đối số. newchuyển các đối số tới initialize.
sawa

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

Tôi hiểu rằng chút, nhưng các poster của chủ đề tôi liên kết với trong câu hỏi của tôi làm nó như thế này: raise(BillRowError.new(:roamingcalls, @index), "Roaming Calls field missing"). Vì vậy, anh ta gọi raisevới hai tham số: một BillRowErrorđối tượng mới và thông điệp của anh ta. Tôi chỉ nhầm lẫn bởi cú pháp ... Về hướng dẫn khác tôi luôn luôn nhìn thấy nó như thế này:raise Error, message
MarioDS

1
Vấn đề không nằm ở việc bạn chuyển bao nhiêu đối số raise; đó là khá nhiều linh hoạt. Vấn đề là bạn đã xác định initializelấy hai đối số và chỉ đưa ra một đối số. Nhìn vào ví dụ của bạn. BillRowError.new(:roamingcalls, @index)được đưa ra hai đối số.
sawa

4

Tôi muốn làm điều gì đó tương tự. Tôi muốn chuyển một đối tượng cho #new và đặt thông báo dựa trên một số quá trình xử lý đố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 :messagethì nó sẽ không hoạt động. Giải quyết vấn đề của OP, bạn cũng có thể chuyển thông báo làm đối số bổ sung và lưu trữ bất kỳ thứ gì bạn thích. Phần quan trọng dường như đang ghi đè #message.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.