finite_machine 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7582fc3410fbb09056dee6ed167b505ebe6febaf
4
- data.tar.gz: 3d6336949ace9e8ce3ef3c469346ae6ea1c69044
3
+ metadata.gz: 88cd408cda756703b6665126580605500ad9273f
4
+ data.tar.gz: 93a8062b97cd5a0fed0367c091cbb06e45242b2c
5
5
  SHA512:
6
- metadata.gz: a1e6145d24909b4762a5be15a85f85c8eac2a07239d3f350ac1eba4283bfe9d4dd517fada486f4b749b97234d32c5b9957b82ccf8bc1ba57441f67a9ba6957d4
7
- data.tar.gz: 0c948fa0384294b6ead78ae12b6e367d033bd6adf69be4c32420e4ac67cff4a07f565f1870c9d8f62653ccdce51c606cc9f828572e5dcf15ce5d471c80ec6438
6
+ metadata.gz: fb7c16c1ec7e7238cfb5d2b37ab554741733b4957af9855878a0b35d7ef4e04f334542c8889d5829ed9d002ac9ef847c20f093590af44db7310ced42b33e53b1
7
+ data.tar.gz: 2963f9d98146612ae2a6d9c3ba5a690bb06d1f32f8fd6e1da1b817f8fdcb0923ec371f38ae48ce4e92ee553c65214f3a1dc34d095d523431a302b32b377008b9
@@ -6,13 +6,17 @@ rvm:
6
6
  - 2.0.0
7
7
  - 2.1.0
8
8
  - ruby-head
9
- - rbx
10
9
  matrix:
11
10
  include:
12
11
  - rvm: jruby-19mode
13
12
  - rvm: jruby-20mode
14
13
  - rvm: jruby-21mode
15
14
  - rvm: jruby-head
15
+ - rvm: rbx
16
16
  allow_failures:
17
- - rvm: 2.1.0
17
+ - rvm: ruby-head
18
+ - rvm: jruby-head
19
+ - rvm: rbx
18
20
  fast_finish: true
21
+ branches:
22
+ only: master
@@ -0,0 +1,12 @@
1
+ 0.2.0 (March 01, 2014)
2
+
3
+ * Ensure correct transition object state
4
+ * Add methods synchronization for thread safety
5
+ * Fix bug - callback event object returns correct from state
6
+ * Add ability to define custom initial event
7
+ * Add hooks class for callbacks registration
8
+ * Extend threadable accessors
9
+ * Add generic state and event listeners
10
+ * Add target to allow integration with external objects,
11
+ and allow easy method lookup through callback context
12
+ * Add ability to specify custom handlers for error conditions
data/README.md CHANGED
@@ -1,8 +1,13 @@
1
1
  # FiniteMachine
2
+ [![Gem Version](https://badge.fury.io/rb/finite_machine.png)][gem]
3
+ [![Build Status](https://secure.travis-ci.org/peter-murach/finite_machine.png?branch=master)][travis]
4
+ [![Code Climate](https://codeclimate.com/github/peter-murach/finite_machine.png)][codeclimate]
2
5
 
3
- A minimal finite state machine with a straightforward syntax. With intuitive
4
- syntax you can quickly model states and add callbacks that can be triggered
5
- synchronously or asynchronously.
6
+ [gem]: http://badge.fury.io/rb/finite_machine
7
+ [travis]: http://travis-ci.org/peter-murach/finite_machine
8
+ [codeclimate]: https://codeclimate.com/github/peter-murach/finite_machine
9
+
10
+ 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.
6
11
 
7
12
  ## Features
8
13
 
@@ -22,7 +27,7 @@ Add this line to your application's Gemfile:
22
27
 
23
28
  gem 'finite_machine'
24
29
 
25
- And then execute:
30
+ Then execute:
26
31
 
27
32
  $ bundle
28
33
 
@@ -30,6 +35,16 @@ Or install it yourself as:
30
35
 
31
36
  $ gem install finite_machine
32
37
 
38
+ ## Contents
39
+
40
+ * [1. Usage](#1-usage)
41
+ * [2. Transitions](#2-transitions)
42
+ * [3. Conditional transitions](#3-conditional-transitions)
43
+ * [4. Callbacks](#4-callbacks)
44
+ * [5. Errors](#5-errors)
45
+ * [6. Integration](#6-integration)
46
+ * [7. Tips](#7-tips)
47
+
33
48
  ## 1 Usage
34
49
 
35
50
  Here is a very simple example of a state machine:
@@ -52,11 +67,11 @@ fm = FiniteMachine.define do
52
67
  end
53
68
  ```
54
69
 
55
- As the example demonstrates, by calling the `define` method on **FiniteMachine** one gets to create an instance of finite state machine. The `events` and `callbacks` scopes help to define the behaviour of the machine. Read [Transitions](#transitions) and [Callbacks](#callbacks) sections for more detail.
70
+ 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](#4-callbacks) sections for more details.
56
71
 
57
72
  ### 1.1 current
58
73
 
59
- The **FiniteMachine** allows to query the current state by calling `current` method.
74
+ The **FiniteMachine** allows you to query the current state by calling the `current` method.
60
75
 
61
76
  ```ruby
62
77
  fm.current # => :red
@@ -64,105 +79,120 @@ The **FiniteMachine** allows to query the current state by calling `current` met
64
79
 
65
80
  ### 1.2 initial
66
81
 
67
- There are number of ways to provide initial state **FiniteMachine** depending on your requirements.
82
+ There are number of ways to provide the initial state **FiniteMachine** depending on your requirements.
68
83
 
69
- By default the **FiniteMachine** will be in `:none` state and you would need to provide event to transition out of this state.
84
+ By default the **FiniteMachine** will be in the `:none` state and you will need to provide an event to transition out of this state.
70
85
 
71
86
  ```ruby
72
- fm = FiniteMachine.define do
73
- events {
74
- event :start, :none => :green
75
- event :slow, :green => :yellow
76
- event :stop, :yellow => :red
77
- }
78
- end
87
+ fm = FiniteMachine.define do
88
+ events {
89
+ event :start, :none => :green
90
+ event :slow, :green => :yellow
91
+ event :stop, :yellow => :red
92
+ }
93
+ end
79
94
 
80
- fm.current # => :none
95
+ fm.current # => :none
81
96
  ```
82
97
 
83
- If you specify initial state using `initial` helper then an `init` event will be created and triggered when the state machine is constructed.
98
+ If you specify initial state using the `initial` helper, an `init` event will be created and triggered when the state machine is created.
84
99
 
85
100
  ```ruby
86
- fm = FiniteMachine.define do
87
- initial :green
101
+ fm = FiniteMachine.define do
102
+ initial :green
88
103
 
89
- events {
90
- event :slow, :green => :yellow
91
- event :stop, :yellow => :red
92
- }
93
- end
104
+ events {
105
+ event :slow, :green => :yellow
106
+ event :stop, :yellow => :red
107
+ }
108
+ end
94
109
 
95
- fm.current # => :green
110
+ fm.current # => :green
96
111
  ```
97
112
 
98
- Finally, if you want to defer calling the initial state method pass the `:defer` option to `initial` helper.
113
+ 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.
99
114
 
100
115
  ```ruby
101
- fm = FiniteMachine.define do
102
- initial state: :green, defer: true
116
+ fm = FiniteMachine.define do
117
+ initial :green, event: :start
103
118
 
104
- events {
105
- event :slow, :green => :yellow
106
- event :stop, :yellow => :red
107
- }
108
- end
109
- fm.current # => :none
110
- fm.init
111
- fm.current # => :green
119
+ events {
120
+ event :slow, :green => :yellow
121
+ event :stop, :yellow => :red
122
+ }
123
+ end
124
+
125
+ fm.current # => :green
126
+ ```
127
+
128
+ If you want to defer calling the initial state method pass the `:defer` option to the `initial` helper.
129
+
130
+ ```ruby
131
+ fm = FiniteMachine.define do
132
+ initial state: :green, defer: true
133
+
134
+ events {
135
+ event :slow, :green => :yellow
136
+ event :stop, :yellow => :red
137
+ }
138
+ end
139
+ fm.current # => :none
140
+ fm.init
141
+ fm.current # => :green
112
142
  ```
113
143
 
114
144
  ### 1.3 terminal
115
145
 
116
- To specify a final state **FiniteMachine** uses `terminal` method.
146
+ To specify a final state **FiniteMachine** uses the `terminal` method.
117
147
 
118
148
  ```ruby
119
- fm = FiniteMachine.define do
120
- initial :green
121
- terminal :red
149
+ fm = FiniteMachine.define do
150
+ initial :green
151
+ terminal :red
122
152
 
123
- events {
124
- event :slow, :green => :yellow
125
- event :stop, :yellow => :red
126
- }
127
- end
153
+ events {
154
+ event :slow, :green => :yellow
155
+ event :stop, :yellow => :red
156
+ }
157
+ end
128
158
  ```
129
159
 
130
- After terminal state has been specified, you can use `finished?` method on the state machine instance to verify if the terminal state has been reached or not.
160
+ When the terminal state has been specified, you can use `finished?` method on the state machine instance to verify if the terminal state has been reached or not.
131
161
 
132
162
  ```ruby
133
- fm.finished? # => false
134
- fm.slow
135
- fm.finished? # => false
136
- fm.stop
137
- fm.finished? # => true
163
+ fm.finished? # => false
164
+ fm.slow
165
+ fm.finished? # => false
166
+ fm.stop
167
+ fm.finished? # => true
138
168
  ```
139
169
 
140
170
  ### 1.4 is?
141
171
 
142
- To verify whether or not a state machine is in a given state, **FiniteMachine** uses `is?` method. It returns `true` if machien is found to be in a state, and `false` otherwise.
172
+ 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.
143
173
 
144
174
  ```ruby
145
- fm.is?(:red) # => true
146
- fm.is?(:yellow) # => false
175
+ fm.is?(:red) # => true
176
+ fm.is?(:yellow) # => false
147
177
  ```
148
178
 
149
179
  ### 1.5 can? and cannot?
150
180
 
151
- To verify whether or not an event can be fired, **FiniteMachine** provides `can?` or `cannot?` methods. `can?` checks if transition can be performed and returns true if state change can happend, and false otherwise. `cannot?` is simply the inverse of `can?`.
181
+ 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?`.
152
182
 
153
183
  ```ruby
154
- fm.can?(:ready) # => true
155
- fm.can?(:go) # => false
156
- fm.cannot?(:ready) # => false
157
- fm.cannot?(:go) # => true
184
+ fm.can?(:ready) # => true
185
+ fm.can?(:go) # => false
186
+ fm.cannot?(:ready) # => false
187
+ fm.cannot?(:go) # => true
158
188
  ```
159
189
 
160
190
  ### 1.6 states
161
191
 
162
- You can use `states` method to query for all states. It returns an array of all the states for the current state machine.
192
+ You can use the `states` method to return an array of all the states for a given state machine.
163
193
 
164
194
  ```ruby
165
- fm.states # => [:none, :green, :yellow, :red]
195
+ fm.states # => [:none, :green, :yellow, :red]
166
196
  ```
167
197
 
168
198
  ### 1.7 target
@@ -170,20 +200,43 @@ You can use `states` method to query for all states. It returns an array of all
170
200
  If you need to execute some external code in the context of the current state machine use `target` helper.
171
201
 
172
202
  ```ruby
173
- car = Car.new
203
+ car = Car.new
174
204
 
175
- fm = FiniteMachine.define do
176
- initial :neutral
205
+ fm = FiniteMachine.define do
206
+ initial :neutral
177
207
 
178
- target car
208
+ target car
179
209
 
180
- events {
181
- event :start, :neutral => :one, if: "engine_on?"
182
- event :shift, :one => :two
183
- }
184
- end
210
+ events {
211
+ event :start, :neutral => :one, if: "engine_on?"
212
+ event :shift, :one => :two
213
+ }
214
+ end
215
+ ```
216
+
217
+ Furthermore, the context created through `target` helper will allow you to reference and call methods from another object.
218
+
219
+ ```ruby
220
+ car = Car.new
221
+
222
+ fm = FiniteMachine.define do
223
+ initial :neutral
224
+
225
+ target car
226
+
227
+ events {
228
+ event :start, :neutral => :one, if: "engine_on?"
229
+ }
230
+
231
+ callbacks {
232
+ on_enter_start do |event| turn_engine_on end
233
+ on_exit_start do |event| turn_engine_off end
234
+ }
235
+ end
185
236
  ```
186
237
 
238
+ For more complex example see [Integration](#6-integration) section.
239
+
187
240
  ## 2 Transitions
188
241
 
189
242
  The `events` scope exposes the `event` helper to define possible state transitions.
@@ -198,7 +251,7 @@ in the form of `:from` and `:to` hash keys or by using the state names themselve
198
251
  event :start, :neutral => :first
199
252
  ```
200
253
 
201
- Once specified the **FiniteMachine** will create custom methods for transitioning between the states.
254
+ Once specified, the **FiniteMachine** will create custom methods for transitioning between each state.
202
255
  The following methods trigger transitions for the example state machine.
203
256
 
204
257
  * ready
@@ -207,14 +260,14 @@ The following methods trigger transitions for the example state machine.
207
260
 
208
261
  ### 2.1 Performing transitions
209
262
 
210
- In order to transition to the next reachable state simply call the event name on the **FiniteMachine** instance.
263
+ In order to transition to the next reachable state, simply call the event's name on the **FiniteMachine** instance.
211
264
 
212
265
  ```ruby
213
266
  fm.ready
214
267
  fm.current # => :yellow
215
268
  ```
216
269
 
217
- Further, you can pass additional parameters with the method call that will be available in the triggered callback.
270
+ Furthermore, you can pass additional parameters with the method call that will be available in the triggered callback.
218
271
 
219
272
  ```ruby
220
273
  fm.go('Piotr!')
@@ -278,7 +331,7 @@ You can also execute methods on an associated object by passing it as an argumen
278
331
  end
279
332
 
280
333
  car = Car.new
281
- car.trun_engine_on
334
+ car.turn_engine_on
282
335
 
283
336
  fm = FiniteMachine.define do
284
337
  initial :neutral
@@ -310,7 +363,7 @@ You can also use a symbol corresponding to the name of a method that will get ca
310
363
  end
311
364
  ```
312
365
 
313
- ### 3.2 Using a String
366
+ ### 3.3 Using a String
314
367
 
315
368
  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.
316
369
 
@@ -328,7 +381,7 @@ Finally, it's possible to use string that will be evaluated using `eval` and nee
328
381
 
329
382
  ### 3.4 Combining transition conditions
330
383
 
331
- When multiple conditions define whether or not a transition should happen, an Array can be used. Moreover, you can apply both `:if` and `:unless` to the same transition.
384
+ 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.
332
385
 
333
386
  ```ruby
334
387
  fsm = FiniteMachine.define do
@@ -347,15 +400,24 @@ The transition only runs when all the `:if` conditions and none of the `unless`
347
400
 
348
401
  ## 4 Callbacks
349
402
 
350
- You can consume state machine events and the information they provide by registering a callback. The following main 3 types of callbacks are available in **FiniteMachine**:
403
+ You can watch state machine events and the information they provide by registering a callback. The following 3 types of callbacks are available in **FiniteMachine**:
351
404
 
352
405
  * `on_enter`
353
406
  * `on_transition`
354
407
  * `on_exit`
355
408
 
356
- Use `callbacks` scope to introduce the listeners. You can register a callback to listen for state changes or events being triggered. Use the state or event name as a first parameter to the callback followed by a list arguments that you expect to receive.
409
+ In addition, you can listen for generic state changes or events fired by using the following 6 callbacks:
357
410
 
358
- When you subscribe to `:green` state event, the callback will be called whenever someone instruments change for that state. The same will happend upon subscription to event `ready`, namely, the callback will be called each time the state transition method is called.
411
+ * `on_enter_state`
412
+ * `on_enter_event`
413
+ * `on_transition_state`
414
+ * `on_transition_event`
415
+ * `on_exit_state`
416
+ * `on_exit_event`
417
+
418
+ Use the `callbacks` scope to introduce the listeners. You can register a callback to listen for state changes or events being triggered. Use the state or event name as a first parameter to the callback followed by a list arguments that you expect to receive.
419
+
420
+ When you subscribe to the `:green` state event, the callback will be called whenever someone instruments change for that state. The same will happend on subscription to event `ready`, namely, the callback will be called each time the state transition method is called.
359
421
 
360
422
  ```ruby
361
423
  fm = FiniteMachine.define do
@@ -380,19 +442,25 @@ fm.go('Piotr!')
380
442
 
381
443
  ### 4.1 on_enter
382
444
 
383
- This method is executed before given event or state change happens. If you provide only a callback without the name for the state or event to listen for, then `any` state and event will be observered.
445
+ This method is executed before given event or state change. If you provide only a callback without the name of the state or event to listen out for, then `:any` state and `:any` event will be observered.
446
+
447
+ You can further narrow down the listener to only watch enter state changes using `on_enter_state` callback. Similarly, use `on_enter_event` to only watch for event changes.
384
448
 
385
449
  ### 4.2 on_transition
386
450
 
387
- This method is executed when given event or state change happens. If you provide only a callback without the name for the state or event to listen for, then `any` state and event will be observered.
451
+ This method is executed when given event or state change happens. If you provide only a callback without the name of the state or event to listen out for, then `:any` state and `:any` event will be observered.
452
+
453
+ You can further narrow down the listener to only watch state transition changes using `on_transition_state` callback. Similarly, use `on_transition_event` to only watch for event transition changes.
388
454
 
389
455
  ### 4.3 on_exit
390
456
 
391
- This method is executed after given event or state change happens. If you provide only a callback without the name for the state or event to listen for, then `any` state and event will be observered.
457
+ This method is executed after a given event or state change happens. If you provide only a callback without the name of the state or event to listen for, then `:any` state and `:any` event will be observered.
458
+
459
+ You can further narrow down the listener to only watch state exit changes using `on_exit_state` callback. Similarly, use `on_exit_event` to only watch for event exit changes.
392
460
 
393
461
  ### 4.4 Parameters
394
462
 
395
- All callbacks are passed `TransitionEvent` object with the following attributes.
463
+ All callbacks get the `TransitionEvent` object with the following attributes.
396
464
 
397
465
  * name # the event name
398
466
  * from # the state transitioning from
@@ -400,24 +468,42 @@ All callbacks are passed `TransitionEvent` object with the following attributes.
400
468
 
401
469
  followed by the rest of arguments that were passed to the event method.
402
470
 
471
+ ```ruby
472
+ fm = FiniteMachine.define do
473
+ initial :red
474
+
475
+ events {
476
+ event :ready, :red => :yellow
477
+ }
478
+
479
+ callbacks {
480
+ on_enter_ready { |event, time|
481
+ puts "lights switching from #{event.from} to #{event.to} in #{time} seconds"
482
+ }
483
+ }
484
+ end
485
+
486
+ fm.ready(3) # => 'lights switching from red to yellow in 3 seconds'
487
+ ```
488
+
403
489
  ### 4.5 Same kind of callbacks
404
490
 
405
491
  You can define any number of the same kind of callback. These callbacks will be executed in the order they are specified.
406
492
 
407
493
  ```ruby
408
- fm = FiniteMachine.define do
409
- initial :green
494
+ fm = FiniteMachine.define do
495
+ initial :green
410
496
 
411
- events {
412
- event :slow, :green => :yellow
413
- }
497
+ events {
498
+ event :slow, :green => :yellow
499
+ }
414
500
 
415
- callbacks {
416
- on_enter(:yellow) { this_is_run_first }
417
- on_enter(:yellow) { then_this }
418
- }
419
- end
420
- fm.slow # => will invoke both callbacks
501
+ callbacks {
502
+ on_enter(:yellow) { this_is_run_first }
503
+ on_enter(:yellow) { then_this }
504
+ }
505
+ end
506
+ fm.slow # => will invoke both callbacks
421
507
  ```
422
508
 
423
509
  ### 4.6 Fluid callbacks
@@ -442,30 +528,143 @@ fm = FiniteMachine.define do
442
528
  end
443
529
  ```
444
530
 
531
+ ### 4.7 Executing methods inside callbacks
532
+
533
+ In order to execute method from another object use `target` helper.
534
+
535
+ ```ruby
536
+ class Car
537
+ attr_accessor :reverse_lights
538
+
539
+ def turn_reverse_lights_off
540
+ @reverse_lights = false
541
+ end
542
+
543
+ def turn_reverse_lights_on
544
+ @reverse_lights = true
545
+ end
546
+ end
547
+
548
+ car = Car.new
549
+
550
+ fm = FiniteMachine.define do
551
+ initial :neutral
552
+
553
+ target car
554
+
555
+ events {
556
+ event :forward, [:reverse, :neutral] => :one
557
+ event :back, [:neutral, :one] => :reverse
558
+ }
559
+
560
+ callbacks {
561
+ on_enter_reverse { |event| turn_reverse_lights_on }
562
+ on_exit_reverse { |event| turn_reverse_lights_off }
563
+ }
564
+ end
565
+ ```
566
+
567
+ Note that you can also fire events from callbacks.
568
+
569
+ ```ruby
570
+ fm = FiniteMachine.define do
571
+ initial :neutral
572
+
573
+ target car
574
+ events {
575
+ event :forward, [:reverse, :neutral] => :one
576
+ event :back, [:neutral, :one] => :reverse
577
+ }
578
+
579
+ callbacks {
580
+ on_enter_reverse { |event| forward('Piotr!') }
581
+ on_exit_reverse { |event, name| puts "Go #{name}" }
582
+ }
583
+ end
584
+ fm.back # => Go Piotr!
585
+ ```
586
+
587
+ For more complex example see [Integration](#6-integration) section.
588
+
445
589
  ## 5 Errors
446
590
 
447
- ## 6 Integration
591
+ By default, the **FiniteMachine** will throw an exception whenever the machine is in invalid state or fails to transition.
448
592
 
449
- 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(e.i.ActiveRecord) to transform them into state machine or require mixing into exisiting class.
593
+ * FiniteMachine::TransitionError
594
+ * FiniteMachine::InvalidStateError
595
+ * FiniteMachine::InvalidCallbackError
596
+
597
+ 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.
450
598
 
451
599
  ```ruby
600
+ fm = FiniteMachine.define do
601
+ initial :green, event: :start
602
+
603
+ events {
604
+ event :slow, :green => :yellow
605
+ event :stop, :yellow => :red
606
+ }
607
+
608
+ handlers {
609
+ handle FiniteMachine::InvalidStateError do |exception|
610
+ # run some custom logging
611
+ raise exception
612
+ end
613
+
614
+ handle FiniteMachine::TransitionError, with: proc { |exception| ... }
615
+ }
616
+ end
617
+ ```
618
+
619
+ ### 5.1 Using target
620
+
621
+ 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.
622
+
623
+ ```ruby
624
+ class Logger
625
+ def log_error(exception)
626
+ puts "Exception : #{exception.message}"
627
+ end
628
+ end
629
+
630
+ fm = FiniteMachine.define do
631
+ target logger
632
+
633
+ initial :green
634
+
635
+ events {
636
+ event :slow, :green => :yellow
637
+ event :stop, :yellow => :red
638
+ }
639
+
640
+ handlers {
641
+ handle 'InvalidStateError', with: :log_error
642
+ }
643
+ end
644
+ ```
645
+
646
+ ## 6 Integration
452
647
 
648
+ 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.
649
+
650
+ ```ruby
453
651
  class Car
454
652
  attr_accessor :reverse_lights
455
653
 
456
654
  def turn_reverse_lights_off
457
- reverse_lights = false
655
+ @reverse_lights = false
458
656
  end
459
657
 
460
658
  def turn_reverse_lights_on
461
- reverse_lights = true
659
+ @reverse_lights = true
462
660
  end
463
661
 
464
662
  def gears
663
+ context = self
465
664
  @gears ||= FiniteMachine.define do
466
665
  initial :neutral
467
666
 
468
- target: self
667
+ target: context
469
668
 
470
669
  events {
471
670
  event :start, :neutral => :one
@@ -475,27 +674,68 @@ class Car
475
674
  }
476
675
 
477
676
  callbacks {
478
- on_enter :reverse do |car, event|
479
- car.turnReverseLightsOn
677
+ on_enter :reverse do |event|
678
+ turn_reverse_lights_on
480
679
  end
481
680
 
482
- on_exit :reverse do |car, event|
483
- car.turnReverseLightsOff
681
+ on_exit :reverse do |event|
682
+ turn_reverse_lights_off
484
683
  end
485
684
 
486
- on_transition do |car, event|
685
+ on_transition do |event|
487
686
  puts "shifted from #{event.from} to #{event.to}"
488
687
  end
489
688
  }
490
689
  end
491
690
  end
492
691
  end
692
+ ```
693
+
694
+ ### 6.1 ActiveRecord
695
+
696
+ In order to integrate **FiniteMachine** with ActiveRecord use the `target` helper to reference the current class and call ActiveRecord methods inside the callbacks to persist the state.
697
+
698
+ ```ruby
699
+ class Account < ActiveRecord::Base
700
+ validates :state, presence: true
701
+
702
+ def initialize
703
+ self.state = :unapproved
704
+ end
705
+
706
+ def manage
707
+ context = self
708
+ @machine ||= FiniteMachine.define do
709
+ target context
710
+
711
+ initial context.state
712
+
713
+ events {
714
+ event :enqueue, :unapproved => :pending
715
+ event :authorize, :pending => :access
716
+ }
717
+
718
+ callbacks {
719
+ on_enter_state do |event|
720
+ state = event.to
721
+ save
722
+ end
723
+ }
724
+ end
725
+ end
726
+ end
493
727
 
728
+ account = Account.new
729
+ account.state # => :unapproved
730
+ account.manage.enqueue
731
+ account.state # => :pending
732
+ account.manage.authorize
733
+ account.state # => :access
494
734
  ```
495
735
 
496
736
  ## 7 Tips
497
737
 
498
- Creating standalone **FiniteMachine** brings few benefits, one of them being easier testing. This is especially true if the state machine is extremely complex itself.
738
+ Creating a standalone **FiniteMachine** brings few 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.
499
739
 
500
740
  ## Contributing
501
741