diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 92aef51..6d2e40e 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -14,10 +14,10 @@ jobs:
strategy:
fail-fast: false
matrix:
- ruby: [2.4, 2.5, 2.6, 2.7, 3.0, 3.1]
+ ruby: [3.1, 3.3]
steps:
- name: Checkout code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..b695504
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,19 @@
+
+# Changelog
+
+## New in the version 3.1.0
+
+* [#227](https://github.com/geekq/workflow/pull/227) Allow event arguments to be taken into account when selecting the event
+* [#232](https://github.com/geekq/workflow/pull/232) Add ability to include partial workflow definitions for composability
+* [#241](https://github.com/geekq/workflow/pull/241) Example for defining workflow dynamically from JSON
+
+## New in the version 3.0.0
+
+* [#228](https://github.com/geekq/workflow/pull/228) Support for Ruby 3 keyword args, provided by @agirling
+* retire Ruby 2.6 since it has reached end of live; please use workflow 2.x, if you still depend on that Ruby version
+* [#229](https://github.com/geekq/workflow/pull/229) Switch from travis CI to GihHub actions for continuous integration
+
+## New in the versions 2.x
+
+* extract persistence adapters, Rails/ActiveRecord integration is now a separate gem
+ workflow-activerecord
diff --git a/README.adoc b/README.adoc
index e8ef40f..cfd7d54 100644
--- a/README.adoc
+++ b/README.adoc
@@ -16,15 +16,6 @@ at http://rubygems.org/gems/workflow : select a version (optional,
default is latest release), click "Documentation" link. When reading on
github.com, the README refers to the upcoming release.
-**Note: Workflow 2.0 is a major refactoring of the library.
-For different options/troubleshooting using it with your Rails application see
-link:#activerecord[State persistence with ActiveRecord].**
-
-Note for contributors: it looks like github closed all the pull requests after
-I had changed the default branch on 2019-01-12. Please check the new refactored
-workflow 2.0, complementing workflow-activerecord and recreate your pull
-request if needed.
-
toc::[]
What is workflow?
@@ -111,7 +102,7 @@ article.current_state.between? :awaiting_review, :rejected # => true
```
Now we can call the submit event, which transitions to the
-:awaiting_review state:
+`:awaiting_review` state:
```rb
article.submit!
@@ -140,7 +131,7 @@ gem install workflow
install the `activesupport` and `ruby-graphviz` gems.
Versions up to and including 1.0.0 are also available as a single file download -
-[lib/workflow.rb file](https://github.com/geekq/workflow/blob/v1.0.0/lib/workflow.rb).
+link:https://github.com/geekq/workflow/blob/v1.0.0/lib/workflow.rb[lib/workflow.rb file].
=== Examples
@@ -354,43 +345,6 @@ favorite database.
Advanced usage
--------------
-### Accessing your workflow specification
-
-You can easily reflect on workflow specification programmatically - for
-the whole class or for the current object. Examples:
-
-```rb
-article2.current_state.events # lists possible events from here
-article2.current_state.events[:reject].transitions_to # => :rejected
-
-Article.workflow_spec.states.keys
-#=> [:rejected, :awaiting_review, :being_reviewed, :accepted, :new]
-
-Article.workflow_spec.state_names
-#=> [:rejected, :awaiting_review, :being_reviewed, :accepted, :new]
-
-# list all events for all states
-Article.workflow_spec.states.values.collect &:events
-```
-
-You can also store and later retrieve additional meta data for every
-state and every event:
-
-```rb
-class MyProcess
- include Workflow
- workflow do
- state :main, :meta => {:importance => 8}
- state :supplemental, :meta => {:importance => 1}
- end
-end
-puts MyProcess.workflow_spec.states[:supplemental].meta[:importance] # => 1
-```
-
-The workflow library itself uses this feature to tweak the graphical
-representation of the workflow. See below.
-
-
### Conditional event transitions
Conditions can be a "method name symbol" with a corresponding instance method, a `proc` or `lambda` which are added to events, like so:
@@ -416,6 +370,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
+link:test/conditionals_test.rb#L45[conditionals_test.rb]
### Advanced transition hooks
@@ -496,50 +452,76 @@ The whole event sequence is as follows:
* event specific action
* on_transition (if action did not halt)
* on_exit
- * PERSIST WORKFLOW STATE, i.e. transition
+ * PERSIST WORKFLOW STATE (i.e. transition) or on_error
* on_entry
* after_transition
-Documenting with diagrams
--------------------------
+### Accessing your workflow specification
-You can generate a graphical representation of the workflow for
-a particular class for documentation purposes.
-Use `Workflow::create_workflow_diagram(class)` in your rake task like:
+You can easily reflect on workflow specification programmatically - for
+the whole class or for the current object. Examples:
```rb
-namespace :doc do
- desc "Generate a workflow graph for a model passed e.g. as 'MODEL=Order'."
- task :workflow => :environment do
- require 'workflow/draw'
- Workflow::Draw::workflow_diagram(ENV['MODEL'].constantize)
+article2.current_state.events # lists possible events from here
+article2.current_state.events[:reject].transitions_to # => :rejected
+
+Article.workflow_spec.states.keys
+#=> [:rejected, :awaiting_review, :being_reviewed, :accepted, :new]
+
+Article.workflow_spec.state_names
+#=> [:rejected, :awaiting_review, :being_reviewed, :accepted, :new]
+
+# list all events for all states
+Article.workflow_spec.states.values.collect &:events
+```
+
+You can also store and later retrieve additional meta data for every
+state and every event:
+
+```rb
+class MyProcess
+ include Workflow
+ workflow do
+ state :main, :meta => {:importance => 8}
+ state :supplemental, :meta => {:importance => 1}
end
end
+puts MyProcess.workflow_spec.states[:supplemental].meta[:importance] # => 1
```
+The workflow library itself uses this feature to tweak the graphical
+representation of the workflow. See below.
-Changelog
----------
-### New in the version 2.0.2
+### Defining workflow dynamically from JSON
-* finalize extraction of persistence adapters, remove remodel adapter
+For an advance example please see
+link:https://github.com/geekq/workflow/blob/develop/test/workflow_from_json_test.rb[workflow_from_json_test.rb].
-### New in the version 2.0.1
-* retire Ruby 2.3 since it has reached end of live
-* fix #213 ruby-graphiz warnings
+### Compose workflow definition with `include`
-### New in the version 2.0.0
+In case you have very extensive workflow definition or would like to reuse
+workflow definition for different classes, you can include parts like in
+the link:https://github.com/geekq/workflow/blob/develop/test/main_test.rb#L95-L110[`including a child workflow definition` example].
-* extract Rails/ActiveRecord integration into a separate gem
- workflow-activerecord
-* Remodel integration removed - needs to be a separate gem
+Documenting with diagrams
+-------------------------
-Special thanks to https://github.com/voltechs[voltechs] for implementing
-Rails 5 support and helping to revive `workflow`!
+You can generate a graphical representation of the workflow for
+a particular class for documentation purposes.
+Use `Workflow::create_workflow_diagram(class)` in your rake task like:
+```rb
+namespace :doc do
+ desc "Generate a workflow graph for a model passed e.g. as 'MODEL=Order'."
+ task :workflow => :environment do
+ require 'workflow/draw'
+ Workflow::Draw::workflow_diagram(ENV['MODEL'].constantize)
+ end
+end
+```
Support, Participation
----------------------
@@ -560,6 +542,11 @@ bundle install
bundle exec rake test
```
+### Check list for you pull request
+
+* [ ] unit tests for the new behavior provided: new tests fail without you change, all tests succeed with your change
+* [ ] documentation update included
+
### Other 3rd party libraries
https://github.com/kwent/active_admin-workflow[ActiveAdmin-Workflow] - is an
@@ -569,7 +556,7 @@ integration with https://github.com/activeadmin/activeadmin[ActiveAdmin].
Author: Vladimir Dobriakov,
-Copyright (c) 2010-2019 Vladimir Dobriakov and Contributors
+Copyright (c) 2010-2024 Vladimir Dobriakov and Contributors
Copyright (c) 2008-2009 Vodafone
@@ -578,4 +565,3 @@ Copyright (c) 2007-2008 Ryan Allen, FlashDen Pty Ltd
Based on the work of Ryan Allen and Scott Barron
Licensed under MIT license, see the MIT-LICENSE file.
-
diff --git a/lib/workflow.rb b/lib/workflow.rb
index 558e281..31e639d 100644
--- a/lib/workflow.rb
+++ b/lib/workflow.rb
@@ -2,7 +2,7 @@
require 'workflow/specification'
-# See also README.markdown for documentation
+# See also README for documentation
module Workflow
module ClassMethods
attr_reader :workflow_spec
@@ -61,12 +61,12 @@ def assign_workflow(specification_object)
state.events.flat.each do |event|
event_name = event.name
module_eval do
- define_method "#{event_name}!".to_sym do |*args|
- process_event!(event_name, *args)
+ define_method "#{event_name}!".to_sym do |*args, **kwargs|
+ 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
@@ -94,8 +94,8 @@ def halted_because
@halted_because
end
- def process_event!(name, *args)
- event = current_state.events.first_applicable(name, self)
+ def process_event!(name, *args, **kwargs)
+ 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?
@@ -107,26 +107,26 @@ def process_event!(name, *args)
from = current_state
to = spec.states[event.transitions_to]
- run_before_transition(from, to, name, *args)
+ run_before_transition(from, to, name, *args, **kwargs)
return false if @halted
begin
- return_value = run_action(event.action, *args) || run_action_callback(event.name, *args)
+ return_value = run_action(event.action, *args, **kwargs) || run_action_callback(event.name, *args, **kwargs)
rescue StandardError => e
- run_on_error(e, from, to, name, *args)
+ run_on_error(e, from, to, name, *args, **kwargs)
end
return false if @halted
- run_on_transition(from, to, name, *args)
+ run_on_transition(from, to, name, *args, **kwargs)
- run_on_exit(from, to, name, *args)
+ run_on_exit(from, to, name, *args, **kwargs)
transition_value = persist_workflow_state to.to_s
- run_on_entry(to, from, name, *args)
+ run_on_entry(to, from, name, *args, **kwargs)
- run_after_transition(from, to, name, *args)
+ run_after_transition(from, to, name, *args, **kwargs)
return_value.nil? ? transition_value : return_value
end
@@ -169,31 +169,31 @@ def check_transition(event)
end
end
- def run_before_transition(from, to, event, *args)
- instance_exec(from.name, to.name, event, *args, &spec.before_transition_proc) if
+ def run_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)
+ def run_on_error(error, from, to, event, *args, **kwargs)
if spec.on_error_proc
- instance_exec(error, from.name, to.name, event, *args, &spec.on_error_proc)
+ instance_exec(error, from.name, to.name, event, *args, **kwargs, &spec.on_error_proc)
halt(error.message)
else
raise error
end
end
- def run_on_transition(from, to, event, *args)
- instance_exec(from.name, to.name, event, *args, &spec.on_transition_proc) if spec.on_transition_proc
+ def run_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)
- instance_exec(from.name, to.name, event, *args, &spec.after_transition_proc) if
+ def run_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
- def run_action(action, *args)
- instance_exec(*args, &action) if action
+ def run_action(action, *args, **kwargs)
+ instance_exec(*args, **kwargs, &action) if action
end
def has_callback?(action)
@@ -206,27 +206,27 @@ def has_callback?(action)
self.private_methods(false).map(&:to_sym).include?(action)
end
- def run_action_callback(action_name, *args)
+ def run_action_callback(action_name, *args, **kwargs)
action = action_name.to_sym
- self.send(action, *args) if has_callback?(action)
+ self.send(action, *args, **kwargs) if has_callback?(action)
end
- def run_on_entry(state, prior_state, triggering_event, *args)
+ def run_on_entry(state, prior_state, triggering_event, *args, **kwargs)
if state.on_entry
- instance_exec(prior_state.name, triggering_event, *args, &state.on_entry)
+ 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 if has_callback?(hook_name)
+ self.send hook_name, prior_state, triggering_event, *args, **kwargs if has_callback?(hook_name)
end
end
- def run_on_exit(state, new_state, triggering_event, *args)
+ def run_on_exit(state, new_state, triggering_event, *args, **kwargs)
if state
if state.on_exit
- instance_exec(new_state.name, triggering_event, *args, &state.on_exit)
+ 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 if has_callback?(hook_name)
+ self.send hook_name, new_state, triggering_event, *args, **kwargs if has_callback?(hook_name)
end
end
end
diff --git a/lib/workflow/event.rb b/lib/workflow/event.rb
index ed5048a..c425dd7 100644
--- a/lib/workflow/event.rb
+++ b/lib/workflow/event.rb
@@ -15,12 +15,26 @@ 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)
+ m = object.method(condition)
+ # Conditionals can now take the arguments of the trasition action into account #227
+ # But in case the current conditional wants to ignore any event_argument on its decision -
+ # does not accept parameters, also support that.
+ if m.arity == 0 # no additional parameters accepted
+ object.send(condition)
+ else
+ object.send(condition, *event_arguments)
+ end
else
- condition.call(object)
+ # Blocks and non-lambda Procs can ignore extra arguments without raising an error in Ruby,
+ # but lambdas cannot, so we still have to check arity
+ if condition.arity == 1 # no additional parameters accepted
+ condition.call(object)
+ else
+ condition.call(object, *event_arguments)
+ end
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
diff --git a/lib/workflow/specification.rb b/lib/workflow/specification.rb
index b98033f..acf3102 100644
--- a/lib/workflow/specification.rb
+++ b/lib/workflow/specification.rb
@@ -20,6 +20,10 @@ def state_names
private
+ def include(proc)
+ instance_eval(&proc)
+ end
+
def state(name, meta = {:meta => {}}, &events_and_etc)
# meta[:meta] to keep the API consistent..., gah
new_state = Workflow::State.new(name, self, meta[:meta])
diff --git a/lib/workflow/version.rb b/lib/workflow/version.rb
index 6468839..43b9fe9 100644
--- a/lib/workflow/version.rb
+++ b/lib/workflow/version.rb
@@ -1,3 +1,3 @@
module Workflow
- VERSION = "2.0.2"
+ VERSION = "3.1.1"
end
diff --git a/test/conditionals_test.rb b/test/conditionals_test.rb
new file mode 100644
index 0000000..6f9fae3
--- /dev/null
+++ b/test/conditionals_test.rb
@@ -0,0 +1,116 @@
+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 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 and transition filters 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 do
+ event :check, :transitions_to => :low_battery, :if => :check_low_battery?
+ event :check, :transitions_to => :on # stay in on state otherwise
+ end
+ state :low_battery
+ end
+ attr_reader :battery
+ def initialize(battery)
+ @battery = battery
+ end
+
+ def sufficient_battery_level?(power_adapter)
+ power_adapter || @battery > 10
+ end
+
+ def check_low_battery?() # supports no arguments, lets test below, what happens if the action uses addtional args
+ # 'in check_low_battery? method'
+ 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?
+ device.check!('foo') # the conditional in the definition above does not support arguments, but make it work
+ # by ignoring superfluous arguments for compatibility
+ assert device.on?
+ end
+
+ test 'gh-227 allow event arguments in conditions - test with a proc' do
+ c = Class.new do
+ include Workflow
+ # define more advanced workflow, where event methods allow arguments
+ workflow do
+ state :off do
+ # turn_on and transition filters 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 do
+ # Use a lambda proc, which enforces correct arity
+ event :check, :transitions_to => :low_battery, :if => -> (obj) { return false }
+ event :check, :transitions_to => :on # stay in on state otherwise
+ end
+ 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?
+ device.check!('foo') # also ensure that if conditional in the definition above does not support arguments,
+ # it still works and just ignores superfluous arguments
+ assert device.on?
+ end
+
+end
+
diff --git a/test/main_test.rb b/test/main_test.rb
index 6bc0f17..d7031fb 100644
--- a/test/main_test.rb
+++ b/test/main_test.rb
@@ -92,6 +92,23 @@ class MainTest < Minitest::Test
assert_equal 'one', c.new.current_state.to_s
end
+ test 'including a child workflow definition for composable workflows' do
+ child = Proc.new do
+ state :two
+ end
+
+ c = Class.new
+ c.class_eval do
+ include Workflow
+ workflow do
+ state :one
+ include child
+ state :three
+ end
+ end
+ assert_equal [:one, :two, :three], c.workflow_spec.states.keys
+ end
+
# TODO Consider following test case:
# test 'multiple events with the same name and different arguments lists from different states'
@@ -328,42 +345,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
@@ -390,4 +371,3 @@ def capture_streams
end
end
-
diff --git a/test/workflow_from_json_test.rb b/test/workflow_from_json_test.rb
new file mode 100644
index 0000000..07b443b
--- /dev/null
+++ b/test/workflow_from_json_test.rb
@@ -0,0 +1,158 @@
+require File.join(File.dirname(__FILE__), 'test_helper')
+require 'workflow'
+class WorkflowFromJsonTest < Minitest::Test
+
+ test '#241 define a workflow from JSON' 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 {|prior_state, triggering_event| send state_def[:on_entry], prior_state, triggering_event } if state_def.include?(:on_entry)
+ 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
+ before_transition(from, to, triggering_event, *args, **kwargs)
+ end
+
+ on_transition do |from, to, triggering_event, *args, **kwargs|
+ ran_normal << :on_transition
+ on_transition(from, to, triggering_event, *args, **kwargs)
+ end
+
+ after_transition do |from, to, triggering_event, *args, **kwargs|
+ ran_normal << :after_transition
+ after_transition(from, to, triggering_event, *args, **kwargs)
+ 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
diff --git a/workflow.gemspec b/workflow.gemspec
index 41e6e99..747f726 100644
--- a/workflow.gemspec
+++ b/workflow.gemspec
@@ -18,21 +18,25 @@ Gem::Specification.new do |gem|
gem.licenses = ['MIT']
gem.homepage = "https://github.com/geekq/workflow"
+ gem.metadata["homepage_uri"] = gem.homepage
+ gem.metadata["source_code_uri"] = gem.homepage
+ gem.metadata["changelog_uri"] = "https://github.com/geekq/workflow/blob/develop/CHANGELOG.md"
+
gem.files = Dir['CHANGELOG.md', 'README.md', 'LICENSE', 'lib/**/*']
gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
gem.require_paths = ['lib']
gem.extra_rdoc_files = [
- "README.markdown"
+ "README.adoc"
]
- gem.required_ruby_version = '>= 2.3'
- gem.add_development_dependency 'rdoc', '~> 6.1'
- gem.add_development_dependency 'bundler', '~> 2.0'
- gem.add_development_dependency 'mocha', '~> 1.8'
- gem.add_development_dependency 'rake', '~> 12.3'
- gem.add_development_dependency 'minitest', '~> 5.11'
+ gem.required_ruby_version = '>= 2.7'
+ gem.add_development_dependency 'rdoc', '~> 6.4'
+ gem.add_development_dependency 'bundler', '~> 2.3'
+ gem.add_development_dependency 'mocha', '~> 2.2'
+ gem.add_development_dependency 'rake', '~> 13.1'
+ gem.add_development_dependency 'minitest', '~> 5.21'
gem.add_development_dependency 'ruby-graphviz', '~> 1.2'
end