DEV Community

Ivan Shamatov
Ivan Shamatov

Posted on

Каскадный rescue в ruby и в ActiveSupport::Rescuable

Возьмем пример

class E < StandardError; end

begin
  raise E
rescue E
  puts "here we rescue E"
rescue 
  puts "here we rescue StandardError"
end
Enter fullscreen mode Exit fullscreen mode

Если вы используете вот такой вот каскадный rescue, то вы наверняка знаете, что сработает только обработчик первой ошибки, которая подпадает под условие.

=> here we rescue E
nil
Enter fullscreen mode Exit fullscreen mode

Если мы повторно сделаем raise в обработке ошибки, то этот raise уже отловлен не будет и приведет к эксепшену

class E < StandardError; end

begin
  raise E
rescue E
  puts "here we rescue E"
  raise
rescue 
  puts "here we rescue StandardError"
end
Enter fullscreen mode Exit fullscreen mode
=> here we rescue E
E: E
from (pry):27:in '__pry__'
Enter fullscreen mode Exit fullscreen mode

rescue_from из ActiveSupport::Rescuable

Если мы захотим перенести такую логику в контроллер, или у нас есть какой-то кастомный бизнес-экшен, который использует ActiveSupport::Rescuable, то нужно помнить, что работать обработчик будет по-другому.

Пусть у нас будет вот такой базовый бизнес-экшн.

class Action
  include ActiveSupport::Rescuable

  def call
    call_impl
  rescue => ex
    rescue_with_handler(ex) or raise
  end
end
Enter fullscreen mode Exit fullscreen mode

И мы наследуемся от него, определяя call_impl и добавляя обработчики ошибок,

class A < Action
  class E < StandardError; end

  rescue_from E do |ex|
    puts "here we rescue E"
    raise
  end

  rescue_from StandardError do |ex|
    puts "here we rescue StandardError"
  end

  def call_impl
    raise E
  end
end
Enter fullscreen mode Exit fullscreen mode

то получим вот такой результат.

> A.new.call
here we rescue StandardError
=> #<A::E: A::E>
Enter fullscreen mode Exit fullscreen mode

Дело в том, что rescue_from работает в FILO режиме (First In Last Out), то есть последний обработчик, который мы определили, будет первым попробованным.

Поменяем местами обработчики местами

class A < Action
  class E < StandardError; end

  rescue_from StandardError do |ex|
    puts "here we rescue StandardError"
  end

  rescue_from E do |ex|
    puts "here we rescue E"
    raise
  end

  def call_impl
    raise E
  end
end
Enter fullscreen mode Exit fullscreen mode

и вот результат

irb(main):044:0> A.new.call
here we rescue E
Traceback (most recent call last):
...
1: from (irb):41:in `call_impl'
A::E (A::E)
Enter fullscreen mode Exit fullscreen mode

Таким образом мы видим, что сработал именно обработчик ошибки A::E, а не StandardError, а так же, что raise внутри обработчика ошибки не привел к еще повторному rescue из StandardError.

Top comments (0)