finite_machine 0.11.2 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +80 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +679 -624
  5. data/lib/finite_machine.rb +35 -45
  6. data/lib/finite_machine/async_call.rb +5 -21
  7. data/lib/finite_machine/callable.rb +4 -4
  8. data/lib/finite_machine/catchable.rb +24 -14
  9. data/lib/finite_machine/choice_merger.rb +20 -20
  10. data/lib/finite_machine/const.rb +16 -0
  11. data/lib/finite_machine/definition.rb +3 -3
  12. data/lib/finite_machine/dsl.rb +98 -151
  13. data/lib/finite_machine/env.rb +4 -2
  14. data/lib/finite_machine/event_definition.rb +7 -15
  15. data/lib/finite_machine/{events_chain.rb → events_map.rb} +40 -53
  16. data/lib/finite_machine/hook_event.rb +60 -61
  17. data/lib/finite_machine/hooks.rb +44 -36
  18. data/lib/finite_machine/listener.rb +2 -2
  19. data/lib/finite_machine/logger.rb +5 -4
  20. data/lib/finite_machine/{event_queue.rb → message_queue.rb} +76 -32
  21. data/lib/finite_machine/observer.rb +71 -34
  22. data/lib/finite_machine/safety.rb +16 -14
  23. data/lib/finite_machine/state_definition.rb +3 -5
  24. data/lib/finite_machine/state_machine.rb +93 -76
  25. data/lib/finite_machine/state_parser.rb +55 -83
  26. data/lib/finite_machine/subscribers.rb +2 -2
  27. data/lib/finite_machine/threadable.rb +3 -1
  28. data/lib/finite_machine/transition.rb +34 -34
  29. data/lib/finite_machine/transition_builder.rb +23 -32
  30. data/lib/finite_machine/transition_event.rb +12 -11
  31. data/lib/finite_machine/two_phase_lock.rb +8 -6
  32. data/lib/finite_machine/undefined_transition.rb +5 -6
  33. data/lib/finite_machine/version.rb +2 -2
  34. metadata +58 -142
  35. data/.gitignore +0 -18
  36. data/.rspec +0 -5
  37. data/.ruby-gemset +0 -1
  38. data/.ruby-version +0 -1
  39. data/.travis.yml +0 -26
  40. data/Gemfile +0 -15
  41. data/Rakefile +0 -8
  42. data/assets/finite_machine_logo.png +0 -0
  43. data/examples/atm.rb +0 -45
  44. data/examples/bug_system.rb +0 -145
  45. data/finite_machine.gemspec +0 -23
  46. data/lib/finite_machine/async_proxy.rb +0 -30
  47. data/spec/integration/system_spec.rb +0 -95
  48. data/spec/spec_helper.rb +0 -33
  49. data/spec/unit/alias_target_spec.rb +0 -108
  50. data/spec/unit/async_events_spec.rb +0 -138
  51. data/spec/unit/callable/call_spec.rb +0 -113
  52. data/spec/unit/callbacks_spec.rb +0 -942
  53. data/spec/unit/can_spec.rb +0 -98
  54. data/spec/unit/choice_spec.rb +0 -331
  55. data/spec/unit/define_spec.rb +0 -55
  56. data/spec/unit/definition_spec.rb +0 -115
  57. data/spec/unit/event_names_spec.rb +0 -19
  58. data/spec/unit/event_queue_spec.rb +0 -52
  59. data/spec/unit/events_chain/add_spec.rb +0 -25
  60. data/spec/unit/events_chain/cancel_transitions_spec.rb +0 -22
  61. data/spec/unit/events_chain/choice_transition_spec.rb +0 -28
  62. data/spec/unit/events_chain/clear_spec.rb +0 -15
  63. data/spec/unit/events_chain/events_spec.rb +0 -18
  64. data/spec/unit/events_chain/inspect_spec.rb +0 -24
  65. data/spec/unit/events_chain/match_transition_spec.rb +0 -37
  66. data/spec/unit/events_chain/move_to_spec.rb +0 -48
  67. data/spec/unit/events_chain/states_for_spec.rb +0 -17
  68. data/spec/unit/events_spec.rb +0 -459
  69. data/spec/unit/handlers_spec.rb +0 -152
  70. data/spec/unit/hook_event/build_spec.rb +0 -15
  71. data/spec/unit/hook_event/eql_spec.rb +0 -36
  72. data/spec/unit/hook_event/infer_default_name_spec.rb +0 -13
  73. data/spec/unit/hook_event/initialize_spec.rb +0 -25
  74. data/spec/unit/hook_event/notify_spec.rb +0 -14
  75. data/spec/unit/hooks/call_spec.rb +0 -24
  76. data/spec/unit/hooks/clear_spec.rb +0 -16
  77. data/spec/unit/hooks/inspect_spec.rb +0 -17
  78. data/spec/unit/hooks/register_spec.rb +0 -22
  79. data/spec/unit/if_unless_spec.rb +0 -353
  80. data/spec/unit/initial_spec.rb +0 -222
  81. data/spec/unit/inspect_spec.rb +0 -17
  82. data/spec/unit/is_spec.rb +0 -55
  83. data/spec/unit/log_transitions_spec.rb +0 -30
  84. data/spec/unit/logger_spec.rb +0 -38
  85. data/spec/unit/respond_to_spec.rb +0 -38
  86. data/spec/unit/state_parser/inspect_spec.rb +0 -25
  87. data/spec/unit/state_parser/parse_spec.rb +0 -59
  88. data/spec/unit/states_spec.rb +0 -34
  89. data/spec/unit/subscribers_spec.rb +0 -42
  90. data/spec/unit/target_spec.rb +0 -225
  91. data/spec/unit/terminated_spec.rb +0 -95
  92. data/spec/unit/transition/check_conditions_spec.rb +0 -54
  93. data/spec/unit/transition/inspect_spec.rb +0 -25
  94. data/spec/unit/transition/matches_spec.rb +0 -23
  95. data/spec/unit/transition/states_spec.rb +0 -31
  96. data/spec/unit/transition/to_state_spec.rb +0 -27
  97. data/spec/unit/trigger_spec.rb +0 -22
  98. data/spec/unit/undefined_transition/eql_spec.rb +0 -17
  99. data/tasks/console.rake +0 -11
  100. data/tasks/coverage.rake +0 -11
  101. data/tasks/spec.rake +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 75abd57820e49c51d189b0a338477694f8ec7059
4
- data.tar.gz: 5d983ca01af05a88d06626a2cd9f27207c87ad0d
2
+ SHA256:
3
+ metadata.gz: 2d4822c18a4beea1845f39a33e910051412c7fea3479783b361897ecbc60b006
4
+ data.tar.gz: ec4f8663f7ede7eb0357b9404b814ca670a523923f6c0b4435c2f09acd6c2e32
5
5
  SHA512:
6
- metadata.gz: ecd18ac6229dd4293ef77c02f8390f1ad012840680526ff434a72b6dedb107fb85c237cdf028ef0c24574a376041becebeb49b0312ccf1a09a1307aafc74026e
7
- data.tar.gz: de118ce61f3dce393546528420918b6d042b298f0c54a7d92baa853e54763e685e6deadc143954b6e894d004e4a8458f8ff1f23113f646c3a0bbf6009f52438b
6
+ metadata.gz: 57bdadc9ea5349045fe6711983dae949e8a8e78d569dbe7e91ac410b7afec7f1a2cfbb95f6b400b068ffcb1a7c4f783e84575f0680c2956ec5dc8473d91ee312
7
+ data.tar.gz: b71c1fe3d0f8701fff5060b8dd6882fa9682e2a79dddbfda865d6d553c9130b896690ad16b4607a567e4d03f95a5dd0b4d33517cf008883e7dd1eab1851e411e
@@ -1,5 +1,74 @@
1
1
  # Change Log
2
2
 
3
+ ## [v0.14.0] - 2020-09-12
4
+
5
+ ### Added
6
+ * Add ability to declare alias target inside definition
7
+ * Add #respond_to_missing? to DSL to check if a given method is supported
8
+ * Add current state attribute to StateMachine#inspect
9
+
10
+ ## [v0.13.0] - 2020-05-13
11
+
12
+ ### Added
13
+ * Add sync as a dependency
14
+ * Add metadata to gemspec
15
+
16
+ ### Changed
17
+ * Change StateMachine#final_state to #terminal_states by Brad Gessler(@bradgessler)
18
+ * Change to remove artefacts like tests & tasks from gemspec
19
+
20
+ ### Fixed
21
+ * Fix Ruby 2.7 keyword arguments warnings
22
+ * Fix MessageQueue deadlock by Pavel Rosický(@ahorek)
23
+
24
+ ## [v0.12.1] - 2019-07-12
25
+
26
+ ### Changed
27
+ * Change to relax dev dependencies versions
28
+
29
+ ### Fixed
30
+ * Fix FiniteMachine.new to stop coercing object target that responds to to_hash into options
31
+
32
+ ## [v0.12.0] - 2018-11-11
33
+
34
+ ### Added
35
+ * Add concurrent-ruby as dependency
36
+ * Add FiniteMachine#new for declaring state machines
37
+ * Add Observer#cancel_event for cancelling event transitions in callbacks, instead of using callback return value
38
+ * Add Const for declaring unique machine constants
39
+ * Add :auto_methods configuration option for disabling automatic conversion of event names into methods
40
+
41
+ ### Changed
42
+ * Change gemspec to require Ruby >= 2.0
43
+ * Change FiniteMachine#define to create machine class instances
44
+ * Change EventsChain to EventsMap and use Concurrent::Map for holding event transitions
45
+ * Change Hooks to use Concurrent::Map for storing callbacks
46
+ * Change MessageQueue to use mutex to synchronize access
47
+ * Change StateParser to remove internal state and use class methods instead
48
+ * Change Observer to create callbacks queue on demand
49
+ * Change :any key to be a unique constant ANY_EVENT and ANY_STATE
50
+ * Change #event_names to #events for retrieving all events
51
+ * Remove thread synchronization from AsyncCall, TransitinEvent, HookEvent, DSL, Hooks, TransitionBuilder, ChoiceMerger objects
52
+ * Remove #async call from StateMachine
53
+ * Remove #target, #alias_target, #callbacks, #events and #handlers calls from DSL
54
+
55
+ ### Fixed
56
+ * Fix StateParser to raise error without nil
57
+ * Fix to rollback to current state when an error occurs
58
+
59
+ ## [v0.11.3] - 2016-03-04
60
+
61
+ ### Added
62
+ * Add performance tests & memory usage benchmarks
63
+
64
+ ### Changed
65
+ * Change EventQueue to MessageQueue for handling generic asynchronous messages
66
+ * Split async behaviour to use CallbackQueue for observed callbacks and EventQueue for async event triggers.
67
+ * Change AsyncProxy and Observer to lazy load message queue
68
+
69
+ ### Fixed
70
+ * Fix memory leak - issue #42 with help from @craiglittle
71
+
3
72
  ## [v0.11.2] - 2015-12-30
4
73
 
5
74
  ### Added
@@ -237,6 +306,17 @@
237
306
  ### Fixed
238
307
  * Fix bug - callback event object returns correct from state
239
308
 
309
+ ## [v0.1.0] - 2014-02-09
310
+
311
+ ## [v0.0.1] - 2014-01-10
312
+
313
+ * Initial release
314
+
315
+ [v0.14.0]: https://github.com/peter-murach/finite_machine/compare/v0.13.0...v0.14.0
316
+ [v0.13.0]: https://github.com/peter-murach/finite_machine/compare/v0.12.1...v0.13.0
317
+ [v0.12.1]: https://github.com/peter-murach/finite_machine/compare/v0.12.0...v0.12.1
318
+ [v0.12.0]: https://github.com/peter-murach/finite_machine/compare/v0.11.3...v0.12.0
319
+ [v0.11.3]: https://github.com/peter-murach/finite_machine/compare/v0.11.2...v0.11.3
240
320
  [v0.11.2]: https://github.com/peter-murach/finite_machine/compare/v0.11.1...v0.11.2
241
321
  [v0.11.1]: https://github.com/peter-murach/finite_machine/compare/v0.11.0...v0.11.1
242
322
  [v0.11.0]: https://github.com/peter-murach/finite_machine/compare/v0.10.2...v0.11.0
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014 Piotr Murach
1
+ Copyright (c) 2014 Piotr Murach (piotrmurach.com)
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,41 +1,44 @@
1
1
  <div align="center">
2
- <a href="http://peter-murach.github.io/finite_machine/"><img width="236" src="https://raw.githubusercontent.com/peter-murach/finite_machine/master/assets/finite_machine_logo.png" alt="finite machine logo" /></a>
2
+ <a href="https://piotrmurach.github.io/finite_machine/"><img width="236" src="https://github.com/piotrmurach/finite_machine/raw/master/assets/finite_machine_logo.png" alt="finite machine logo" /></a>
3
3
  </div>
4
+
4
5
  # FiniteMachine
6
+
5
7
  [![Gem Version](https://badge.fury.io/rb/finite_machine.svg)][gem]
6
- [![Build Status](https://secure.travis-ci.org/peter-murach/finite_machine.svg?branch=master)][travis]
7
- [![Code Climate](https://codeclimate.com/github/peter-murach/finite_machine/badges/gpa.svg)][codeclimate]
8
- [![Coverage Status](https://coveralls.io/repos/peter-murach/finite_machine/badge.svg)][coverage]
9
- [![Inline docs](http://inch-ci.org/github/peter-murach/finite_machine.svg)][inchpages]
10
- [![Gitter](https://badges.gitter.im/Join Chat.svg)][gitter]
8
+ [![Build Status](https://secure.travis-ci.org/piotrmurach/finite_machine.svg?branch=master)][travis]
9
+ [![Build status](https://ci.appveyor.com/api/projects/status/8ho4ijacpr7b4f4t?svg=true)][appveyor]
10
+ [![Code Climate](https://codeclimate.com/github/piotrmurach/finite_machine/badges/gpa.svg)][codeclimate]
11
+ [![Coverage Status](https://coveralls.io/repos/github/piotrmurach/finite_machine/badge.svg?branch=master)][coverage]
12
+ [![Inline docs](http://inch-ci.org/github/piotrmurach/finite_machine.svg)][inchpages]
13
+ [![Gitter](https://badges.gitter.im/Join%20Chat.svg)][gitter]
11
14
 
12
15
  [gem]: http://badge.fury.io/rb/finite_machine
13
- [travis]: http://travis-ci.org/peter-murach/finite_machine
14
- [codeclimate]: https://codeclimate.com/github/peter-murach/finite_machine
15
- [coverage]: https://coveralls.io/r/peter-murach/finite_machine
16
- [inchpages]: http://inch-ci.org/github/peter-murach/finite_machine
17
- [gitter]: https://gitter.im/peter-murach/finite_machine?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
16
+ [travis]: http://travis-ci.org/piotrmurach/finite_machine
17
+ [appveyor]: https://ci.appveyor.com/project/piotrmurach/finite-machine
18
+ [codeclimate]: https://codeclimate.com/github/piotrmurach/finite_machine
19
+ [coverage]: https://coveralls.io/github/piotrmurach/finite_machine?branch=master
20
+ [inchpages]: http://inch-ci.org/github/piotrmurach/finite_machine
21
+ [gitter]: https://gitter.im/piotrmurach/finite_machine
18
22
 
19
- > A minimal finite state machine with a straightforward and intuitive syntax. You can quickly model states and add callbacks that can be triggered synchronously or asynchronously. The machine is event driven with a focus on passing synchronous and asynchronous messages to trigger state transitions.
23
+ > A minimal finite state machine with a straightforward and intuitive syntax. You can quickly model states and transitions and register callbacks to watch for triggered transitions.
20
24
 
21
25
  ## Features
22
26
 
23
27
  * plain object state machine
24
- * easy custom object integration
25
- * natural DSL for declaring events, callbacks and exceptions
26
- * observers (pub/sub) for state changes
27
- * ability to check reachable states
28
- * ability to check for terminal state
29
- * transition guard conditions
30
- * dynamic conditional branching
31
- * sync and async transitions
32
- * sync and async callbacks
28
+ * easy [custom object integration](#29-target)
29
+ * natural DSL for declaring events, callbacks and exception handlers
30
+ * [callbacks](#4-callbacks) for state and event changes
31
+ * ability to check [reachable](#28-can-and-cannot) state(s)
32
+ * ability to check for [terminal](#25-terminal) state(s)
33
+ * transition [guard conditions](#38-conditional-transitions)
34
+ * dynamic [choice pseudostates](#39-choice-pseudostates)
35
+ * thread safe
33
36
 
34
37
  ## Installation
35
38
 
36
39
  Add this line to your application's Gemfile:
37
40
 
38
- gem 'finite_machine'
41
+ gem "finite_machine"
39
42
 
40
43
  Then execute:
41
44
 
@@ -48,143 +51,225 @@ Or install it yourself as:
48
51
  ## Contents
49
52
 
50
53
  * [1. Usage](#1-usage)
51
- * [1.1 current](#11-current)
52
- * [1.2 initial](#12-initial)
53
- * [1.3 terminal](#13-terminal)
54
- * [1.4 is?](#14-is)
55
- * [1.5 can? and cannot?](#15-can-and-cannot)
56
- * [1.6 target](#16-target)
57
- * [1.7 Alias target](#17-alias-target)
58
- * [1.8 restore!](#18-restore)
59
- * [1.9 states](#19-states)
60
- * [1.10 event names](#110-event-names)
61
- * [2. Transitions](#2-transitions)
62
- * [2.1 Performing transitions](#21-performing-transitions)
63
- * [2.2 Dangerous transitions](#22-dangerous-transitions)
64
- * [2.3 Asynchronous transitions](#23-asynchronous-transitions)
65
- * [2.4 Multiple from states](#24-multiple-from-states)
66
- * [2.5 From :any state](#25-from-any-state)
67
- * [2.6 Grouping states under single event](#26-grouping-states-under-single-event)
68
- * [2.7 Silent transitions](#27-silent-transitions)
69
- * [2.8 Log transitions](#28-log-transitions)
70
- * [3. Conditional transitions](#3-conditional-transitions)
71
- * [3.1 Using a Proc](#31-using-a-proc)
72
- * [3.2 Using a Symbol](#32-using-a-symbol)
73
- * [3.3 Using a String](#33-using-a-string)
74
- * [3.4 Combining transition conditions](#34-combining-transition-conditions)
75
- * [4. Choice pseudostates](#4-choice-pseudostates)
76
- * [4.1 Dynamic choice conditions](#41-dynamic-choice-conditions)
77
- * [4.2 Multiple from states](#42-multiple-from-states)
78
- * [5. Callbacks](#5-callbacks)
79
- * [5.1 on_enter](#51-on_enter)
80
- * [5.2 on_transition](#52-on_transition)
81
- * [5.3 on_exit](#53-on_exit)
82
- * [5.4 on_before](#54-on_before)
83
- * [5.5 on_after](#55-on_after)
84
- * [5.6 once_on](#56-once_on)
85
- * [5.7 Execution sequence](#57-execution-sequence)
86
- * [5.8 Parameters](#58-parameters)
87
- * [5.9 Same kind of callbacks](#59-same-kind-of-callbacks)
88
- * [5.10 Fluid callbacks](#510-fluid-callbacks)
89
- * [5.11 Executing methods inside callbacks](#511-executing-methods-inside-callbacks)
90
- * [5.12 Defining callbacks](#512-defining-callbacks)
91
- * [5.13 Asynchronous callbacks](#513-asynchronous-callbacks)
92
- * [5.14 Cancelling inside callbacks](#514-cancelling-inside-callbacks)
93
- * [6. Errors](#6-errors)
94
- * [6.1 Using target](#61-using-target)
95
- * [7. Stand-Alone FiniteMachine](#7-stand-alone-finitemachine)
96
- * [7.1 Creating a Definition](#71-creating-a-definition)
97
- * [7.2 Targeting definition](#72-targeting-definition)
98
- * [7.3 Definition inheritance](#73-definition-inheritance)
99
- * [8. Integration](#8-integration)
100
- * [8.1 Plain Ruby Objects](#81-plain-ruby-objects)
101
- * [8.2 ActiveRecord](#82-activerecord)
102
- * [8.3 Transactions](#83-transactions)
103
- * [9. Tips](#9-tips)
104
-
105
- ## 1 Usage
54
+ * [2. API](#2-api)
55
+ * [2.1 new](#21-new)
56
+ * [2.2 define](#22-define)
57
+ * [2.3 current](#23-current)
58
+ * [2.4 initial](#24-initial)
59
+ * [2.5 terminal](#25-terminal)
60
+ * [2.6 is?](#26-is)
61
+ * [2.7 trigger](#27-trigger)
62
+ * [2.7.1 :auto_methods](#271-auto_methods)
63
+ * [2.8 can? and cannot?](#28-can-and-cannot)
64
+ * [2.9 target](#29-target)
65
+ * [2.9.1 :alias_target](#27-alias_target)
66
+ * [2.10 restore!](#210-restore)
67
+ * [2.11 states](#211-states)
68
+ * [2.12 events](#212-events)
69
+ * [3. States and Transitions](#3-states-and-transitions)
70
+ * [3.1 Triggering transitions](#31-triggering-transitions)
71
+ * [3.2 Dangerous transitions](#32-dangerous-transitions)
72
+ * [3.3 Multiple from states](#33-multiple-from-states)
73
+ * [3.4 any_state transitions](#34-any_state-transitions)
74
+ * [3.5 Collapsing transitions](#35-collapsing-transitions)
75
+ * [3.6 Silent transitions](#36-silent-transitions)
76
+ * [3.7 Logging transitions](#37-logging-transitions)
77
+ * [3.8 Conditional transitions](#38-conditional-transitions)
78
+ * [3.8.1 Using a Proc](#381-using-a-proc)
79
+ * [3.8.2 Using a Symbol](#382-using-a-symbol)
80
+ * [3.8.3 Using a String](#383-using-a-string)
81
+ * [3.8.4 Combining transition conditions](#384-combining-transition-conditions)
82
+ * [3.9 Choice pseudostates](#39-choice-pseudostates)
83
+ * [3.9.1 Dynamic choice conditions](#391-dynamic-choice-conditions)
84
+ * [3.9.2 Multiple from states](#392-multiple-from-states)
85
+ * [4. Callbacks](#4-callbacks)
86
+ * [4.1 on_(enter|transition|exit)](#41-on_entertransitionexit)
87
+ * [4.2 on_(before|after)](#42-on_beforeafter)
88
+ * [4.3 once_on](#43-once_on)
89
+ * [4.4 Execution sequence](#44-execution-sequence)
90
+ * [4.5 Callback parameters](#45-callback-parameters)
91
+ * [4.6 Duplicate callbacks](#46-duplicate-callbacks)
92
+ * [4.7 Fluid callbacks](#47-fluid-callbacks)
93
+ * [4.8 Methods inside callbacks](#48-methods-inside-callbacks)
94
+ * [4.9 Cancelling callbacks](#49-cancelling-callbacks)
95
+ * [4.10 Asynchronous callbacks](#410-asynchronous-callbacks)
96
+ * [4.11 Instance callbacks](#411-instance-callbacks)
97
+ * [5. Error Handling](#5-error-handling)
98
+ * [5.1 Using target](#51-using-target)
99
+ * [6. Stand-alone](#6-stand-alone)
100
+ * [6.1 Creating a Definition](#61-creating-a-definition)
101
+ * [6.2 Targeting definition](#62-targeting-definition)
102
+ * [6.3 Definition inheritance](#63-definition-inheritance)
103
+ * [7. Integration](#7-integration)
104
+ * [7.1 Plain Ruby Objects](#71-plain-ruby-objects)
105
+ * [7.2 ActiveRecord](#72-activerecord)
106
+ * [7.3 Transactions](#73-transactions)
107
+ * [8. Tips](#8-tips)
108
+
109
+ ## 1. Usage
106
110
 
107
111
  Here is a very simple example of a state machine:
108
112
 
109
113
  ```ruby
110
- fm = FiniteMachine.define do
114
+ fm = FiniteMachine.new do
111
115
  initial :red
112
116
 
113
- events {
114
- event :ready, :red => :yellow
115
- event :go, :yellow => :green
116
- event :stop, :green => :red
117
- }
118
-
119
- callbacks {
120
- on_before(:ready) { |event| ... }
121
- on_after(:go) { |event| ... }
122
- on_before(:stop) { |event| ... }
123
- }
117
+ event :ready, :red => :yellow
118
+ event :go, :yellow => :green
119
+ event :stop, :green => :red
120
+
121
+ on_before(:ready) { |event| ... }
122
+ on_exit(:yellow) { |event| ... }
123
+ on_enter(:green) { |event| ... }
124
+ on_after(:stop) { |event| ... }
124
125
  end
125
126
  ```
126
127
 
127
- As the example demonstrates, by calling the `define` method on **FiniteMachine** you create an instance of finite state machine. The `events` and `callbacks` scopes help to define the behaviour of the machine. Read [Transitions](#2-transitions) and [Callbacks](#5-callbacks) sections for more details.
128
+ By calling the `new` method on **FiniteMachine**, you gain access to a powerful DSL for expressing transitions and registering callbacks.
129
+
130
+ Having declared the states and transitions, you can check current state:
131
+
132
+ ```ruby
133
+ fm.current # => :red
134
+ ````
135
+
136
+ And then trigger transitions using the `trigger`:
137
+
138
+ ```ruby
139
+ fm.trigger(:ready)
140
+ ```
141
+
142
+ Or you can use direct method calls:
143
+
144
+ ```ruby
145
+ fm.ready
146
+ ```
147
+
148
+ Read [States and Transitions](#3-states-and-transitions) and [Callbacks](#4-callbacks) sections for more details.
128
149
 
129
- Alternatively, you can construct the machine like a regular object without using the DSL. The same machine could be reimplemented as follows:
150
+ Alternatively, you can construct the state machine like a regular object using the same DSL methods. Similar machine could be reimplemented as follows:
130
151
 
131
152
  ```ruby
132
- fm = FiniteMachine.new initial: :red
153
+ fm = FiniteMachine.new(initial: :red)
133
154
  fm.event(:ready, :red => :yellow)
134
155
  fm.event(:go, :yellow => :green)
135
156
  fm.event(:stop, :green => :red)
136
157
  fm.on_before(:ready) { |event| ... }
137
- fm.on_after(:go) { |event| ... }
138
- fm.on_before(:stop) { |event| ...}
158
+ fm.on_exit(:yellow) { |event| ... }
159
+ fm.on_enter(:green) { |event| ... }
160
+ fm.on_after(:stop) { |event| ... }
161
+ ```
162
+
163
+ ## 2. API
164
+
165
+ ### 2.1 new
166
+
167
+ In most cases you will want to create an instance of **FiniteMachine** class using the `new` method. At the bare minimum you need specify the transition events inside a block using the `event` helper:
168
+
169
+ ```ruby
170
+ fm = FiniteMachine.new do
171
+ initial :green
172
+
173
+ event :slow, :green => :yellow
174
+ event :stop, :yellow => :red
175
+ event :ready, :red => :yellow
176
+ event :go, :yellow => :green
177
+ end
178
+ ```
179
+
180
+ Alternatively, you can skip block definition and instead call DSL methods directly on the state machine instance:
181
+
182
+ ```ruby
183
+ fm = FiniteMachine.new
184
+ fm.initial(:green)
185
+ fm.event(:slow, :green => :yellow)
186
+ fm.event(:stop, :yellow => :red)
187
+ fm.event(:ready,:red => :yellow)
188
+ fm.event(:go, :yellow => :green)
139
189
  ```
140
190
 
141
- ### 1.1 current
191
+ As a guiding rule, any method exposed via DSL is available as a regular method call on the state machine instance.
192
+
193
+ ### 2.2 define
194
+
195
+ To create a reusable definition for a state machine use `define` method. By calling `define` you're creating an anonymous class that can act as a factory for state machines. For example, below we create a `TrafficLights` class that contains our state machine definition:
196
+
197
+ ```ruby
198
+ TrafficLights = FiniteMachine.define do
199
+ initial :green
200
+
201
+ event :slow, :green => :yellow
202
+ event :stop, :yellow => :red
203
+ event :ready, :red => :yellow
204
+ event :go, :yellow => :green
205
+ end
206
+ ```
207
+
208
+ Then we can create however many instance of above class:
209
+
210
+ ```ruby
211
+ lights_fm_a = TrafficLights.new
212
+ lights_fm_b = TrafficLights.new
213
+ ```
214
+
215
+ Each instance will start in consistent state:
216
+
217
+ ```ruby
218
+ lights_fm_a.current # => :green
219
+ lights_fm_b.current # => :green
220
+ ```
221
+
222
+ We can then trigger event for one instance and not the other:
223
+
224
+ ```ruby
225
+ lights_fm_a.slow
226
+ lights_fm_a.current # => :yellow
227
+ lights_fm_b.current # => :green
228
+ ```
229
+
230
+ ### 2.3 current
142
231
 
143
232
  The **FiniteMachine** allows you to query the current state by calling the `current` method.
144
233
 
145
234
  ```ruby
146
- fm.current # => :red
235
+ fm.current # => :red
147
236
  ```
148
237
 
149
- ### 1.2 initial
238
+ ### 2.4 initial
150
239
 
151
240
  There are number of ways to provide the initial state in **FiniteMachine** depending on your requirements.
152
241
 
153
- By default the **FiniteMachine** will be in the `:none` state and you will need to provide an event to transition out of this state.
242
+ By default the **FiniteMachine** will be in the `:none` state and you will need to provide an explicit event to transition out of this state.
154
243
 
155
244
  ```ruby
156
- fm = FiniteMachine.define do
157
- events {
158
- event :start, :none => :green
159
- event :slow, :green => :yellow
160
- event :stop, :yellow => :red
161
- }
245
+ fm = FiniteMachine.new do
246
+ event :init, :none => :green
247
+ event :slow, :green => :yellow
248
+ event :stop, :yellow => :red
162
249
  end
163
250
 
164
251
  fm.current # => :none
165
- fm.start # => true
252
+ fm.init # => true
166
253
  fm.current # => :green
167
254
  ```
168
255
 
169
- If you specify initial state using the `initial` helper, the state machine will be created already in that state.
256
+ If you specify initial state using the `initial` helper, then the state machine will be created already in that state and an implicit `init` event will be created for you and automatically triggered upon the state machine initialization.
170
257
 
171
258
  ```ruby
172
- fm = FiniteMachine.define do
173
- initial :green
259
+ fm = FiniteMachine.new do
260
+ initial :green # fires init event that transitions from :none to :green state
174
261
 
175
- events {
176
- event :slow, :green => :yellow
177
- event :stop, :yellow => :red
178
- }
262
+ event :slow, :green => :yellow
263
+ event :stop, :yellow => :red
179
264
  end
180
265
 
181
266
  fm.current # => :green
182
267
  ```
183
268
 
184
- or by passing named argument `:initial` like so
269
+ Or by passing named argument `:initial` like so:
185
270
 
186
271
  ```ruby
187
- fm = FiniteMachine.define initial: :green do
272
+ fm = FiniteMachine.new(initial: :green) do
188
273
  ...
189
274
  end
190
275
  ```
@@ -192,13 +277,11 @@ end
192
277
  If you want to defer setting the initial state, pass the `:defer` option to the `initial` helper. By default **FiniteMachine** will create `init` event that will allow to transition from `:none` state to the new state.
193
278
 
194
279
  ```ruby
195
- fm = FiniteMachine.define do
280
+ fm = FiniteMachine.new do
196
281
  initial :green, defer: true # Defer calling :init event
197
282
 
198
- events {
199
- event :slow, :green => :yellow
200
- event :stop, :yellow => :red
201
- }
283
+ event :slow, :green => :yellow
284
+ event :stop, :yellow => :red
202
285
  end
203
286
  fm.current # => :none
204
287
  fm.init # execute initial transition
@@ -208,13 +291,11 @@ fm.current # => :green
208
291
  If your target object already has `init` method or one of the events names redefines `init`, you can use different name by passing `:event` option to `initial` helper.
209
292
 
210
293
  ```ruby
211
- fm = FiniteMachine.define do
294
+ fm = FiniteMachine.new do
212
295
  initial :green, event: :start, defer: true # Rename event from :init to :start
213
296
 
214
- events {
215
- event :slow, :green => :yellow
216
- event :stop, :yellow => :red
217
- }
297
+ event :slow, :green => :yellow
298
+ event :stop, :yellow => :red
218
299
  end
219
300
 
220
301
  fm.current # => :none
@@ -222,34 +303,30 @@ fm.start # => call the renamed event
222
303
  fm.current # => :green
223
304
  ```
224
305
 
225
- By default the `initial` does not trigger any callbacks. If you need to fire callbacks and any event associated actions on initial transition, pass the `silent` option set to `false` like so
306
+ By default the `initial` does not trigger any callbacks. If you need to fire callbacks and any event associated actions on initial transition, pass the `silent` option set to `false` like so:
226
307
 
227
308
  ```ruby
228
- fm = FiniteMachine.define do
309
+ fm = FiniteMachine.new do
229
310
  initial :green, silent: false # callbacks are triggered
230
311
 
231
- events {
232
- event :slow, :green => :yellow
233
- event :stop, :yellow => :red
234
- }
312
+ event :slow, :green => :yellow
313
+ event :stop, :yellow => :red
235
314
  end
236
315
  ```
237
316
 
238
- ### 1.3 terminal
317
+ ### 2.5 terminal
239
318
 
240
- To specify a final state **FiniteMachine** uses the `terminal` method. The `terminal` can accept more than one state.
319
+ To specify a final state **FiniteMachine** uses the `terminal` method.
241
320
 
242
321
  ```ruby
243
- fm = FiniteMachine.define do
322
+ fm = FiniteMachine.new do
244
323
  initial :green
245
324
 
246
325
  terminal :red
247
326
 
248
- events {
249
- event :slow, :green => :yellow
250
- event :stop, :yellow => :red
251
- event :go, :red => :green
252
- }
327
+ event :slow, :green => :yellow
328
+ event :stop, :yellow => :red
329
+ event :go, :red => :green
253
330
  end
254
331
  ```
255
332
 
@@ -263,7 +340,27 @@ fm.stop # => true
263
340
  fm.terminated? # => true
264
341
  ```
265
342
 
266
- ### 1.4 is?
343
+ The `terminal` can accept more than one state.
344
+
345
+ ```ruby
346
+ fm = FiniteMachine.new do
347
+ initial :open
348
+
349
+ terminal :close, :canceled
350
+
351
+ event :resolve, :open => :close
352
+ event :decline, :open => :canceled
353
+ end
354
+ ```
355
+
356
+ And the terminal state can be checked using `terminated?`:
357
+
358
+ ```ruby
359
+ fm.decline
360
+ fm.terminated? # => true
361
+ ```
362
+
363
+ ### 2.6 is?
267
364
 
268
365
  To verify whether or not a state machine is in a given state, **FiniteMachine** uses `is?` method. It returns `true` if the machine is found to be in the given state, or `false` otherwise.
269
366
 
@@ -279,9 +376,47 @@ fm.red? # => true
279
376
  fm.yellow? # => false
280
377
  ```
281
378
 
282
- ### 1.5 can? and cannot?
379
+ ### 2.7 trigger
380
+
381
+ Transition events can be fired by calling the `trigger` method with the event name and remaining arguments as data. The return value is either `true` or `false` depending whether the transition succeeded or not:
382
+
383
+ ```ruby
384
+ fm.trigger(:ready) # => true
385
+ fm.trigger(:ready, "one", "two", "three") # => true
386
+ ```
387
+
388
+ By default, the **FiniteMachine** automatically converts all the transition event names into methods:
389
+
390
+ ```ruby
391
+ fm.ready # => true
392
+ fm.ready("one", "two", "three") # => true
393
+ ```
394
+
395
+ Please see [States and Transitions](#3-states-and-transitions) for in-depth treatment of firing transitions.
283
396
 
284
- To verify whether or not an event can be fired, **FiniteMachine** provides `can?` or `cannot?` methods. `can?` checks if **FiniteMachine** can fire a given event, returning true, otherwise, it will return false. `cannot?` is simply the inverse of `can?`.
397
+
398
+ #### 2.7.1 `:auto_methods`
399
+
400
+ By default, all event names will be converted by **FiniteMachine** into method names. This also means that you won't be able to use event names such as `:fail` or `:trigger` as these are already defined on the machine instance. In situations when you wish to use any event name for your event names use `:auto_methods` keyword to disable automatic methods generation. For example, to define `:fail` event:
401
+
402
+ ```ruby
403
+ fm = FiniteMachine.new(auto_methods: false) do
404
+ initial :green
405
+
406
+ event :fail, :green => :red
407
+ end
408
+ ```
409
+
410
+ And then you can use `trigger` to fire the event:
411
+
412
+ ```ruby
413
+ fm.trigger(:fail)
414
+ fm.current # => :red
415
+ ```
416
+
417
+ ### 2.8 `can?` and `cannot?`
418
+
419
+ To verify whether or not an event can be fired, **FiniteMachine** provides `can?` or `cannot?` methods. `can?` checks if **FiniteMachine** can fire a given event, returning `true`, otherwise, it will return `false`. The `cannot?` is simply the inverse of `can?`.
285
420
 
286
421
  ```ruby
287
422
  fm.can?(:ready) # => true
@@ -293,14 +428,13 @@ fm.cannot?(:go) # => true
293
428
  The `can?` and `cannot?` helper methods take into account the `:if` and `:unless` conditions applied to events. The set of values that `:if` or `:unless` condition takes as block parameter can be passed in directly via `can?` and `cannot?` methods' arguments, after the name of the event. For instance,
294
429
 
295
430
  ```ruby
296
- fm = FiniteMachine.define do
431
+ fm = FiniteMachine.new do
297
432
  initial :green
298
433
 
299
- events {
300
- event :slow, :green => :yellow
301
- event :stop, :yellow => :red, if: proc { |_, param| :breaks == param }
302
- }
434
+ event :slow, :green => :yellow
435
+ event :stop, :yellow => :red, if: ->(_, param) { :breaks == param }
303
436
  end
437
+
304
438
  fm.can?(:slow) # => true
305
439
  fm.can?(:stop) # => false
306
440
 
@@ -309,95 +443,106 @@ fm.can?(:stop, :breaks) # => true
309
443
  fm.can?(:stop, :no_breaks) # => false
310
444
  ```
311
445
 
312
- ### 1.6 target
446
+ ### 2.9 target
313
447
 
314
- If you need to execute some external code in the context of the current state machine use `target` helper.
448
+ If you need to execute some external code in the context of the current state machine, pass that object as a first argument to `new` method.
449
+
450
+ Assuming we have a simple `Engine` class that holds an internal state whether the car's engine is on or off:
315
451
 
316
452
  ```ruby
317
- car = Car.new
453
+ class Engine
454
+ def initialize
455
+ @engine = false
456
+ end
318
457
 
319
- fm = FiniteMachine.define do
320
- initial :neutral
458
+ def turn_on
459
+ @engine = true
460
+ end
321
461
 
322
- target car
462
+ def turn_off
463
+ @engine = false
464
+ end
323
465
 
324
- events {
325
- event :start, :neutral => :one, if: "engine_on?"
326
- event :shift, :one => :two
327
- }
466
+ def engine_on?
467
+ @engine
468
+ end
328
469
  end
329
470
  ```
330
471
 
331
- Furthermore, the context created through `target` helper will allow you to reference and call methods from another object inside your callbacks. You can reference external context by calling `target`.
472
+ And given an instance of `Engine` class:
332
473
 
333
474
  ```ruby
334
- car = Car.new
475
+ engine = Engine.new
476
+ ```
335
477
 
336
- fm = FiniteMachine.define do
337
- initial :neutral
478
+ You can provide a context to a state machine by passing it as a first argument to a `new` call. You can then reference this context inside the callbacks by calling the `target` helper:
338
479
 
339
- target car
480
+ ```ruby
481
+ fm = FiniteMachine.new(engine) do
482
+ initial :neutral
340
483
 
341
- events {
342
- event :start, :neutral => :one, if: "engine_on?"
343
- }
484
+ event :start, :neutral => :one, unless: "engine_on?"
485
+ event :stop, :one => :neutral
344
486
 
345
- callbacks {
346
- on_enter_start do |event| target.turn_engine_on end
347
- on_exit_start do |event| target.turn_engine_off end
348
- }
487
+ on_before_start { |event| target.turn_on }
488
+ on_after_stop { |event| target.turn_off }
349
489
  end
350
490
  ```
351
491
 
352
- For more complex example see [Integration](#8-integration) section.
492
+ For more complex example see [Integration](#7-integration) section.
493
+
494
+ #### 2.9.1 `:alias_target`
353
495
 
354
- Finally, you can always reference an external context inside the **FiniteMachine** by simply calling `target`, for instance, to reference it inside a callback:
496
+ If you wish to better express the intention behind the context object, in particular when calling actions in callbacks, you can use the `:alias_target` option:
355
497
 
356
498
  ```ruby
357
- car = Car.new
499
+ engine = Engine.new
358
500
 
359
- fm = FiniteMachine.define do
501
+ fm = FiniteMachine.new(engine, alias_target: :engine) do
360
502
  initial :neutral
361
503
 
362
- target car
504
+ event :start, :neutral => :one, unless: "engine_on?"
505
+ event :stop, :none => :neutral, if: "engine_on?"
363
506
 
364
- events {
365
- event :start, :neutral => :one, if: "engine_on?"
366
- }
367
- callbacks {
368
- on_enter_start do |event|
369
- target.turn_engine_on
370
- end
371
- }
507
+ on_before_start { |event| engine.turn_on }
508
+ on_after_stop { |event| engine.turn_off }
372
509
  end
373
510
  ```
374
511
 
375
- ### 1.7 Alias target
376
-
377
- If you need to better express the intention behind the target name, in particular when calling actions in callbacks, you can use the `alias_target` helper:
512
+ Alternatively, you can use the `alias_target` helper method:
378
513
 
379
514
  ```ruby
380
- car = Car.new
515
+ engine = Engine.new
516
+
517
+ Car = FiniteMachine.define do
518
+ alias_target :engine
381
519
 
382
- fm = FiniteMachine.define do
383
520
  initial :neutral
384
521
 
385
- target car
522
+ event :start, :neutral => :one, if: "engine_on?"
523
+ event :stop, :none => :neutral, if: "engine_on?"
386
524
 
387
- alias_target :car
525
+ on_before_start { |event| engine.turn_on }
526
+ on_after_stop { |event| engine.turn_off }
527
+ end
528
+ ```
388
529
 
389
- events {
390
- event :start, :neutral => :one, if: "engine_on?"
391
- }
530
+ Then to link `Car` definition with `Engine` instance, pass the `Engine` instance as a first argument:
392
531
 
393
- callbacks {
394
- on_enter_start do |event| car.turn_engine_on end
395
- on_exit_start do |event| car.turn_engine_off end
396
- }
397
- end
532
+ ```ruby
533
+ car = Car.new(engine)
398
534
  ```
399
535
 
400
- ### 1.8 restore!
536
+ Triggering `start` event will change `Engine` instance state from `false` to `true`:
537
+
538
+ ```ruby
539
+ engine.engine_on? # => false
540
+ car.start
541
+ car.current # => :one
542
+ engine.engine_on? # => true
543
+ ```
544
+
545
+ ### 2.10 restore!
401
546
 
402
547
  In order to set the machine to a given state and thus skip triggering callbacks use the `restore!` method:
403
548
 
@@ -407,7 +552,7 @@ fm.restore!(:neutral)
407
552
 
408
553
  This method may be suitable when used testing your state machine or in restoring the state from datastore.
409
554
 
410
- ### 1.9 states
555
+ ### 2.11 states
411
556
 
412
557
  You can use the `states` method to return an array of all the states for a given state machine.
413
558
 
@@ -415,25 +560,25 @@ You can use the `states` method to return an array of all the states for a given
415
560
  fm.states # => [:none, :green, :yellow, :red]
416
561
  ```
417
562
 
418
- ### 1.10 event names
563
+ ### 2.12 events
419
564
 
420
- To find out all the event names supported by the state machine issue `event_names` method:
565
+ To find out all the event names supported by the state machine issue `events` method:
421
566
 
422
567
  ```ruby
423
- fm.event_names # => [:init, :ready, :go, :stop]
568
+ fm.events # => [:init, :ready, :go, :stop]
424
569
  ```
425
570
 
426
- ## 2 Transitions
571
+ ## 3. States and Transitions
427
572
 
428
- The `events` scope exposes the `event` helper to define possible state transitions.
573
+ The **FiniteMachine** DSL exposes the `event` helper to define possible state transitions.
429
574
 
430
- The `event` helper accepts as a first parameter the name which will later be used to create
431
- method on the **FiniteMachine** instance. As a second parameter `event` accepts an arbitrary number of states either
575
+ The `event` helper accepts as a first argument the transition's name which will later be used to create
576
+ method on the **FiniteMachine** instance. As a second argument the `event` accepts an arbitrary number of states either
432
577
  in the form of `:from` and `:to` hash keys or by using the state names themselves as key value pairs.
433
578
 
434
579
  ```ruby
435
580
  event :start, from: :neutral, to: :first
436
- or
581
+ # or
437
582
  event :start, :neutral => :first
438
583
  ```
439
584
 
@@ -444,7 +589,9 @@ The following methods trigger transitions for the example state machine.
444
589
  * go
445
590
  * stop
446
591
 
447
- ### 2.1 Performing transitions
592
+ You can always opt out from automatic method generation by using [:auto_methods](#271-auto_methods) option.
593
+
594
+ ### 3.1 Triggering transitions
448
595
 
449
596
  In order to transition to the next reachable state, simply call the event's name on the **FiniteMachine** instance. If the transition succeeds the `true` value is returned, otherwise `false`.
450
597
 
@@ -462,15 +609,15 @@ fm.trigger(:ready) # => true
462
609
  Furthermore, you can pass additional parameters with the method call that will be available in the triggered callback as well as used by any present guarding conditions.
463
610
 
464
611
  ```ruby
465
- fm.go('Piotr!') # => true
612
+ fm.go("Piotr!") # => true
466
613
  fm.current # => :green
467
614
  ```
468
615
 
469
616
  By default **FiniteMachine** will swallow all exceptions when and return `false` on failure. If you prefer to be notified when illegal transition occurs see [Dangerous transitions](#22-dangerous-transitions).
470
617
 
471
- ### 2.2 Dangerous transitions
618
+ ### 3.2 Dangerous transitions
472
619
 
473
- When you declare event, for instance `ready`, the **FiniteMachine** will provide a dangerous version with a bang `ready!`. In the case when you attempt to perform illegal transition or **FiniteMachine** throws internall error, the state machine will propagate the errors. You can use handlers to decide how to handle errors on case by case basis see [6. Errors](#6-errors)
620
+ When you declare event, for instance `ready`, the **FiniteMachine** will provide a dangerous version with a bang `ready!`. In the case when you attempt to perform illegal transition or **FiniteMachine** throws internal error, the state machine will propagate the errors. You can use handlers to decide how to handle errors on case by case basis see [6. Error Handling](#6-errors)
474
621
 
475
622
  ```ruby
476
623
  fm.ready! # => raises FiniteMachine::InvalidStateError
@@ -482,63 +629,36 @@ If you prefer you can also use `trigger!` method to fire event:
482
629
  fm.trigger!(:ready)
483
630
  ```
484
631
 
485
- ### 2.3 Asynchronous transitions
486
-
487
- By default the transitions will be fired synchronosuly.
488
-
489
- ```ruby
490
- fm.ready
491
- or
492
- fm.sync.ready
493
- fm.current # => :yellow
494
- ```
495
-
496
- In order to fire the event transition asynchronously use the `async` scope like so
497
-
498
- ```ruby
499
- fm.async.ready('Piotr') # => executes in separate Thread
500
- ```
501
-
502
- The `async` call allows for alternative syntax whereby the method name is passed as one of the parameters like so:
503
-
504
- ```ruby
505
- fm.async(:ready, 'Piotr')
506
- ```
507
-
508
- ### 2.4 Multiple from states
632
+ ### 3.3 Multiple from states
509
633
 
510
634
  If an event transitions from multiple states to the same state then all the states can be grouped into an array.
511
635
  Alternatively, you can create separate events under the same name for each transition that needs combining.
512
636
 
513
637
  ```ruby
514
- fm = FiniteMachine.define do
638
+ fm = FiniteMachine.new do
515
639
  initial :neutral
516
640
 
517
- events {
518
- event :start, :neutral => :one
519
- event :shift, :one => :two
520
- event :shift, :two => :three
521
- event :shift, :three => :four
522
- event :slow, [:one, :two, :three] => :one
523
- }
641
+ event :start, :neutral => :one
642
+ event :shift, :one => :two
643
+ event :shift, :two => :three
644
+ event :shift, :three => :four
645
+ event :slow, [:one, :two, :three] => :one
524
646
  end
525
647
  ```
526
648
 
527
- ### 2.5 From :any state
649
+ ### 3.4 `any_state` transitions
528
650
 
529
- The **FiniteMachine** offers few ways to transition out of any state. This is parrticularly useful when the machine already defines many states.
651
+ The **FiniteMachine** offers few ways to transition out of any state. This is particularly useful when the machine already defines many states.
530
652
 
531
- You can pass `:any` for the name of the state, for instance:
653
+ You can use `any_state` as the name for a given state, for instance:
532
654
 
533
655
  ```ruby
534
- event :run, from: :any, to: :green
535
-
536
- or
537
-
538
- event :run, :any => :green
656
+ event :run, from: any_state, to: :green
657
+ # or
658
+ event :run, any_state => :green
539
659
  ```
540
660
 
541
- Alternatively, you can skip the `:any` state definition and just specify `to` state:
661
+ Alternatively, you can skip the `any_state` call and just specify `to` state:
542
662
 
543
663
  ```ruby
544
664
  event :run, to: :green
@@ -546,78 +666,73 @@ event :run, to: :green
546
666
 
547
667
  All the above `run` event definitions will always transition the state machine into `:green` state.
548
668
 
549
- ### 2.6 Grouping states under single event
669
+ ### 3.5 Collapsing transitions
550
670
 
551
671
  Another way to specify state transitions under single event name is to group all your state transitions into a single hash like so:
552
672
 
553
673
  ```ruby
554
- fm = FiniteMachine.define do
674
+ fm = FiniteMachine.new do
555
675
  initial :initial
556
676
 
557
- events {
558
- event :bump, :initial => :low,
559
- :low => :medium,
560
- :medium => :high
561
- }
677
+ event :bump, :initial => :low,
678
+ :low => :medium,
679
+ :medium => :high
680
+ end
562
681
  ```
563
682
 
564
683
  The same can be more naturally rewritten also as:
565
684
 
566
685
  ```ruby
567
- fm = FiniteMachine.define do
686
+ fm = FiniteMachine.new do
568
687
  initial :initial
569
688
 
570
- events {
571
- event :bump, :initial => :low
572
- event :bump, :low => :medium
573
- event :bump, :medium => :high
574
- }
689
+ event :bump, :initial => :low
690
+ event :bump, :low => :medium
691
+ event :bump, :medium => :high
692
+ end
575
693
  ```
576
694
 
577
- ### 2.7 Silent transitions
695
+ ### 3.6 Silent transitions
578
696
 
579
697
  The **FiniteMachine** allows to selectively silence events and thus prevent any callbacks from firing. Using the `silent` option passed to event definition like so:
580
698
 
581
699
  ```ruby
582
- fm = FiniteMachine.define do
700
+ fm = FiniteMachine.new do
583
701
  initial :yellow
584
702
 
585
- events {
586
- event :go :yellow => :green, silent: true
587
- event :stop, :green => :red
588
- }
703
+ event :go :yellow => :green, silent: true
704
+ event :stop, :green => :red
589
705
  end
590
706
 
591
- fsm.go # no callbacks
592
- fms.stop # callbacks are fired
707
+ fm.go # no callbacks
708
+ fm.stop # callbacks are fired
593
709
  ```
594
710
 
595
- ### 2.8 Log transitions
711
+ ### 3.7 Logging transitions
596
712
 
597
713
  To help debug your state machine, **FiniteMachine** provides `:log_transitions` option.
598
714
 
599
715
  ```ruby
600
- FiniteMachine.define log_transitions: true do
716
+ FiniteMachine.new(log_transitions: true) do
601
717
  ...
602
718
  end
603
719
  ```
604
720
 
605
- ## 3 Conditional transitions
721
+ ### 3.8 Conditional transitions
606
722
 
607
723
  Each event takes an optional `:if` and `:unless` options which act as a predicate for the transition. The `:if` and `:unless` can take a symbol, a string, a Proc or an array. Use `:if` option when you want to specify when the transition **should** happen. If you want to specify when the transition **should not** happen then use `:unless` option.
608
724
 
609
- ### 3.1 Using a Proc
725
+ #### 3.8.1 Using a Proc
610
726
 
611
727
  You can associate the `:if` and `:unless` options with a Proc object that will get called right before transition happens. Proc object gives you ability to write inline condition instead of separate method.
612
728
 
613
729
  ```ruby
614
- fm = FiniteMachine.define do
730
+ fm = FiniteMachine.new do
615
731
  initial :green
616
732
 
617
- events {
618
- event :slow, :green => :yellow, if: -> { return false }
619
- }
733
+ event :slow, :green => :yellow, if: -> { return false }
620
734
  end
735
+
621
736
  fm.slow # doesn't transition to :yellow state
622
737
  fm.current # => :green
623
738
  ```
@@ -625,14 +740,13 @@ fm.current # => :green
625
740
  Condition by default receives the current context, which is the current state machine instance, followed by extra arguments.
626
741
 
627
742
  ```ruby
628
- fsm = FiniteMachine.define do
743
+ fm = FiniteMachine.new do
629
744
  initial :red
630
745
 
631
- events {
632
- event :go, :red => :green,
633
- if: -> (context, a) { context.current == a }
634
- }
746
+ event :go, :red => :green,
747
+ if: ->(context, a) { context.current == a }
635
748
  end
749
+
636
750
  fm.go(:yellow) # doesn't transition
637
751
  fm.go # raises ArgumentError
638
752
  ```
@@ -640,263 +754,235 @@ fm.go # raises ArgumentError
640
754
  **Note** If you specify condition with a given number of arguments then you need to call an event with the exact number of arguments, otherwise you will get `ArgumentError`. Thus in above scenario to prevent errors specify condition like so:
641
755
 
642
756
  ```ruby
643
- if: -> (context, *args) { ... }
757
+ if: ->(context, *args) { ... }
644
758
  ```
645
759
 
646
760
  Provided your **FiniteMachine** is associated with another object through `target` helper. Then the target object together with event arguments will be passed to the `:if` or `:unless` condition scope.
647
761
 
648
762
  ```ruby
649
- class Car
650
- attr_accessor :engine_on
763
+ class Engine
764
+ def initialize
765
+ @engine = false
766
+ end
651
767
 
652
- def turn_engine_on
653
- @engine_on = true
768
+ def turn_on
769
+ @engine = true
654
770
  end
655
771
 
656
- def turn_engine_off
657
- @engine_on = false
772
+ def turn_off
773
+ @engine = false
658
774
  end
659
775
 
660
776
  def engine_on?
661
- @engine_on
777
+ @engine
662
778
  end
663
779
  end
664
780
 
665
- car = Car.new
666
- car.turn_engine_on
781
+ engine = Engine.new
782
+ engine.turn_on
667
783
 
668
- fm = FiniteMachine.define do
784
+ car = FiniteMachine.new(engine) do
669
785
  initial :neutral
670
786
 
671
- target car
672
-
673
- events {
674
- event :start, :neutral => :one, if: -> (target, state) {
675
- target.engine_on = state
676
- target.engine_on?
677
- }
678
- }
787
+ event :start, :neutral => :one, if: ->(target, state) do
788
+ state ? target.engine_on : target.engine_off
789
+ end
679
790
  end
680
791
 
681
792
  fm.start(false)
682
- fm.current # => :neutral
793
+ fm.current # => :neutral
794
+ engine.engine_on? # => false
795
+
683
796
  fm.start(true)
684
- fm.current # => :one
797
+ fm.current # => :one
798
+ engine.engine_on? # => true
685
799
  ```
686
800
 
687
- When the one-liner conditions are not enough for your needs, you can perform conditional logic inside the callbacks. See [5.10 Cancelling inside callbacks](#510-cancelling-inside-callbacks)
801
+ When the one-liner conditions are not enough for your needs, you can perform conditional logic inside the callbacks. See [4.9 Cancelling callbacks](#49-cancelling-inside-callbacks)
688
802
 
689
- ### 3.2 Using a Symbol
803
+ #### 3.8.2 Using a Symbol
690
804
 
691
805
  You can also use a symbol corresponding to the name of a method that will get called right before transition happens.
692
806
 
693
807
  ```ruby
694
- fsm = FiniteMachine.define do
808
+ fm = FiniteMachine.new(engine) do
695
809
  initial :neutral
696
810
 
697
- target car
698
-
699
- events {
700
- event :start, :neutral => :one, if: :engine_on?
701
- }
811
+ event :start, :neutral => :one, if: :engine_on?
702
812
  end
703
813
  ```
704
814
 
705
- ### 3.3 Using a String
815
+ #### 3.8.3 Using a String
706
816
 
707
817
  Finally, it's possible to use string that will be evaluated using `eval` and needs to contain valid Ruby code. It should only be used when the string represents a short condition.
708
818
 
709
819
  ```ruby
710
- fsm = FiniteMachine.define do
820
+ fm = FiniteMachine.new(engine) do
711
821
  initial :neutral
712
822
 
713
- target car
714
-
715
- events {
716
- event :start, :neutral => :one, if: "engine_on?"
717
- }
823
+ event :start, :neutral => :one, if: "engine_on?"
718
824
  end
719
825
  ```
720
826
 
721
- ### 3.4 Combining transition conditions
827
+ #### 3.8.4 Combining transition conditions
722
828
 
723
829
  When multiple conditions define whether or not a transition should happen, an Array can be used. Furthermore, you can apply both `:if` and `:unless` to the same transition.
724
830
 
725
831
  ```ruby
726
- fsm = FiniteMachine.define do
832
+ fm = FiniteMachine.new do
727
833
  initial :green
728
834
 
729
- events {
730
- event :slow, :green => :yellow,
731
- if: [ -> { return true }, -> { return true} ],
732
- unless: -> { return true }
733
- event :stop, :yellow => :red
734
- }
835
+ event :slow, :green => :yellow,
836
+ if: [ -> { return true }, -> { return true} ],
837
+ unless: -> { return true }
838
+ event :stop, :yellow => :red
735
839
  end
736
840
  ```
737
841
 
738
842
  The transition only runs when all the `:if` conditions and none of the `unless` conditions are evaluated to `true`.
739
843
 
740
- ## 4 Choice pseudostates
844
+ ### 3.9 Choice pseudostates
741
845
 
742
- Choice pseudostate allows you to implement conditional branch. The conditions of an event's transitions are evaluated in order to to select only one outgoing transition.
846
+ Choice pseudostate allows you to implement conditional branch. The conditions of an event's transitions are evaluated in order to select only one outgoing transition.
743
847
 
744
848
  You can implement the conditional branch as ordinary events grouped under the same name and use familiar `:if/:unless` conditions:
745
849
 
746
850
  ```ruby
747
- fsm = FiniteMachine.define do
851
+ fm = FiniteMachine.define do
748
852
  initial :green
749
853
 
750
- events {
751
- event :next, :green => :yellow, if: -> { false }
752
- event :next, :green => :red, if: -> { true }
753
- }
854
+ event :next, :green => :yellow, if: -> { false }
855
+ event :next, :green => :red, if: -> { true }
754
856
  end
755
857
 
756
- fsm.current # => :green
757
- fsm.next
758
- fsm.current # => :red
858
+ fm.current # => :green
859
+ fm.next
860
+ fm.current # => :red
759
861
  ```
760
862
 
761
863
  The same conditional logic can be implemented using much shorter and more descriptive style using `choice` method:
762
864
 
763
865
  ```ruby
764
- fsm = FiniteMachine.define do
866
+ fm = FiniteMachine.new do
765
867
  initial :green
766
868
 
767
- events {
768
- event :next, from: :green do
769
- choice :yellow, if: -> { false }
770
- choice :red, if: -> { true }
771
- end
772
- }
869
+ event :next, from: :green do
870
+ choice :yellow, if: -> { false }
871
+ choice :red, if: -> { true }
872
+ end
773
873
  end
774
874
 
775
- fsm.current # => :green
776
- fsm.next
777
- fsm.current # => :red
875
+ fm.current # => :green
876
+ fm.next
877
+ fm.current # => :red
778
878
  ```
779
879
 
780
- ### 4.1 Dynamic choice conditions
880
+ #### 3.9.1 Dynamic choice conditions
781
881
 
782
882
  Just as with event conditions you can make conditional logic dynamic and dependent on parameters passed in:
783
883
 
784
884
  ```ruby
785
- fsm = FiniteMachine.define do
885
+ fm = FiniteMachine.new do
786
886
  initial :green
787
887
 
788
- events {
789
- event :next, from: :green do
790
- choice :yellow, if: -> (context, a) { a < 1 }
791
- choice :red, if: -> (context, a) { a > 1 }
792
- default :red
793
- end
794
- }
888
+ event :next, from: :green do
889
+ choice :yellow, if: ->(context, a) { a < 1 }
890
+ choice :red, if: ->(context, a) { a > 1 }
891
+ default :red
892
+ end
795
893
  end
796
894
 
797
- fsm.current # => :green
798
- fsm.next(0)
799
- fsm.current # => :yellow
895
+ fm.current # => :green
896
+ fm.next(0)
897
+ fm.current # => :yellow
800
898
  ```
801
899
 
802
900
  If more than one of the conditions evaluates to true, a first matching one is chosen. If none of the conditions evaluate to true, then the `default` state is matched. However if default state is not present and non of the conditions match, no transition is performed. To avoid such situation always specify `default` choice.
803
901
 
804
- ### 4.2 Multiple from states
902
+ #### 3.9.2 Multiple from states
805
903
 
806
904
  Similarly to event definitions, you can specify the event to transition from a group of states:
807
905
 
808
906
  ```ruby
809
- FiniteMachine.define do
907
+ FiniteMachine.new do
810
908
  initial :red
811
909
 
812
- events {
813
- event :next, from: [:yellow, :red] do
814
- choice :pink, if: -> { false }
815
- choice :green
816
- end
817
- }
910
+ event :next, from: [:yellow, :red] do
911
+ choice :pink, if: -> { false }
912
+ choice :green
913
+ end
818
914
  end
819
915
  ```
820
916
 
821
- or from any state using the `:any` state name like so:
917
+ Or from any state using the `:any` state name like so:
822
918
 
823
919
  ```ruby
824
- FiniteMachine.define do
920
+ FiniteMachine.new do
825
921
  initial :red
826
922
 
827
- events {
828
- event :next, from: :any do
829
- choice :pink, if: -> { false }
830
- choice :green
831
- end
832
- }
923
+ event :next, from: :any do
924
+ choice :pink, if: -> { false }
925
+ choice :green
926
+ end
833
927
  end
834
928
  ```
835
929
 
836
- ## 5 Callbacks
837
-
838
- You can watch state machine events and the information they provide by registering one or more predefined callback types. The following 5 types of callbacks are available in **FiniteMachine**:
930
+ ## 4. Callbacks
839
931
 
840
- * `on_enter`
841
- * `on_transition`
842
- * `on_exit`
843
- * `on_before`
844
- * `on_after`
932
+ You can register a callback to listen for state transitions and events triggered, and based on these perform custom actions. There are five callbacks available in **FiniteMachine**:
845
933
 
846
- Use the `callbacks` scope to introduce the listeners. You can register a callback to listen for state changes or events being triggered.
934
+ * `on_before` - triggered before any transition
935
+ * `on_exit` - triggered when leaving any state
936
+ * `on_transition` - triggered during any transition
937
+ * `on_enter` - triggered when entering any state
938
+ * `on_after` - triggered after any transition
847
939
 
848
940
  Use the state or event name as a first parameter to the callback helper followed by block with event argument and a list arguments that you expect to receive like so:
849
941
 
850
942
  ```ruby
851
- on_enter :green { |event, a, b, c| ... }
943
+ on_enter(:green) { |event, a, b, c| ... }
852
944
  ```
853
945
 
854
946
  When you subscribe to the `:green` state change, the callback will be called whenever someone triggers event that transitions in or out of that state. The same will happen on subscription to event `ready`, namely, the callback will be called each time the state transition method is triggered regardless of the states it transitions from or to.
855
947
 
856
948
  ```ruby
857
- fm = FiniteMachine.define do
949
+ fm = FiniteMachine.new do
858
950
  initial :red
859
951
 
860
- events {
861
- event :ready, :red => :yellow
862
- event :go, :yellow => :green
863
- event :stop, :green => :red
864
- }
865
-
866
- callbacks {
867
- on_before :ready { |event, time1, time2, time3| puts "#{time1} #{time2} #{time3} Go!" }
868
- on_before :go { |event, name| puts "Going fast #{name}" }
869
- on_before :stop { |event| ... }
870
- }
952
+ event :ready, :red => :yellow
953
+ event :go, :yellow => :green
954
+ event :stop, :green => :red
955
+
956
+ on_before :ready do |event, time1, time2, time3|
957
+ puts "#{time1} #{time2} #{time3} Go!" }
958
+ end
959
+ on_before :go do |event, name|
960
+ puts "Going fast #{name}"
961
+ end
962
+ on_before(:stop) { |event| ... }
871
963
  end
872
964
 
873
965
  fm.ready(1, 2, 3)
874
- fm.go('Piotr!')
966
+ fm.go("Piotr!")
875
967
  ```
876
968
 
877
969
  **Note** Regardless of how the state is entered or exited, all the associated callbacks will be executed. This provides means for guaranteed initialization and cleanup.
878
970
 
879
- ### 5.1 on_enter
971
+ ### 4.1 on_(enter|transition|exit)
880
972
 
881
973
  The `on_enter` callback is executed before given state change is fired. By passing state name you can narrow down the listener to only watch out for enter state changes. Otherwise, all enter state changes will be watched.
882
974
 
883
- ### 5.2 on_transition
884
-
885
975
  The `on_transition` callback is executed when given state change happens. By passing state name you can narrow down the listener to only watch out for transition state changes. Otherwise, all transition state changes will be watched.
886
976
 
887
- ### 5.3 on_exit
888
-
889
977
  The `on_exit` callback is executed after a given state change happens. By passing state name you can narrow down the listener to only watch out for exit state changes. Otherwise, all exit state changes will be watched.
890
978
 
891
- ### 5.4 on_before
979
+ ### 4.2 on_(before|after)
892
980
 
893
981
  The `on_before` callback is executed before a given event happens. By default it will listen out for all events, you can also listen out for specific events by passing event's name.
894
982
 
895
- ### 5.5 on_after
896
-
897
983
  This callback is executed after a given event happened. By default it will listen out for all events, you can also listen out for specific events by passing event's name.
898
984
 
899
- ### 5.6 once_on
985
+ ### 4.3 once_on
900
986
 
901
987
  **FiniteMachine** allows you to listen on initial state change or when the event is fired first time by using the following 5 types of callbacks:
902
988
 
@@ -906,7 +992,7 @@ This callback is executed after a given event happened. By default it will liste
906
992
  * `once_before`
907
993
  * `once_after`
908
994
 
909
- ### 5.7 Execution sequence
995
+ ### 4.4 Execution sequence
910
996
 
911
997
  Assuming we have the following event specified:
912
998
 
@@ -914,92 +1000,90 @@ Assuming we have the following event specified:
914
1000
  event :go, :red => :yellow
915
1001
  ```
916
1002
 
917
- then by calling `go` event the following callbacks in the following sequence will be executed:
1003
+ Then by calling `go` event the following callbacks sequence will be executed:
918
1004
 
919
1005
  * `on_before` - generic callback before `any` event
920
1006
  * `on_before :go` - callback before the `go` event
921
- * `on_exit :red` - callback for the `:red` state exit
922
1007
  * `on_exit` - generic callback for exit from `any` state
923
- * `on_transition :yellow` - callback for the `:red` to `:yellow` transition
1008
+ * `on_exit :red` - callback for the `:red` state exit
924
1009
  * `on_transition` - callback for transition from `any` state to `any` state
925
- * `on_enter :yellow` - callback for the `:yellow` state entry
1010
+ * `on_transition :yellow` - callback for the `:red` to `:yellow` transition
926
1011
  * `on_enter` - generic callback for entry to `any` state
927
- * `on_after :go` - callback after the `go` event
1012
+ * `on_enter :yellow` - callback for the `:yellow` state entry
928
1013
  * `on_after` - generic callback after `any` event
1014
+ * `on_after :go` - callback after the `go` event
929
1015
 
930
- ### 5.8 Parameters
1016
+ ### 4.5 Callback parameters
931
1017
 
932
- All callbacks get the `TransitionEvent` object with the following attributes.
1018
+ All callbacks as a first argument yielded to a block receive the `TransitionEvent` object with the following attributes:
933
1019
 
934
- * `name # the event name`
935
- * `from # the state transitioning from`
936
- * `to # the state transitioning to`
1020
+ * `name` - the event name`
1021
+ * `from` - the state transitioning from`
1022
+ * `to` - the state transitioning to`
937
1023
 
938
1024
  followed by the rest of arguments that were passed to the event method.
939
1025
 
940
1026
  ```ruby
941
- fm = FiniteMachine.define do
1027
+ fm = FiniteMachine.new do
942
1028
  initial :red
943
1029
 
944
- events {
945
- event :ready, :red => :yellow
946
- }
1030
+ event :ready, :red => :yellow
947
1031
 
948
- callbacks {
949
- on_enter_ready { |event, time|
950
- puts "lights switching from #{event.from} to #{event.to} in #{time} seconds"
951
- }
952
- }
1032
+ on_before_ready do |event, time|
1033
+ puts "lights switching from #{event.from} to #{event.to} in #{time} seconds"
1034
+ end
953
1035
  end
954
1036
 
955
- fm.ready(3) # => 'lights switching from red to yellow in 3 seconds'
1037
+ fm.ready(3)
1038
+ # => "lights switching from red to yellow in 3 seconds"
956
1039
  ```
957
1040
 
958
- ### 5.9 Same kind of callbacks
1041
+ ### 4.6 Duplicate callbacks
959
1042
 
960
1043
  You can define any number of the same kind of callback. These callbacks will be executed in the order they are specified.
961
1044
 
1045
+ Given the following state machine instance:
1046
+
962
1047
  ```ruby
963
- fm = FiniteMachine.define do
1048
+ fm = FiniteMachine.new do
964
1049
  initial :green
965
1050
 
966
- events {
967
- event :slow, :green => :yellow
968
- }
1051
+ event :slow, :green => :yellow
969
1052
 
970
- callbacks {
971
- on_enter(:yellow) { this_is_run_first }
972
- on_enter(:yellow) { then_this }
973
- }
1053
+ on_enter(:yellow) { puts "this is run first" }
1054
+ on_enter(:yellow) { puts "then this is run" }
974
1055
  end
975
- fm.slow # => will invoke both callbacks
976
1056
  ```
977
1057
 
978
- ### 5.10 Fluid callbacks
1058
+ Triggerring the `:slow` event results in:
1059
+
1060
+ ```ruby
1061
+ fm.slow
1062
+ # => "this is run first"
1063
+ # => "then this is run"
1064
+ ```
1065
+
1066
+ ### 4.7 Fluid callbacks
979
1067
 
980
- Callbacks can also be specified as full method calls.
1068
+ Callbacks can also be specified as full method calls separated with underscores:
981
1069
 
982
1070
  ```ruby
983
1071
  fm = FiniteMachine.define do
984
1072
  initial :red
985
1073
 
986
- events {
987
- event :ready, :red => :yellow
988
- event :go, :yellow => :green
989
- event :stop, :green => :red
990
- }
991
-
992
- callbacks {
993
- on_before_ready { |event| ... }
994
- on_before_go { |event| ... }
995
- on_before_stop { |event| ... }
996
- }
1074
+ event :ready, :red => :yellow
1075
+ event :go, :yellow => :green
1076
+ event :stop, :green => :red
1077
+
1078
+ on_before_ready { |event| ... }
1079
+ on_before_go { |event| ... }
1080
+ on_before_stop { |event| ... }
997
1081
  end
998
1082
  ```
999
1083
 
1000
- ### 5.11 Executing methods inside callbacks
1084
+ ### 4.8 Methods inside callbacks
1001
1085
 
1002
- In order to execute method from another object use `target` helper.
1086
+ Given a class `Car`:
1003
1087
 
1004
1088
  ```ruby
1005
1089
  class Car
@@ -1013,111 +1097,118 @@ class Car
1013
1097
  @reverse_lights = true
1014
1098
  end
1015
1099
  end
1100
+ ```
1016
1101
 
1102
+ We can easily manipulate state for an instance of a `Car` class:
1103
+
1104
+ ```ruby
1017
1105
  car = Car.new
1106
+ ```
1018
1107
 
1019
- fm = FiniteMachine.define do
1020
- initial :neutral
1108
+ By defining finite machine using the instance:
1021
1109
 
1022
- target car
1110
+ ```ruby
1111
+ fm = FiniteMachine.new(car) do
1112
+ initial :neutral
1023
1113
 
1024
- events {
1025
- event :forward, [:reverse, :neutral] => :one
1026
- event :back, [:neutral, :one] => :reverse
1027
- }
1114
+ event :forward, [:reverse, :neutral] => :one
1115
+ event :back, [:neutral, :one] => :reverse
1028
1116
 
1029
- callbacks {
1030
- on_enter_reverse { |event| target.turn_reverse_lights_on }
1031
- on_exit_reverse { |event| target.turn_reverse_lights_off }
1032
- }
1117
+ on_enter_reverse { |event| target.turn_reverse_lights_on }
1118
+ on_exit_reverse { |event| target.turn_reverse_lights_off }
1033
1119
  end
1034
1120
  ```
1035
1121
 
1036
1122
  Note that you can also fire events from callbacks.
1037
1123
 
1038
1124
  ```ruby
1039
- fm = FiniteMachine.define do
1125
+ fm = FiniteMachine.new do
1040
1126
  initial :neutral
1041
1127
 
1042
- events {
1043
- event :forward, [:reverse, :neutral] => :one
1044
- event :back, [:neutral, :one] => :reverse
1045
- }
1128
+ event :forward, [:reverse, :neutral] => :one
1129
+ event :back, [:neutral, :one] => :reverse
1046
1130
 
1047
- callbacks {
1048
- on_enter_reverse { |event| forward('Piotr!') }
1049
- on_exit_reverse { |event, name| puts "Go #{name}" }
1050
- }
1131
+ on_enter_reverse { |event| forward("Piotr!") }
1132
+ on_exit_reverse { |event, name| puts "Go #{name}" }
1051
1133
  end
1052
- fm.back # => Go Piotr!
1053
1134
  ```
1054
1135
 
1055
- For more complex example see [Integration](#8-integration) section.
1136
+ Then triggerring `:back` event gives:
1056
1137
 
1057
- ### 5.12 Defining callbacks
1138
+ ```ruby
1139
+ fm.back # => Go Piotr!
1140
+ ```
1141
+
1142
+ For more complex example see [Integration](#7-integration) section.
1143
+
1144
+ ### 4.9 Cancelling callbacks
1058
1145
 
1059
- When defining callbacks you are not limited to the `callbacks` helper. After **FiniteMachine** instance is created you can register callbacks the same way as before by calling `on` and supplying the type of notification and state/event you are interested in.
1146
+ A simple way to prevent transitions is to use [3 Conditional transitions](#3-conditional-transitions).
1147
+
1148
+ There are times when you want to cancel transition in a callback. For example, you have logic which allows transition to happen only under certain complex conditions. Using `cancel_event` inside the `on_(enter|transition|exit)` or `on_(before|after)` callbacks will stop all the callbacks from firing and prevent current transition from happening.
1149
+
1150
+ For example, the following state machine cancels any event leaving `:red` state:
1060
1151
 
1061
1152
  ```ruby
1062
- fm = FiniteMachine.define do
1153
+ fm = FiniteMachine.new do
1063
1154
  initial :red
1064
1155
 
1065
- events {
1066
- event :ready, :red => :yellow
1067
- event :go, :yellow => :green
1068
- event :stop, :green => :red
1069
- }
1070
- end
1156
+ event :ready, :red => :yellow
1157
+ event :go, :yellow => :green
1158
+ event :stop, :green => :red
1071
1159
 
1072
- fm.on_enter_yellow do |event|
1073
- ...
1160
+ on_exit :red do |event|
1161
+ ...
1162
+ cancel_event
1163
+ end
1074
1164
  end
1075
1165
  ```
1076
1166
 
1077
- ### 5.13 Asynchronous callbacks
1078
-
1079
- By default all callbacks are run synchronosuly. In order to add a callback that runs asynchronously, you need to pass second `:async` argument like so:
1167
+ Then firing `:ready` event will not transition out of the current `:red` state:
1080
1168
 
1081
1169
  ```ruby
1082
- on_enter :green, :async do |event| ... end
1170
+ fm.current # => :red
1171
+ fm.ready
1172
+ fm.current # => :red
1083
1173
  ```
1084
1174
 
1085
- or
1175
+ ### 4.10 Asynchronous callbacks
1176
+
1177
+ By default all callbacks are run synchronously. In order to add a callback that runs asynchronously, you need to pass second `:async` argument like so:
1086
1178
 
1087
1179
  ```ruby
1088
- on_enter_green(:async) { |event| }
1180
+ on_enter(:green, :async) do |event| ... end
1181
+ # or
1182
+ on_enter_green(:async) { |event| }
1089
1183
  ```
1090
1184
 
1091
- This will ensure that when the callback is fired it will run in seperate thread outside of the main execution thread.
1092
-
1093
- ### 5.14 Cancelling inside callbacks
1185
+ This will ensure that when the callback is fired it will run in separate thread outside of the main execution thread.
1094
1186
 
1095
- Preferred way to handle cancelling transitions is to use [3 Conditional transitions](#3-conditional-transitions). However if the logic is more than one liner you can cancel the event, hence the transition by returning `FiniteMachine::CANCELLED` constant from the callback scope. The two ways you can affect the event are
1187
+ ### 4.11 Instance callbacks
1096
1188
 
1097
- * `on_exit :state_name`
1098
- * `on_before :event_name`
1189
+ When defining callbacks you are not limited to the **FiniteMachine** block definition. After creating an instance, you can register callbacks the same way as before by calling `on` and supplying the type of notification and state/event you are interested in.
1099
1190
 
1100
- For example
1191
+ For example, given the following state machine:
1101
1192
 
1102
1193
  ```ruby
1103
- fm = FiniteMachine.define do
1194
+ fm = FiniteMachine.new do
1104
1195
  initial :red
1105
1196
 
1106
- events {
1107
- event :ready, :red => :yellow
1108
- event :go, :yellow => :green
1109
- event :stop, :green => :red
1110
- }
1111
- callbacks {
1112
- on_exit :red do |event| FiniteMachine::CANCELLED end
1113
- }
1197
+ event :ready, :red => :yellow
1198
+ event :go, :yellow => :green
1199
+ event :stop, :green => :red
1114
1200
  end
1201
+ ```
1115
1202
 
1116
- fm.ready
1117
- fm.current # => :red
1203
+ We can add callbacks as follows:
1204
+
1205
+ ```ruby
1206
+ fm.on_enter(:yellow) { |event| ... }
1207
+ # or
1208
+ fm.en_enter_yellow { |event| ... }
1118
1209
  ```
1119
1210
 
1120
- ## 6 Errors
1211
+ ## 5. Error Handling
1121
1212
 
1122
1213
  By default, the **FiniteMachine** will throw an exception whenever the machine is in invalid state or fails to transition.
1123
1214
 
@@ -1125,31 +1216,27 @@ By default, the **FiniteMachine** will throw an exception whenever the machine i
1125
1216
  * `FiniteMachine::InvalidStateError`
1126
1217
  * `FiniteMachine::InvalidCallbackError`
1127
1218
 
1128
- You can attach specific error handler inside the `handlers` scope by passing the name of the error and actual callback to be executed when the error happens inside the `handle` method. The `handle` receives a list of exception class or exception class names, and an option `:with` with a name of the method or a Proc object to be called to handle the error. As an alternative, you can pass a block.
1219
+ You can attach specific error handler using the 'handle' with the name of the error as a first argument and a callback to be executed when the error happens. The `handle` receives a list of exception class or exception class names, and an option `:with` with a name of the method or a Proc object to be called to handle the error. As an alternative, you can pass a block.
1129
1220
 
1130
1221
  ```ruby
1131
- fm = FiniteMachine.define do
1222
+ fm = FiniteMachine.new do
1132
1223
  initial :green, event: :start
1133
1224
 
1134
- events {
1135
- event :slow, :green => :yellow
1136
- event :stop, :yellow => :red
1137
- }
1225
+ event :slow, :green => :yellow
1226
+ event :stop, :yellow => :red
1138
1227
 
1139
- handlers {
1140
- handle FiniteMachine::InvalidStateError do |exception|
1141
- # run some custom logging
1142
- raise exception
1143
- end
1228
+ handle FiniteMachine::InvalidStateError do |exception|
1229
+ # run some custom logging
1230
+ raise exception
1231
+ end
1144
1232
 
1145
- handle FiniteMachine::TransitionError, with: proc { |exception| ... }
1146
- }
1233
+ handle FiniteMachine::TransitionError, with: -> { |exception| ... }
1147
1234
  end
1148
1235
  ```
1149
1236
 
1150
- ### 6.1 Using target
1237
+ ### 5.1 Using target
1151
1238
 
1152
- You can pass an external context via `target` helper that will be the receiver for the handler. The handler method needs to take one argument that will be called with the exception.
1239
+ You can pass an external context as a first argument to the **FiniteMachine** initialization that will be available as context in the handler block or `:with` value. For example, the `log_error` method is made available when `:with` option key is used:
1153
1240
 
1154
1241
  ```ruby
1155
1242
  class Logger
@@ -1158,27 +1245,21 @@ class Logger
1158
1245
  end
1159
1246
  end
1160
1247
 
1161
- fm = FiniteMachine.define do
1162
- target logger
1163
-
1248
+ fm = FiniteMachine.new(logger) do
1164
1249
  initial :green
1165
1250
 
1166
- events {
1167
- event :slow, :green => :yellow
1168
- event :stop, :yellow => :red
1169
- }
1251
+ event :slow, :green => :yellow
1252
+ event :stop, :yellow => :red
1170
1253
 
1171
- handlers {
1172
- handle 'InvalidStateError', with: :log_error
1173
- }
1254
+ handle "InvalidStateError", with: :log_error
1174
1255
  end
1175
1256
  ```
1176
1257
 
1177
- ## 7 Stand-Alone FiniteMachine
1258
+ ## 6. Stand-alone
1178
1259
 
1179
- **FiniteMachine** allows you to seperate your state machine from the target class so that you can keep your concerns broken in small maintainable pieces.
1260
+ **FiniteMachine** allows you to separate your state machine from the target class so that you can keep your concerns broken in small maintainable pieces.
1180
1261
 
1181
- ### 7.1 Creating a Definition
1262
+ ### 6.1 Creating a Definition
1182
1263
 
1183
1264
  You can turn a class into a **FiniteMachine** by simply subclassing `FiniteMachine::Definition`. As a rule of thumb, every single public method of the **FiniteMachine** is available inside your class:
1184
1265
 
@@ -1186,31 +1267,29 @@ You can turn a class into a **FiniteMachine** by simply subclassing `FiniteMachi
1186
1267
  class Engine < FiniteMachine::Definition
1187
1268
  initial :neutral
1188
1269
 
1189
- events {
1190
- event :forward, [:reverse, :neutral] => :one
1191
- event :shift, :one => :two
1192
- event :back, [:neutral, :one] => :reverse
1193
- }
1270
+ event :forward, [:reverse, :neutral] => :one
1271
+ event :shift, :one => :two
1272
+ event :back, [:neutral, :one] => :reverse
1194
1273
 
1195
- callbacks {
1196
- on_enter :reverse do |event|
1197
- target.turn_reverse_lights_on
1198
- end
1274
+ on_enter :reverse do |event|
1275
+ target.turn_reverse_lights_on
1276
+ end
1199
1277
 
1200
- on_exit :reverse do |event|
1201
- target.turn_reverse_lights_off
1202
- end
1203
- }
1278
+ on_exit :reverse do |event|
1279
+ target.turn_reverse_lights_off
1280
+ end
1204
1281
 
1205
- handlers {
1206
- handle FiniteMachine::InvalidStateError do |exception| ... end
1207
- }
1282
+ handle FiniteMachine::InvalidStateError do |exception|
1283
+ ...
1284
+ end
1208
1285
  end
1209
1286
  ```
1210
1287
 
1211
- ### 7.2 Targeting definition
1288
+ ### 6.2 Targeting definition
1212
1289
 
1213
- The next step is to instantiate your state machine and use `target` to load specific context.
1290
+ The next step is to instantiate your state machine and use a custom class instance to load specific context.
1291
+
1292
+ For example, having the following `Car` class:
1214
1293
 
1215
1294
  ```ruby
1216
1295
  class Car
@@ -1227,32 +1306,31 @@ class Car
1227
1306
  end
1228
1307
  end
1229
1308
  ```
1309
+
1230
1310
  Thus, to associate `Engine` to `Car` do:
1231
1311
 
1232
1312
  ```ruby
1233
1313
  car = Car.new
1234
- engine = Engine.new
1235
- engine.target car
1314
+ engine = Engine.new(car)
1236
1315
 
1237
1316
  car.reverse_lignts? # => false
1238
1317
  engine.back
1239
1318
  car.reverse_lights? # => true
1240
1319
  ```
1241
1320
 
1242
- Alternatively, create method inside the `Car` that will do the integration like so
1321
+ Alternatively, create method inside the `Car` that will do the integration like so:
1243
1322
 
1244
1323
  ```ruby
1245
1324
  class Car
1246
1325
  ... # as above
1326
+
1247
1327
  def engine
1248
- @engine ||= Engine.new
1249
- @engine.target(self)
1250
- @engine
1328
+ @engine ||= Engine.new(self)
1251
1329
  end
1252
1330
  end
1253
1331
  ```
1254
1332
 
1255
- ### 7.3 Definition inheritance
1333
+ ### 6.3 Definition inheritance
1256
1334
 
1257
1335
  You can create more specialised versions of a generic definition by using inheritance. Assuming a generic state machine definition:
1258
1336
 
@@ -1260,27 +1338,19 @@ You can create more specialised versions of a generic definition by using inheri
1260
1338
  class GenericStateMachine < FiniteMachine::Definition
1261
1339
  initial :red
1262
1340
 
1263
- events {
1264
- event :start, :red => :green
1265
- }
1341
+ event :start, :red => :green
1266
1342
 
1267
- callbacks {
1268
- on_enter { |event| ... }
1269
- }
1343
+ on_enter { |event| ... }
1270
1344
  end
1271
1345
  ```
1272
1346
 
1273
- we can easily create a more specifc definition that adds new event and more specifc callback to the mix.
1347
+ You can easily create a more specific definition that adds new events and more specific callbacks to the mix.
1274
1348
 
1275
1349
  ```ruby
1276
1350
  class SpecificStateMachine < GenericStateMachine
1277
- events {
1278
- event :stop, :green => :yellow
1279
- }
1351
+ event :stop, :green => :yellow
1280
1352
 
1281
- callbacks {
1282
- on_enter(:yellow) { |event| ... }
1283
- }
1353
+ on_enter(:yellow) { |event| ... }
1284
1354
  end
1285
1355
  ```
1286
1356
 
@@ -1288,16 +1358,15 @@ Finally to use the specific state machine definition do:
1288
1358
 
1289
1359
  ```ruby
1290
1360
  specific_fsm = SpecificStateMachine.new
1291
- specific_fsm.target ... # Target specific object
1292
1361
  ```
1293
1362
 
1294
- ## 8 Integration
1363
+ ## 7. Integration
1295
1364
 
1296
- Since **FiniteMachine** is an object in its own right, it leaves integration with other systems up to you. In contrast to other Ruby libraries, it does not extend from models (i.e. ActiveRecord) to transform them into a state machine or require mixing into exisiting classes.
1365
+ Since **FiniteMachine** is an object in its own right, it leaves integration with other systems up to you. In contrast to other Ruby libraries, it does not extend from models (i.e. ActiveRecord) to transform them into a state machine or require mixing into existing classes.
1297
1366
 
1298
- ### 8.1 Plain Ruby Objects
1367
+ ### 7.1 Plain Ruby Objects
1299
1368
 
1300
- In order to use **FiniteMachine** with an object, you need to define a method that will construct the state machine. You can implement the state machine using the `define` DSL or create a seperate object that can be instantiated. To complete integration you will need to specify `target` context to allow state machine to communicate with the other methods inside the class like so:
1369
+ In order to use **FiniteMachine** with an object, you need to define a method that will construct the state machine. You can implement the state machine using the `new` DSL or create a separate object that can be instantiated. To complete integration you will need to specify `target` context to allow state machine to communicate with the other methods inside the class like so:
1301
1370
 
1302
1371
  ```ruby
1303
1372
  class Car
@@ -1314,32 +1383,25 @@ class Car
1314
1383
  end
1315
1384
 
1316
1385
  def gears
1317
- context = self
1318
- @gears ||= FiniteMachine.define do
1386
+ @gears ||= FiniteMachine.new(self) do
1319
1387
  initial :neutral
1320
1388
 
1321
- target context
1322
-
1323
- events {
1324
- event :start, :neutral => :one
1325
- event :shift, :one => :two
1326
- event :shift, :two => :one
1327
- event :back, [:neutral, :one] => :reverse
1328
- }
1389
+ event :start, :neutral => :one
1390
+ event :shift, :one => :two
1391
+ event :shift, :two => :one
1392
+ event :back, [:neutral, :one] => :reverse
1329
1393
 
1330
- callbacks {
1331
- on_enter :reverse do |event|
1332
- target.turn_reverse_lights_on
1333
- end
1394
+ on_enter :reverse do |event|
1395
+ target.turn_reverse_lights_on
1396
+ end
1334
1397
 
1335
- on_exit :reverse do |event|
1336
- target.turn_reverse_lights_off
1337
- end
1398
+ on_exit :reverse do |event|
1399
+ target.turn_reverse_lights_off
1400
+ end
1338
1401
 
1339
- on_transition do |event|
1340
- puts "shifted from #{event.from} to #{event.to}"
1341
- end
1342
- }
1402
+ on_transition do |event|
1403
+ puts "shifted from #{event.from} to #{event.to}"
1404
+ end
1343
1405
  end
1344
1406
  end
1345
1407
  end
@@ -1359,7 +1421,7 @@ car.gears.current # => :reverse
1359
1421
  car.reverse_lights_on? # => true
1360
1422
  ```
1361
1423
 
1362
- ### 8.2 ActiveRecord
1424
+ ### 7.2 ActiveRecord
1363
1425
 
1364
1426
  In order to integrate **FiniteMachine** with ActiveRecord simply add a method with state machine definition. You can also define the state machine in separate module to aid reusability. Once the state machine is defined use the `target` helper to reference the current class. Having defined `target` you call ActiveRecord methods inside the callbacks to persist the state.
1365
1427
 
@@ -1383,22 +1445,15 @@ class Account < ActiveRecord::Base
1383
1445
  end
1384
1446
 
1385
1447
  def manage
1386
- context = self
1387
- @manage ||= FiniteMachine.define do
1388
- target context
1389
-
1448
+ @manage ||= FiniteMachine.new(self) do
1390
1449
  initial :unapproved
1391
1450
 
1392
- events {
1393
- event :enqueue, :unapproved => :pending
1394
- event :authorize, :pending => :access
1395
- }
1451
+ event :enqueue, :unapproved => :pending
1452
+ event :authorize, :pending => :access
1396
1453
 
1397
- callbacks {
1398
- on_enter do |event|
1399
- target.state = state
1400
- end
1401
- }
1454
+ on_enter do |event|
1455
+ target.state = event.to
1456
+ end
1402
1457
  end
1403
1458
  end
1404
1459
  end
@@ -1411,11 +1466,11 @@ account.manage.authorize
1411
1466
  account.state # => :access
1412
1467
  ```
1413
1468
 
1414
- Please note that you do not need to call `target.save` inside callback, it is enought to just set the state. It is much more prefereable to let the `ActiveRecord` object to persist when it makes sense for the application and thus keep the state machine focused on managing the state transitions.
1469
+ Please note that you do not need to call `target.save` inside callback, it is enough to just set the state. It is much more preferable to let the `ActiveRecord` object to persist when it makes sense for the application and thus keep the state machine focused on managing the state transitions.
1415
1470
 
1416
- ### 8.3 Transactions
1471
+ ### 7.3 Transactions
1417
1472
 
1418
- When using **FiniteMachine** with ActiveRecord it advisable to trigger state changes inside transactions to ensure integrity of the database. Given Account example from section 8.2 one can run event in transaction in the following way:
1473
+ When using **FiniteMachine** with ActiveRecord it advisable to trigger state changes inside transactions to ensure integrity of the database. Given Account example from section 7.2 one can run event in transaction in the following way:
1419
1474
 
1420
1475
  ```ruby
1421
1476
  ActiveRecord::Base.transaction do
@@ -1427,7 +1482,7 @@ If the transition fails it will raise `TransitionError` which will cause the tra
1427
1482
 
1428
1483
  Please check the ORM of your choice if it supports database transactions.
1429
1484
 
1430
- ## 9 Tips
1485
+ ## 8 Tips
1431
1486
 
1432
1487
  Creating a standalone **FiniteMachine** brings a number of benefits, one of them being easier testing. This is especially true if the state machine is extremely complex itself. Ideally, you would test the machine in isolation and then integrate it with other objects or ORMs.
1433
1488
 
@@ -1441,4 +1496,4 @@ Creating a standalone **FiniteMachine** brings a number of benefits, one of them
1441
1496
 
1442
1497
  ## Copyright
1443
1498
 
1444
- Copyright (c) 2014-2015 Piotr Murach. See LICENSE for further details.
1499
+ Copyright (c) 2014 Piotr Murach. See LICENSE for further details.