finite_machine 0.2.0 → 0.3.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: 88cd408cda756703b6665126580605500ad9273f
4
- data.tar.gz: 93a8062b97cd5a0fed0367c091cbb06e45242b2c
3
+ metadata.gz: 29fd461f4bd19a780bac89bc363026b214db867f
4
+ data.tar.gz: 70e229f5f28433618f2408d35136aad7a0a1bb8e
5
5
  SHA512:
6
- metadata.gz: fb7c16c1ec7e7238cfb5d2b37ab554741733b4957af9855878a0b35d7ef4e04f334542c8889d5829ed9d002ac9ef847c20f093590af44db7310ced42b33e53b1
7
- data.tar.gz: 2963f9d98146612ae2a6d9c3ba5a690bb06d1f32f8fd6e1da1b817f8fdcb0923ec371f38ae48ce4e92ee553c65214f3a1dc34d095d523431a302b32b377008b9
6
+ metadata.gz: 00f1f997a94d5f5cb8571921d088a5bc4245001a45463f3b0fc57538094623d94e97c52df76780e957f16279c1bd43d8d0c004595a55a60005f70993fe4873d2
7
+ data.tar.gz: c62a75b03d74891a0f83bc91ef3bac4e22090f29b0adb9edc398f0290f39dfbcde36513d994373698be17554aa4618916bcd15018275d2afc197f93b0e6944b7
data/.rspec CHANGED
@@ -1,2 +1,4 @@
1
1
  --color
2
2
  --format progress
3
+ --order random
4
+ --warnings
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ finite_machine
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.0.0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ 0.3.0 (March 30, 2014)
2
+
3
+ * Move development dependencies to Gemfile
4
+ * Increase test coverage to 95%
5
+ * Fix bug with event methods dynamic redefinition
6
+ * Change attr_threadsafe to accept default values
7
+ * Fix observer respond_to
8
+ * Add ability to specify callbacks on machine instance
9
+ * Add once_on type of callback
10
+ * Add off method for removing callbacks
11
+ * Add async method to state_machine for asynchronous events firing
12
+ * Fix Callable to correctly forward arguments
13
+ * Add state helpers fsm.green? to allow easily check current state
14
+
1
15
  0.2.0 (March 01, 2014)
2
16
 
3
17
  * Ensure correct transition object state
data/Gemfile CHANGED
@@ -1,4 +1,15 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in finite_machine.gemspec
4
3
  gemspec
4
+
5
+ group :development do
6
+ gem 'rake', '~> 10.1.0'
7
+ gem 'rspec', '~> 2.14.1'
8
+ gem 'yard', '~> 0.8.7'
9
+ end
10
+
11
+ group :metrics do
12
+ gem 'coveralls', '~> 0.7.0'
13
+ gem 'simplecov', '~> 0.8.2'
14
+ gem 'yardstick', '~> 0.9.9'
15
+ end
data/README.md CHANGED
@@ -2,12 +2,16 @@
2
2
  [![Gem Version](https://badge.fury.io/rb/finite_machine.png)][gem]
3
3
  [![Build Status](https://secure.travis-ci.org/peter-murach/finite_machine.png?branch=master)][travis]
4
4
  [![Code Climate](https://codeclimate.com/github/peter-murach/finite_machine.png)][codeclimate]
5
+ [![Coverage Status](https://coveralls.io/repos/peter-murach/finite_machine/badge.png)][coverage]
6
+ [![Inline docs](http://inch-pages.github.io/github/peter-murach/finite_machine.png)][inchpages]
5
7
 
6
8
  [gem]: http://badge.fury.io/rb/finite_machine
7
9
  [travis]: http://travis-ci.org/peter-murach/finite_machine
8
10
  [codeclimate]: https://codeclimate.com/github/peter-murach/finite_machine
11
+ [coverage]: https://coveralls.io/r/peter-murach/finite_machine
12
+ [inchpages]: http://inch-pages.github.io/github/peter-murach/finite_machine
9
13
 
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.
14
+ 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.
11
15
 
12
16
  ## Features
13
17
 
@@ -18,6 +22,7 @@ A minimal finite state machine with a straightforward and intuitive syntax. You
18
22
  * ability to check reachable states
19
23
  * ability to check for terminal state
20
24
  * conditional transitions
25
+ * sync and async transitions
21
26
  * sync and async callbacks (TODO - only sync)
22
27
  * nested/composable states (TODO)
23
28
 
@@ -176,6 +181,13 @@ fm.is?(:red) # => true
176
181
  fm.is?(:yellow) # => false
177
182
  ```
178
183
 
184
+ Moreover, you can use helper methods to check for current state using the state name itself like so
185
+
186
+ ```ruby
187
+ fm.red? # => true
188
+ fm.yellow? # => false
189
+ ```
190
+
179
191
  ### 1.5 can? and cannot?
180
192
 
181
193
  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?`.
@@ -246,9 +258,9 @@ method on the **FiniteMachine** instance. As a second parameter `event` accepts
246
258
  in the form of `:from` and `:to` hash keys or by using the state names themselves as key value pairs.
247
259
 
248
260
  ```ruby
249
- event :start, from: :neutral, to: :first
250
- or
251
- event :start, :neutral => :first
261
+ event :start, from: :neutral, to: :first
262
+ or
263
+ event :start, :neutral => :first
252
264
  ```
253
265
 
254
266
  Once specified, the **FiniteMachine** will create custom methods for transitioning between each state.
@@ -263,18 +275,35 @@ The following methods trigger transitions for the example state machine.
263
275
  In order to transition to the next reachable state, simply call the event's name on the **FiniteMachine** instance.
264
276
 
265
277
  ```ruby
266
- fm.ready
267
- fm.current # => :yellow
278
+ fm.ready
279
+ fm.current # => :yellow
268
280
  ```
269
281
 
270
282
  Furthermore, you can pass additional parameters with the method call that will be available in the triggered callback.
271
283
 
272
284
  ```ruby
273
- fm.go('Piotr!')
274
- fm.current # => :green
285
+ fm.go('Piotr!')
286
+ fm.current # => :green
275
287
  ```
276
288
 
277
- ### 2.2 single event with multiple from states
289
+ ### 2.2 Asynchronous transitions
290
+
291
+ By default the transitions will be fired synchronosuly.
292
+
293
+ ```ruby
294
+ fm.ready
295
+ or
296
+ fm.sync.ready
297
+ fm.current # => :yellow
298
+ ```
299
+
300
+ In order to fire the event transition asynchronously use the `async` scope like so
301
+
302
+ ```ruby
303
+ fm.async.ready # => executes in separate Thread
304
+ ```
305
+
306
+ ### 2.3 single event with multiple from states
278
307
 
279
308
  If an event transitions from multiple states to the same state then all the states can be grouped into an array.
280
309
  Altenatively, you can create separte events under the same name for each transition that needs combining.
@@ -302,49 +331,49 @@ Each event takes an optional `:if` and `:unless` options which act as a predicat
302
331
  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.
303
332
 
304
333
  ```ruby
305
- fm = FiniteMachine.define do
306
- initial :green
334
+ fm = FiniteMachine.define do
335
+ initial :green
307
336
 
308
- events {
309
- event :slow, :green => :yellow, if: -> { return false }
310
- }
311
- end
312
- fm.slow # doesn't transition to :yellow state
313
- fm.current # => :green
337
+ events {
338
+ event :slow, :green => :yellow, if: -> { return false }
339
+ }
340
+ end
341
+ fm.slow # doesn't transition to :yellow state
342
+ fm.current # => :green
314
343
  ```
315
344
 
316
345
  You can also execute methods on an associated object by passing it as an argument to `target` helper.
317
346
 
318
347
  ```ruby
319
- class Car
320
- def turn_engine_on
321
- @engine_on = true
322
- end
348
+ class Car
349
+ def turn_engine_on
350
+ @engine_on = true
351
+ end
323
352
 
324
- def turn_engine_off
325
- @engine_on = false
326
- end
353
+ def turn_engine_off
354
+ @engine_on = false
355
+ end
327
356
 
328
- def engine_on?
329
- @engine_on
330
- end
357
+ def engine_on?
358
+ @engine_on
331
359
  end
360
+ end
332
361
 
333
- car = Car.new
334
- car.turn_engine_on
362
+ car = Car.new
363
+ car.turn_engine_on
335
364
 
336
- fm = FiniteMachine.define do
337
- initial :neutral
365
+ fm = FiniteMachine.define do
366
+ initial :neutral
338
367
 
339
- target car
368
+ target car
340
369
 
341
- events {
342
- event :start, :neutral => :one, if: "engine_on?"
343
- }
344
- end
370
+ events {
371
+ event :start, :neutral => :one, if: "engine_on?"
372
+ }
373
+ end
345
374
 
346
- fm.start
347
- fm.current # => :one
375
+ fm.start
376
+ fm.current # => :one
348
377
  ```
349
378
 
350
379
  ### 3.2 Using a Symbol
@@ -352,15 +381,15 @@ You can also execute methods on an associated object by passing it as an argumen
352
381
  You can also use a symbol corresponding to the name of a method that will get called right before transition happens.
353
382
 
354
383
  ```ruby
355
- fsm = FiniteMachine.define do
356
- initial :neutral
384
+ fsm = FiniteMachine.define do
385
+ initial :neutral
357
386
 
358
- target car
387
+ target car
359
388
 
360
- events {
361
- event :start, :neutral => :one, if: :engine_on?
362
- }
363
- end
389
+ events {
390
+ event :start, :neutral => :one, if: :engine_on?
391
+ }
392
+ end
364
393
  ```
365
394
 
366
395
  ### 3.3 Using a String
@@ -368,15 +397,15 @@ You can also use a symbol corresponding to the name of a method that will get ca
368
397
  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.
369
398
 
370
399
  ```ruby
371
- fsm = FiniteMachine.define do
372
- initial :neutral
400
+ fsm = FiniteMachine.define do
401
+ initial :neutral
373
402
 
374
- target car
403
+ target car
375
404
 
376
- events {
377
- event :start, :neutral => :one, if: "engine_on?"
378
- }
379
- end
405
+ events {
406
+ event :start, :neutral => :one, if: "engine_on?"
407
+ }
408
+ end
380
409
  ```
381
410
 
382
411
  ### 3.4 Combining transition conditions
@@ -384,16 +413,16 @@ Finally, it's possible to use string that will be evaluated using `eval` and nee
384
413
  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.
385
414
 
386
415
  ```ruby
387
- fsm = FiniteMachine.define do
388
- initial :green
416
+ fsm = FiniteMachine.define do
417
+ initial :green
389
418
 
390
- events {
391
- event :slow, :green => :yellow,
392
- if: [ -> { return true }, -> { return true} ],
393
- unless: -> { return true }
394
- event :stop, :yellow => :red
395
- }
396
- end
419
+ events {
420
+ event :slow, :green => :yellow,
421
+ if: [ -> { return true }, -> { return true} ],
422
+ unless: -> { return true }
423
+ event :stop, :yellow => :red
424
+ }
425
+ end
397
426
  ```
398
427
 
399
428
  The transition only runs when all the `:if` conditions and none of the `unless` conditions are evaluated to `true`.
@@ -458,13 +487,21 @@ This method is executed after a given event or state change happens. If you prov
458
487
 
459
488
  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.
460
489
 
461
- ### 4.4 Parameters
490
+ ### 4.4 once_on
491
+
492
+ **FiniteMachine** allows you to listen on initial state change or when the event is fired first time by using the following 3 types of callbacks:
493
+
494
+ * `once_on_enter`
495
+ * `once_on_transition`
496
+ * `once_on_exit`
497
+
498
+ ### 4.5 Parameters
462
499
 
463
500
  All callbacks get the `TransitionEvent` object with the following attributes.
464
501
 
465
- * name # the event name
466
- * from # the state transitioning from
467
- * to # the state transitioning to
502
+ * `name # the event name`
503
+ * `from # the state transitioning from`
504
+ * `to # the state transitioning to`
468
505
 
469
506
  followed by the rest of arguments that were passed to the event method.
470
507
 
@@ -486,7 +523,7 @@ end
486
523
  fm.ready(3) # => 'lights switching from red to yellow in 3 seconds'
487
524
  ```
488
525
 
489
- ### 4.5 Same kind of callbacks
526
+ ### 4.6 Same kind of callbacks
490
527
 
491
528
  You can define any number of the same kind of callback. These callbacks will be executed in the order they are specified.
492
529
 
@@ -506,7 +543,7 @@ end
506
543
  fm.slow # => will invoke both callbacks
507
544
  ```
508
545
 
509
- ### 4.6 Fluid callbacks
546
+ ### 4.7 Fluid callbacks
510
547
 
511
548
  Callbacks can also be specified as full method calls.
512
549
 
@@ -528,7 +565,7 @@ fm = FiniteMachine.define do
528
565
  end
529
566
  ```
530
567
 
531
- ### 4.7 Executing methods inside callbacks
568
+ ### 4.8 Executing methods inside callbacks
532
569
 
533
570
  In order to execute method from another object use `target` helper.
534
571
 
@@ -586,13 +623,33 @@ fm.back # => Go Piotr!
586
623
 
587
624
  For more complex example see [Integration](#6-integration) section.
588
625
 
626
+ ### 4.9 Defining callbacks
627
+
628
+ 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.
629
+
630
+ ```ruby
631
+ fm = FiniteMachine.define do
632
+ initial :red
633
+
634
+ events {
635
+ event :ready, :red => :yellow
636
+ event :go, :yellow => :green
637
+ event :stop, :green => :red
638
+ }
639
+ end
640
+
641
+ fm.on_enter_yellow do |event|
642
+ ...
643
+ end
644
+ ```
645
+
589
646
  ## 5 Errors
590
647
 
591
648
  By default, the **FiniteMachine** will throw an exception whenever the machine is in invalid state or fails to transition.
592
649
 
593
- * FiniteMachine::TransitionError
594
- * FiniteMachine::InvalidStateError
595
- * FiniteMachine::InvalidCallbackError
650
+ * `FiniteMachine::TransitionError`
651
+ * `FiniteMachine::InvalidStateError`
652
+ * `FiniteMachine::InvalidCallbackError`
596
653
 
597
654
  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.
598
655
 
@@ -664,7 +721,7 @@ class Car
664
721
  @gears ||= FiniteMachine.define do
665
722
  initial :neutral
666
723
 
667
- target: context
724
+ target context
668
725
 
669
726
  events {
670
727
  event :start, :neutral => :one
@@ -705,7 +762,7 @@ class Account < ActiveRecord::Base
705
762
 
706
763
  def manage
707
764
  context = self
708
- @machine ||= FiniteMachine.define do
765
+ @manage ||= FiniteMachine.define do
709
766
  target context
710
767
 
711
768
  initial context.state
data/Rakefile CHANGED
@@ -1,41 +1,8 @@
1
- require "bundler/gem_tasks"
2
-
3
- begin
4
- require 'rspec/core/rake_task'
5
-
6
- desc 'Run all specs'
7
- RSpec::Core::RakeTask.new(:spec) do |task|
8
- task.pattern = 'spec/{unit,integration}{,/*/**}/*_spec.rb'
9
- end
1
+ # encoding: utf-8
10
2
 
11
- namespace :spec do
12
- desc 'Run unit specs'
13
- RSpec::Core::RakeTask.new(:unit) do |task|
14
- task.pattern = 'spec/unit{,/*/**}/*_spec.rb'
15
- end
16
-
17
- desc 'Run integration specs'
18
- RSpec::Core::RakeTask.new(:integration) do |task|
19
- task.pattern = 'spec/integration{,/*/**}/*_spec.rb'
20
- end
21
- end
3
+ require "bundler/gem_tasks"
22
4
 
23
- rescue LoadError
24
- %w[spec spec:unit spec:integration].each do |name|
25
- task name do
26
- $stderr.puts "In order to run #{name}, do `gem install rspec`"
27
- end
28
- end
29
- end
5
+ FileList['tasks/**/*.rake'].each(&method(:import))
30
6
 
31
7
  desc 'Run all specs'
32
8
  task ci: %w[ spec ]
33
-
34
- desc 'Load gem inside irb console'
35
- task :console do
36
- require 'irb'
37
- require 'irb/completion'
38
- require File.join(__FILE__, '../lib/finite_machine')
39
- ARGV.clear
40
- IRB.start
41
- end