diff --git a/lib/active_model/command/composite.rb b/lib/active_model/command/composite.rb index 090bb57..8e8f15e 100644 --- a/lib/active_model/command/composite.rb +++ b/lib/active_model/command/composite.rb @@ -1,22 +1,18 @@ +# frozen_string_literal: true + +require_relative "rescuable" + module ActiveModel module Command module Composite module InstanceMethods - module PrependMethods - def execute - super - rescue HaltedExecution => error - handle_halted_execution(error) - end - end - module DeprecatedPrependMethods Deprecation = ActiveSupport::Deprecation.new('1.0', 'ActiveModel::Command') def call super - rescue HaltedExecution => error - handle_halted_execution(error) + rescue SubcommandFailure => error + handle_failed_subcommand(error) self end @@ -32,22 +28,18 @@ def self.prepended(receiver) end end - def self.included(receiver) - receiver.send :prepend, PrependMethods - end - private - def handle_halted_execution(error) - @errors.merge!(error.command.errors) - @result = nil - end - def call_subcommand(command) raise ArgumentError, "not a command" unless command.is_a?(Command) command.call unless command.called? return command.result if command.success? - raise HaltedExecution.new(command) + raise SubcommandFailure.new(command) + end + + def handle_failed_subcommand(error) + @errors.merge!(error.command.errors) + @result = nil end end @@ -59,7 +51,10 @@ def self.prepended(receiver) def self.included(receiver) receiver.send :include, ActiveModel::Command + receiver.send :include, ActiveModel::Command::Rescuable receiver.send :include, InstanceMethods + + receiver.rescue_from SubcommandFailure, with: :handle_failed_subcommand end end end diff --git a/lib/active_model/command/errors.rb b/lib/active_model/command/errors.rb index 07f2e40..de02f23 100644 --- a/lib/active_model/command/errors.rb +++ b/lib/active_model/command/errors.rb @@ -4,12 +4,20 @@ module ActiveModel module Command AlreadyExecuted = Class.new(RuntimeError) UnsupportedErrors = Class.new(RuntimeError) - class HaltedExecution < RuntimeError + class SubcommandFailure < RuntimeError attr_reader :command def initialize(command) @command = command end end + HaltedExecution = Class.new(SubcommandFailure) do + Deprecation = ActiveSupport::Deprecation.new('1.0', 'ActiveModel::Command') + + def initialize(_) + Deprecation.deprecation_warning("use SubcommandFailure instead") + super + end + end end end diff --git a/lib/active_model/command/rescuable.rb b/lib/active_model/command/rescuable.rb new file mode 100644 index 0000000..3b861bb --- /dev/null +++ b/lib/active_model/command/rescuable.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "active_support/rescuable" + +module ActiveModel + module Command + # Includes ActiveSupport::Rescuable into the command and wraps + # command execution with exception handling. + module Rescuable + module InstanceMethods + module PrependMethods + def execute + super + rescue => exception + return if rescue_with_handler(exception) + raise + end + end + + def self.included(receiver) + receiver.send :prepend, PrependMethods + end + end + + def self.included(receiver) + receiver.send :include, ActiveSupport::Rescuable + receiver.send :include, InstanceMethods + end + end + end +end diff --git a/lib/active_model/command/version.rb b/lib/active_model/command/version.rb index 2180b09..dcf4d70 100644 --- a/lib/active_model/command/version.rb +++ b/lib/active_model/command/version.rb @@ -1,5 +1,5 @@ module ActiveModel module Command - VERSION = "0.2.0" + VERSION = "0.3.0" end end diff --git a/spec/active_model/command/rescuable_spec.rb b/spec/active_model/command/rescuable_spec.rb new file mode 100644 index 0000000..9f5ceaf --- /dev/null +++ b/spec/active_model/command/rescuable_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +RSpec.describe ActiveModel::Command::Rescuable do + let(:command) { command_class.new } + let(:command_class) { + Class.new do + include ActiveModel::Command + include ActiveModel::Command::Rescuable + + def execute + raise RuntimeError + end + end + } + + describe "#call" do + subject(:call) { command.call } + + context "when an exception is raised that the command rescues" do + context "that the command rescues" do + before do + command_class.rescue_from(RuntimeError, with: -> (_) {}) + end + + it "handles the exception" do + expect { call }.to_not raise_error + end + end + + context "that the command does not rescue" do + it "bubbles up the exception" do + expect { call }.to raise_error(RuntimeError) + end + end + end + end +end