finite_machine 0.11.3 → 0.12.0

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