From 1982e5b9a267d20745b410e9f0551e7db91fadd8 Mon Sep 17 00:00:00 2001 From: Adrian Serafin Date: Fri, 20 May 2022 13:25:51 +0200 Subject: [PATCH 1/3] allow event arguments to be taken into account when selecting the event --- lib/workflow.rb | 2 +- lib/workflow/event.rb | 6 +++--- lib/workflow/event_collection.rb | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/workflow.rb b/lib/workflow.rb index 4f2ec46..2babf51 100644 --- a/lib/workflow.rb +++ b/lib/workflow.rb @@ -95,7 +95,7 @@ def halted_because end def process_event!(name, *args, **kwargs) - event = current_state.events.first_applicable(name, self) + event = current_state.events.first_applicable(name, self, args) raise NoTransitionAllowed.new( "There is no event #{name.to_sym} defined for the #{current_state} state") \ if event.nil? diff --git a/lib/workflow/event.rb b/lib/workflow/event.rb index ed5048a..fd31870 100644 --- a/lib/workflow/event.rb +++ b/lib/workflow/event.rb @@ -15,12 +15,12 @@ def initialize(name, transitions_to, condition = nil, meta = {}, &action) end end - def condition_applicable?(object) + def condition_applicable?(object, event_arguments) if condition if condition.is_a?(Symbol) - object.send(condition) + object.send(condition, *event_arguments) else - condition.call(object) + condition.call(object, *event_arguments) end else true diff --git a/lib/workflow/event_collection.rb b/lib/workflow/event_collection.rb index 5a20cfc..4860bf6 100644 --- a/lib/workflow/event_collection.rb +++ b/lib/workflow/event_collection.rb @@ -26,9 +26,9 @@ def include?(name_or_obj) end end - def first_applicable(name, object_context) + def first_applicable(name, object_context, event_arguments) (self[name] || []).detect do |event| - event.condition_applicable?(object_context) && event + event.condition_applicable?(object_context, event_arguments) && event end end From 3481e3638c24870b983f6511b707c7d18f9a780d Mon Sep 17 00:00:00 2001 From: Vladimir Dobriakov Date: Thu, 16 May 2024 14:27:51 +0200 Subject: [PATCH 2/3] gh-227 prepare tests and documentation --- README.adoc | 8 +++- lib/workflow/version.rb | 2 +- test/conditionals_test.rb | 99 +++++++++++++++++++++++++++++++++++++++ test/main_test.rb | 36 -------------- 4 files changed, 107 insertions(+), 38 deletions(-) create mode 100644 test/conditionals_test.rb diff --git a/README.adoc b/README.adoc index 6a3acbb..b1521bc 100644 --- a/README.adoc +++ b/README.adoc @@ -407,6 +407,8 @@ When calling a `device.can_?` check, or attempting a `device. * If an `:if` check is present, proceed if it evaluates to true, or drop to the next event. * If you've run out of events to check (eg. `battery_level == 0`), then the transition isn't possible. +You can also pass additional arguments, which can be evaluated by :if methods or procs. See examples in +[conditionals_test.rb](test/conditionals_test.rb#L45) ### Advanced transition hooks @@ -513,6 +515,10 @@ end Changelog --------- +=== New in the version 3.1.0 + +* gh-227 allow event arguments to be taken into account when selecting the event + === New in the version 3.0.0 * gh-228 Support for Ruby 3 keyword args, provided by @agirling @@ -552,7 +558,7 @@ integration with https://github.com/activeadmin/activeadmin[ActiveAdmin]. Author: Vladimir Dobriakov, -Copyright (c) 2010-2022 Vladimir Dobriakov and Contributors +Copyright (c) 2010-2024 Vladimir Dobriakov and Contributors Copyright (c) 2008-2009 Vodafone diff --git a/lib/workflow/version.rb b/lib/workflow/version.rb index 7ff4a5e..55363da 100644 --- a/lib/workflow/version.rb +++ b/lib/workflow/version.rb @@ -1,3 +1,3 @@ module Workflow - VERSION = "3.0.0" + VERSION = "3.1.0" end diff --git a/test/conditionals_test.rb b/test/conditionals_test.rb new file mode 100644 index 0000000..17cb14e --- /dev/null +++ b/test/conditionals_test.rb @@ -0,0 +1,99 @@ +require File.join(File.dirname(__FILE__), 'test_helper') + +$VERBOSE = false +require 'workflow' +require 'mocha/minitest' + +class ConditionalsTest < Minitest::Test + + test 'can_? with conditions' do + c = Class.new do + include Workflow + workflow do + state :off do + event :turn_on, :transitions_to => :on, :if => :sufficient_battery_level? + event :turn_on, :transitions_to => :low_battery, :if => proc { |obj| obj.battery > 0 } + end + state :on + state :low_battery + end + attr_reader :battery + def initialize(battery) + @battery = battery + end + + def sufficient_battery_level? + @battery > 10 + end + end + + device = c.new 0 + assert_equal false, device.can_turn_on? + + device = c.new 5 + assert device.can_turn_on? + device.turn_on! + assert device.low_battery? + assert_equal false, device.on? + + device = c.new 50 + assert device.can_turn_on? + device.turn_on! + assert device.on? + end + + test 'gh-227 allow event arguments in conditions - test with proc' do + c = Class.new do + include Workflow + # define more advanced workflow, where event methods allow arguments + workflow do + state :off do + # turn_on accepts additional argument `power_adapter` + event :turn_on, :transitions_to => :on, :if => :sufficient_battery_level? + event :turn_on, :transitions_to => :low_battery # otherwise + end + state :on + state :low_battery + end + attr_reader :battery + def initialize(battery) + @battery = battery + end + + def sufficient_battery_level?(power_adapter=false) + power_adapter || @battery > 10 + end + end + + # test for conditions in a proc + device = c.new 5 + device.turn_on!(true) # case with event arguments to be taken into account + assert device.on? + end + + test 'gh-227 allow event arguments in conditions - test with a method' do + c = Class.new do + include Workflow + # define more advanced workflow, where event methods allow arguments + workflow do + state :off do + # turn_on accepts additional argument `power_adapter` + event :turn_on, :transitions_to => :on, :if => proc { |obj, power_adapter| power_adapter || obj.battery > 10 } + event :turn_on, :transitions_to => :low_battery # otherwise + end + state :on + state :low_battery + end + attr_reader :battery + def initialize(battery) + @battery = battery + end + end + + device = c.new 5 + device.turn_on!(true) # case with event arguments to be taken into account + assert device.on? + end + +end + diff --git a/test/main_test.rb b/test/main_test.rb index 6bc0f17..26da675 100644 --- a/test/main_test.rb +++ b/test/main_test.rb @@ -328,42 +328,6 @@ def reject(reason) assert_equal false, human.can_go_to_college? end - test 'can_? with conditions' do - c = Class.new do - include Workflow - workflow do - state :off do - event :turn_on, :transitions_to => :on, :if => :sufficient_battery_level? - event :turn_on, :transitions_to => :low_battery, :if => proc { |obj| obj.battery > 0 } - end - state :on - state :low_battery - end - attr_reader :battery - def initialize(battery) - @battery = battery - end - - def sufficient_battery_level? - @battery > 10 - end - end - - device = c.new 0 - assert_equal false, device.can_turn_on? - - device = c.new 5 - assert device.can_turn_on? - device.turn_on! - assert device.low_battery? - assert_equal false, device.on? - - device = c.new 50 - assert device.can_turn_on? - device.turn_on! - assert device.on? - end - test 'workflow graph generation' do require 'workflow/draw' Dir.chdir('/tmp') do From d53bd7e31d766b09bca817865288c1a08de21d6a Mon Sep 17 00:00:00 2001 From: Vladimir Dobriakov Date: Thu, 16 May 2024 15:05:26 +0200 Subject: [PATCH 3/3] gh-227 fix first_applicable call - enable *args --- lib/workflow.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/workflow.rb b/lib/workflow.rb index 2babf51..31e639d 100644 --- a/lib/workflow.rb +++ b/lib/workflow.rb @@ -65,8 +65,8 @@ def assign_workflow(specification_object) process_event!(event_name, *args, **kwargs) end - define_method "can_#{event_name}?" do - return !!current_state.events.first_applicable(event_name, self) + define_method "can_#{event_name}?".to_sym do |*args, **kwargs| + return !!current_state.events.first_applicable(event_name, self, args) end end end