diff --git a/README.md b/README.md index 2b5ee92f..0cd36a51 100644 --- a/README.md +++ b/README.md @@ -1053,6 +1053,7 @@ interaction's lifecycle. ``` rb class Increment < ActiveInteraction::Base + set_callback :run, :before, -> { puts 'before run' } set_callback :filter, :before, -> { puts 'before filter' } integer :x @@ -1075,6 +1076,7 @@ class Increment < ActiveInteraction::Base end Increment.run!(x: 1) +# before run # before filter # after validate # >>> @@ -1083,7 +1085,7 @@ Increment.run!(x: 1) # => 2 ``` -In order, the available callbacks are `filter`, `validate`, and `execute`. +In order, the available callbacks are `run`, `filter`, `validate`, and `execute`. You can set `before`, `after`, or `around` on any of them. ### Composition diff --git a/lib/active_interaction/concerns/runnable.rb b/lib/active_interaction/concerns/runnable.rb index 98ae0fbc..5a775f69 100644 --- a/lib/active_interaction/concerns/runnable.rb +++ b/lib/active_interaction/concerns/runnable.rb @@ -15,6 +15,7 @@ module Runnable included do define_callbacks :execute + define_callbacks :run end # @return [Errors] @@ -69,13 +70,15 @@ def compose(other, *args) # @return (see #result=) # @return [nil] def run - return self.result = nil unless valid? - - self.result = run_callbacks(:execute) do - execute - rescue Interrupt => e - errors.backtrace = e.errors.backtrace || e.backtrace - errors.merge!(e.errors) + run_callbacks(:run) do + return self.result = nil unless valid? + + self.result = run_callbacks(:execute) do + execute + rescue Interrupt => e + errors.backtrace = e.errors.backtrace || e.backtrace + errors.merge!(e.errors) + end end end diff --git a/spec/active_interaction/base_spec.rb b/spec/active_interaction/base_spec.rb index ab5b2e1a..35c8f563 100644 --- a/spec/active_interaction/base_spec.rb +++ b/spec/active_interaction/base_spec.rb @@ -469,7 +469,7 @@ def filters(klass) context 'callbacks' do let(:described_class) { Class.new(TestInteraction) } - %w[filter validate execute].each do |name| + %w[run filter validate execute].each do |name| %w[before after around].map(&:to_sym).each do |type| it "runs the #{type} #{name} callback" do called = false @@ -480,6 +480,33 @@ def filters(klass) end end + it 'runs callbacks in the correct order' do + execution_log = [] + described_class.set_callback(:filter, :before) { execution_log << :before_filter } + described_class.set_callback(:filter, :after) { execution_log << :after_filter } + described_class.set_callback(:validate, :before) { execution_log << :before_validate } + described_class.set_callback(:validate, :after) { execution_log << :after_validate } + described_class.set_callback(:execute, :before) { execution_log << :before_execute } + described_class.set_callback(:execute, :after) { execution_log << :after_execute } + described_class.set_callback(:run, :before) { execution_log << :before_run } + described_class.set_callback(:run, :after) { execution_log << :after_run } + + outcome + + expect(execution_log).to eq( + %i[ + before_run + before_filter + after_filter + before_validate + after_validate + before_execute + after_execute + after_run + ] + ) + end + context 'with errors during filter' do before do described_class.set_callback(:filter, :before) do diff --git a/spec/active_interaction/concerns/runnable_spec.rb b/spec/active_interaction/concerns/runnable_spec.rb index 2fcb619c..83e13871 100644 --- a/spec/active_interaction/concerns/runnable_spec.rb +++ b/spec/active_interaction/concerns/runnable_spec.rb @@ -66,6 +66,7 @@ def execute include_examples 'set_callback examples', :validate include_examples 'set_callback examples', :execute + include_examples 'set_callback examples', :run context 'execute with composed interaction' do class WithFailingCompose # rubocop:disable Lint/ConstantDefinitionInBlock