From 7769638d5ceb5e3b9986b9d1d722652b77ab6bdb Mon Sep 17 00:00:00 2001 From: tlloydthwaites <91047784+tlloydthwaites@users.noreply.github.com> Date: Thu, 7 Mar 2024 14:47:43 +1100 Subject: [PATCH 1/4] Allow symbol on_entry/on_exit --- lib/workflow.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/workflow.rb b/lib/workflow.rb index 4f2ec46e0..08def9d3c 100644 --- a/lib/workflow.rb +++ b/lib/workflow.rb @@ -210,23 +210,25 @@ def run_action_callback(action_name, *args, **kwargs) action = action_name.to_sym self.send(action, *args, **kwargs) if has_callback?(action) end - + def run_on_entry(state, prior_state, triggering_event, *args, **kwargs) - if state.on_entry + if state.on_entry&.is_a? Proc instance_exec(prior_state.name, triggering_event, *args, **kwargs, &state.on_entry) else - hook_name = "on_#{state}_entry" - self.send hook_name, prior_state, triggering_event, *args, **kwargs if has_callback?(hook_name) + [state.on_entry&.to_sym, "on_#{state}_entry"].compact.each do |hook_name| + self.send hook_name, prior_state, triggering_event, *args, **kwargs if has_callback?(hook_name) + end end end def run_on_exit(state, new_state, triggering_event, *args, **kwargs) if state - if state.on_exit + if state.on_exit&.is_a? Proc instance_exec(new_state.name, triggering_event, *args, **kwargs, &state.on_exit) else - hook_name = "on_#{state}_exit" - self.send hook_name, new_state, triggering_event, *args, **kwargs if has_callback?(hook_name) + [state.on_exit&.to_sym, "on_#{state}_exit"].compact.each do |hook_name| + self.send hook_name, new_state, triggering_event, *args, **kwargs if has_callback?(hook_name) + end end end end From fbb4b87f2c1bd502e6961cea92419bd7af90e5c1 Mon Sep 17 00:00:00 2001 From: tlloydthwaites <91047784+tlloydthwaites@users.noreply.github.com> Date: Thu, 7 Mar 2024 14:49:09 +1100 Subject: [PATCH 2/4] Allow on_entry/on_exit symbol callbacks --- lib/workflow/specification.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/workflow/specification.rb b/lib/workflow/specification.rb index b98033f5c..454cdcd05 100644 --- a/lib/workflow/specification.rb +++ b/lib/workflow/specification.rb @@ -40,12 +40,12 @@ def event(name, args = {}, &action) ) end - def on_entry(&proc) - @scoped_state.on_entry = proc + def on_entry(sym=nil, &proc) + @scoped_state.on_entry = sym || proc end - def on_exit(&proc) - @scoped_state.on_exit = proc + def on_exit(sym=nil, &proc) + @scoped_state.on_exit = sym || proc end def after_transition(&proc) From 2e9cc45a5e614274801cd928fb02ec5ae8328fae Mon Sep 17 00:00:00 2001 From: tlloydthwaites <91047784+tlloydthwaites@users.noreply.github.com> Date: Thu, 7 Mar 2024 14:50:32 +1100 Subject: [PATCH 3/4] Allow transition/error callbacks by convention --- lib/workflow.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/workflow.rb b/lib/workflow.rb index 08def9d3c..040b14e65 100644 --- a/lib/workflow.rb +++ b/lib/workflow.rb @@ -170,11 +170,13 @@ def check_transition(event) end def run_before_transition(from, to, event, *args, **kwargs) + run_action_callback(:before_transition, from, to, event, *args, **kwargs) instance_exec(from.name, to.name, event, *args, **kwargs, &spec.before_transition_proc) if spec.before_transition_proc end def run_on_error(error, from, to, event, *args, **kwargs) + run_action_callback(:on_error, error, from, to, event, *args, **kwargs) if spec.on_error_proc instance_exec(error, from.name, to.name, event, *args, **kwargs, &spec.on_error_proc) halt(error.message) @@ -184,10 +186,12 @@ def run_on_error(error, from, to, event, *args, **kwargs) end def run_on_transition(from, to, event, *args, **kwargs) + run_action_callback(:on_transition, from, to, event, *args, **kwargs) instance_exec(from.name, to.name, event, *args, **kwargs, &spec.on_transition_proc) if spec.on_transition_proc end def run_after_transition(from, to, event, *args, **kwargs) + run_action_callback(:after_transition, from, to, event, *args, **kwargs) instance_exec(from.name, to.name, event, *args, **kwargs, &spec.after_transition_proc) if spec.after_transition_proc end From e56f8dc9890b86881f40da879af4ddf33137d227 Mon Sep 17 00:00:00 2001 From: tlloydthwaites <91047784+tlloydthwaites@users.noreply.github.com> Date: Thu, 7 Mar 2024 15:02:06 +1100 Subject: [PATCH 4/4] Create symbol_callbacks_test.rb --- test/symbol_callbacks_test.rb | 158 ++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 test/symbol_callbacks_test.rb diff --git a/test/symbol_callbacks_test.rb b/test/symbol_callbacks_test.rb new file mode 100644 index 000000000..33aa28395 --- /dev/null +++ b/test/symbol_callbacks_test.rb @@ -0,0 +1,158 @@ +require File.join(File.dirname(__FILE__), 'test_helper') +require 'workflow' + +class SymbolCallbacksTest < Minitest::Test + + test "symbol callbacks" do + + c = Class.new + c.class_eval do + include Workflow + + attr_reader :events + + def record(event) + @events ||= [] + @events << event + end + + def ran?(event) + @events.include? event + end + + def clear_events + @events = [] + end + + def event_1(test) + # puts "running event_1 with arg '#{test}'" + record __method__ + end + + def before_transition(from, to, triggering_event, *args, **kwargs) + # puts "before_transition #{from} -> #{to}, #{triggering_event}, #{args}, #{kwargs}" + record __method__ + end + + def on_transition(from, to, triggering_event, *args, **kwargs) + # puts "on_transition #{from} -> #{to}, #{triggering_event}, #{args}, #{kwargs}" + record __method__ + end + + def after_transition(from, to, triggering_event, *args, **kwargs) + # puts "after_transition #{from} -> #{to}, #{triggering_event}, #{args}, #{kwargs}" + record __method__ + end + + def event_1_condition? + # puts "event_1_condition? -> true" + record __method__ + true + end + + def on_state_1_exit(new_state, event, *args) + # puts "on_state_1_exit(#{new_state}, #{event}, #{args})" + record __method__ + end + + def entering_state_2(prior_state, triggering_event, *args, **kwargs) + # puts "entering_state_2 #{prior_state}, #{triggering_event}, #{args}, #{kwargs}" + record __method__ + end + + def entering_state_3(prior_state, triggering_event, *args, **kwargs) + # puts "entering_state_3 #{prior_state}, #{triggering_event}, #{args}, #{kwargs}" + record __method__ + end + + def on_state_2_entry(new_state, event, *args) + # puts "on_state_2_entry(#{new_state}, #{event}, #{args})" + record __method__ + end + + end + + ran_normal = [] + hash = { + state_1: { + events: { + event_1: { + transition_to: :state_2, + if: :event_1_condition?, + meta: { e1: 1 } + }, + }, + meta: { a: 1 } + }, + state_2: { + events: { + event_2: { + transition_to: :state_3, + } + }, + on_entry: :entering_state_2, + meta: { a: 2 } + }, + state_3: { + on_entry: "entering_state_3", + } + } + + spec = Workflow::Specification.new do + + hash.each_pair do |state_name, state_def| + + state state_name, state_def + + on_entry state_def[:on_entry] if state_def[:on_entry].present? + on_exit { ran_normal << :on_exit } + + state_def[:events]&.each_pair do |event_name, event_def| + event event_name, event_def + end + + end + + before_transition do |from, to, triggering_event, *args, **kwargs| + ran_normal << :before_transition + end + + on_transition do |from, to, triggering_event, *args, **kwargs| + ran_normal << :on_transition + end + + after_transition do |from, to, triggering_event, *args, **kwargs| + ran_normal << :after_transition + end + + end + + c.send :assign_workflow, spec + + o = c.new + + assert o.state_1?, "Should be state_1" + refute o.state_2?, "Should not be state_2" + + o.event_1! "hello" + [:event_1_condition?, :event_1, :entering_state_2, :before_transition, :on_transition, :after_transition].each do |event| + assert o.ran?(event), "Should have run event #{event}" + end + + refute o.state_1?, "Should not be state_1" + assert o.state_2?, "Should be state_2" + + o.clear_events + o.event_2! + refute o.ran?(:event_1), "Should not have run event_1" + [:entering_state_3, :before_transition, :on_transition, :after_transition].each do |event| + assert o.ran?(event), "Should have run event #{event}" + end + + assert ran_normal.include?(:before_transition), "Should have run before_transition proc" + assert ran_normal.include?(:on_transition), "Should have run on_transition proc" + assert ran_normal.include?(:after_transition), "Should have run after_transition proc" + assert ran_normal.include?(:on_exit), "Should have run on_exit proc" + + end +end