finite_machine 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3ac2b8cf6f6e899a7d6b2f04c240703d45a4b67d
4
+ data.tar.gz: f556b050c651d3120b5a2fe990fd1efc94abf961
5
+ SHA512:
6
+ metadata.gz: f5871e7adbd4a10e4a44a9bd4916f09843890490fd967d9bc8ce7b7c0a20033b72f2344d23fd7cd6bd2a3369cc5f622f343ea3ce1c2b817e8332909d754806fe
7
+ data.tar.gz: a1feb862d90d7da38db41ce326ab3a9dbcc241555a4d65e216eeb4260442f5d1a942ac474551f2b41853ab7bb6b5aeb62b474e7efab2bb2571c22a0bfe0da0bf
data/.travis.yml CHANGED
@@ -16,6 +16,8 @@ matrix:
16
16
  allow_failures:
17
17
  - rvm: ruby-head
18
18
  - rvm: jruby-head
19
+ - rvm: jruby-20mode
20
+ - rvm: jruby-21mode
19
21
  - rvm: rbx
20
22
  fast_finish: true
21
23
  branches:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ 0.7.0 (May 26, 2014)
2
+
3
+ * Change Event to EventHook for callback events
4
+ * Add Event to hold the logic for event specification
5
+ * Fix issue #8 to preserve conditionals between event specifications
6
+ * Change to allow for self-transition - fixes issue #9
7
+ * Change to detect attempt to overwrite already defined method - fixes issue #10
8
+ * Fix #respond_to on state machine to include observer
9
+ * Add string inspection to hooks
10
+ * Fix observer missing methods resolution
11
+ * Change to separate state and event callbacks. Introduced on_enter, on_before,
12
+ once_on_enter, once_on_before new event callbacks.
13
+ * Change generic callbacks to default to any state for on_enter, on_transition,
14
+ on_exit and any event for on_before and on_after
15
+ * Add check for callback name conflicts
16
+ * Ensure proper callback lifecycle
17
+
1
18
  0.6.1 (May 10, 2014)
2
19
 
3
20
  * Fix stdlib requirement
data/README.md CHANGED
@@ -65,14 +65,17 @@ Or install it yourself as:
65
65
  * [4.1 on_enter](#41-on_enter)
66
66
  * [4.2 on_transition](#42-on_transition)
67
67
  * [4.3 on_exit](#43-on_exit)
68
- * [4.4 once_on](#44-once_on)
69
- * [4.5 parameters](#45-parameters)
70
- * [4.6 Same kind of callbacks](#46-same-kind-of-callbacks)
71
- * [4.7 Fluid callbacks](#47-fluid-callbacks)
72
- * [4.8 Executing methods inside callbacks](#48-executing-methods-inside-callbacks)
73
- * [4.9 Defining callbacks](#49-defining-callbacks)
74
- * [4.10 Asynchronous callbacks](#410-asynchronous-callbacks)
75
- * [4.11 Cancelling inside callbacks](#411-cancelling-inside-callbacks)
68
+ * [4.4 on_before](#44-on_before)
69
+ * [4.5 on_after](#45-on_after)
70
+ * [4.6 once_on](#46-once_on)
71
+ * [4.7 Execution sequence](#47-execution-sequence)
72
+ * [4.8 Parameters](#48-parameters)
73
+ * [4.9 Same kind of callbacks](#49-same-kind-of-callbacks)
74
+ * [4.10 Fluid callbacks](#410-fluid-callbacks)
75
+ * [4.11 Executing methods inside callbacks](#411-executing-methods-inside-callbacks)
76
+ * [4.12 Defining callbacks](#412-defining-callbacks)
77
+ * [4.13 Asynchronous callbacks](#413-asynchronous-callbacks)
78
+ * [4.14 Cancelling inside callbacks](#414-cancelling-inside-callbacks)
76
79
  * [5. Errors](#5-errors)
77
80
  * [5.1 Using target](#51-using-target)
78
81
  * [6. Integration](#6-integration)
@@ -94,9 +97,9 @@ fm = FiniteMachine.define do
94
97
  }
95
98
 
96
99
  callbacks {
97
- on_enter(:ready) { |event| ... }
98
- on_exit(:go) { |event| ... }
99
- on_enter(:stop) { |event| ... }
100
+ on_before(:ready) { |event| ... }
101
+ on_after(:go) { |event| ... }
102
+ on_before(:stop) { |event| ... }
100
103
  }
101
104
  end
102
105
  ```
@@ -110,9 +113,9 @@ fm = FiniteMachine.new initial: :red
110
113
  fm.event(:ready, :red => :yellow)
111
114
  fm.event(:go, :yellow => :green)
112
115
  fm.event(:stop, :green => :red)
113
- fm.on_enter(:ready) { |event| ... }
114
- fm.on_exit(:go) { |event| ... }
115
- fm.on_enter(:stop) { |event| ...}
116
+ fm.on_before(:ready) { |event| ... }
117
+ fm.on_after(:go) { |event| ... }
118
+ fm.on_before(:stop) { |event| ...}
116
119
  ```
117
120
 
118
121
  ### 1.1 current
@@ -413,6 +416,19 @@ fm = FiniteMachine.define do
413
416
  }
414
417
  ```
415
418
 
419
+ The same can be more naturally rewritten also as:
420
+
421
+ ```ruby
422
+ fm = FiniteMachine.define do
423
+ initial :initial
424
+
425
+ events {
426
+ event :bump, :initial => :low
427
+ event :bump, :low => :medium
428
+ event :bump, :medium => :high
429
+ }
430
+ ```
431
+
416
432
  ## 3 Conditional transitions
417
433
 
418
434
  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.
@@ -529,24 +545,17 @@ The transition only runs when all the `:if` conditions and none of the `unless`
529
545
 
530
546
  ## 4 Callbacks
531
547
 
532
- 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**:
548
+ You can watch state machine events and the information they provide by registering a callback. The following 5 types of callbacks are available in **FiniteMachine**:
533
549
 
534
550
  * `on_enter`
535
551
  * `on_transition`
536
552
  * `on_exit`
537
-
538
- In addition, you can listen for generic state changes or events fired by using the following 6 callbacks:
539
-
540
- * `on_enter_state`
541
- * `on_enter_event`
542
- * `on_transition_state`
543
- * `on_transition_event`
544
- * `on_exit_state`
545
- * `on_exit_event`
553
+ * `on_before`
554
+ * `on_after`
546
555
 
547
556
  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.
548
557
 
549
- 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.
558
+ When you subscribe to the `:green` state change, the callback will be called whenever someone instruments change for that state. The same will happen on subscription to event `ready`, namely, the callback will be called each time the state transition method is called.
550
559
 
551
560
  ```ruby
552
561
  fm = FiniteMachine.define do
@@ -559,9 +568,9 @@ fm = FiniteMachine.define do
559
568
  }
560
569
 
561
570
  callbacks {
562
- on_enter :ready { |event, time1, time2, time3| puts "#{time1} #{time2} #{time3} Go!" }
563
- on_enter :go { |event, name| puts "Going fast #{name}" }
564
- on_enter :stop { |event| ... }
571
+ on_before :ready { |event, time1, time2, time3| puts "#{time1} #{time2} #{time3} Go!" }
572
+ on_before :go { |event, name| puts "Going fast #{name}" }
573
+ on_before :stop { |event| ... }
565
574
  }
566
575
  end
567
576
 
@@ -571,31 +580,56 @@ fm.go('Piotr!')
571
580
 
572
581
  ### 4.1 on_enter
573
582
 
574
- 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.
575
-
576
- 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.
583
+ 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.
577
584
 
578
585
  ### 4.2 on_transition
579
586
 
580
- 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.
581
-
582
- 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.
587
+ 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.
583
588
 
584
589
  ### 4.3 on_exit
585
590
 
586
- 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.
591
+ 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.
587
592
 
588
- 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.
593
+ ### 4.4 on_before
589
594
 
590
- ### 4.4 once_on
595
+ 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.
591
596
 
592
- **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:
597
+ ### 4.5 on_after
598
+
599
+ 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.
600
+
601
+ ### 4.6 once_on
602
+
603
+ **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:
593
604
 
594
605
  * `once_on_enter`
595
606
  * `once_on_transition`
596
607
  * `once_on_exit`
608
+ * `once_before`
609
+ * `once_after`
610
+
611
+ ### 4.7 Execution sequence
612
+
613
+ Assuming we have the following event specified:
614
+
615
+ ```ruby
616
+ event :go, :red => :yellow
617
+ ```
618
+
619
+ then by calling `go` event the following callbacks in the following sequence will be executed:
597
620
 
598
- ### 4.5 Parameters
621
+ * `on_before :go` - callback before the `go` event
622
+ * `on_before` - generic callback before `any` event
623
+ * `on_exit :red` - callback for the `:red` state exit
624
+ * `on_exit` - generic callback for exit from `any` state
625
+ * `on_transition :yellow` - callback for the `:red` to `:yellow` transition
626
+ * `on_transition` - callback for transition from `any` state to `any` state
627
+ * `on_enter :yellow` - callback for the `:yellow` state entry
628
+ * `on_enter` - generic callback for entry to `any` state
629
+ * `on_after :go` - callback after the `go` event
630
+ * `on_after` - generic callback after `any` event
631
+
632
+ ### 4.8 Parameters
599
633
 
600
634
  All callbacks get the `TransitionEvent` object with the following attributes.
601
635
 
@@ -623,7 +657,7 @@ end
623
657
  fm.ready(3) # => 'lights switching from red to yellow in 3 seconds'
624
658
  ```
625
659
 
626
- ### 4.6 Same kind of callbacks
660
+ ### 4.9 Same kind of callbacks
627
661
 
628
662
  You can define any number of the same kind of callback. These callbacks will be executed in the order they are specified.
629
663
 
@@ -643,7 +677,7 @@ end
643
677
  fm.slow # => will invoke both callbacks
644
678
  ```
645
679
 
646
- ### 4.7 Fluid callbacks
680
+ ### 4.10 Fluid callbacks
647
681
 
648
682
  Callbacks can also be specified as full method calls.
649
683
 
@@ -658,14 +692,14 @@ fm = FiniteMachine.define do
658
692
  }
659
693
 
660
694
  callbacks {
661
- on_enter_ready { |event| ... }
662
- on_enter_go { |event| ... }
663
- on_enter_stop { |event| ... }
695
+ on_before_ready { |event| ... }
696
+ on_before_go { |event| ... }
697
+ on_before_stop { |event| ... }
664
698
  }
665
699
  end
666
700
  ```
667
701
 
668
- ### 4.8 Executing methods inside callbacks
702
+ ### 4.11 Executing methods inside callbacks
669
703
 
670
704
  In order to execute method from another object use `target` helper.
671
705
 
@@ -708,7 +742,8 @@ fm = FiniteMachine.define do
708
742
  initial :neutral
709
743
 
710
744
  target car
711
- events {
745
+
746
+ events {
712
747
  event :forward, [:reverse, :neutral] => :one
713
748
  event :back, [:neutral, :one] => :reverse
714
749
  }
@@ -723,7 +758,7 @@ fm.back # => Go Piotr!
723
758
 
724
759
  For more complex example see [Integration](#6-integration) section.
725
760
 
726
- ### 4.9 Defining callbacks
761
+ ### 4.12 Defining callbacks
727
762
 
728
763
  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.
729
764
 
@@ -743,7 +778,7 @@ fm.on_enter_yellow do |event|
743
778
  end
744
779
  ```
745
780
 
746
- ### 4.10 Asynchronous callbacks
781
+ ### 4.13 Asynchronous callbacks
747
782
 
748
783
  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:
749
784
 
@@ -759,12 +794,12 @@ or
759
794
 
760
795
  This will ensure that when the callback is fired it will run in seperate thread outside of the main execution thread.
761
796
 
762
- ### 4.11 Cancelling inside callbacks
797
+ ### 4.14 Cancelling inside callbacks
763
798
 
764
799
  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
765
800
 
766
801
  * `on_exit :state_name`
767
- * `on_enter :event_name`
802
+ * `on_before :event_name`
768
803
 
769
804
  For example
770
805
 
@@ -6,11 +6,13 @@ require "sync"
6
6
 
7
7
  require "finite_machine/version"
8
8
  require "finite_machine/threadable"
9
+ require "finite_machine/safety"
9
10
  require "finite_machine/thread_context"
10
11
  require "finite_machine/callable"
11
12
  require "finite_machine/catchable"
12
13
  require "finite_machine/async_proxy"
13
14
  require "finite_machine/async_call"
15
+ require "finite_machine/hook_event"
14
16
  require "finite_machine/event"
15
17
  require "finite_machine/event_queue"
16
18
  require "finite_machine/hooks"
@@ -34,10 +36,6 @@ module FiniteMachine
34
36
 
35
37
  ANY_EVENT = :any
36
38
 
37
- ANY_STATE_HOOK = :state
38
-
39
- ANY_EVENT_HOOK = :event
40
-
41
39
  # Returned when transition has successfully performed
42
40
  SUCCEEDED = 1
43
41
 
@@ -67,6 +65,9 @@ module FiniteMachine
67
65
  # Raised when event queue is already dead
68
66
  EventQueueDeadError = Class.new(::StandardError)
69
67
 
68
+ # Raised when argument is already defined
69
+ AlreadyDefinedError = Class.new(::ArgumentError)
70
+
70
71
  Environment = Struct.new(:target)
71
72
 
72
73
  class << self
@@ -73,7 +73,7 @@ module FiniteMachine
73
73
  machine.initial_state = state
74
74
  end
75
75
  event = proc { event name, from: FiniteMachine::DEFAULT_STATE, to: state }
76
- machine.events.call(&event)
76
+ machine.events_dsl.call(&event)
77
77
  end
78
78
 
79
79
  # Attach state machine to an object
@@ -84,7 +84,7 @@ module FiniteMachine
84
84
  # @example
85
85
  # FiniteMachine.define do
86
86
  # target :red
87
- # end
87
+ # end
88
88
  #
89
89
  # @param [Object] object
90
90
  #
@@ -117,7 +117,7 @@ module FiniteMachine
117
117
  #
118
118
  # @api public
119
119
  def events(&block)
120
- machine.events.call(&block)
120
+ machine.events_dsl.call(&block)
121
121
  end
122
122
 
123
123
  # Define state machine callbacks
@@ -184,16 +184,16 @@ module FiniteMachine
184
184
  # @api public
185
185
  def event(name, attrs = {}, &block)
186
186
  sync_exclusive do
187
- _transition = Transition.new(machine, attrs.merge!(name: name))
188
- _transition.define
189
- _transition.define_state_methods
190
- _transition.define_event
187
+ attributes = attrs.merge!(name: name)
188
+ FiniteMachine::StateParser.new(attrs).parse_states do |from, to|
189
+ attributes.merge!(parsed_states: { from => to })
190
+ Transition.create(machine, attributes)
191
+ end
191
192
  end
192
193
  end
193
194
  end # EventsDSL
194
195
 
195
196
  class ErrorsDSL < GenericDSL
196
-
197
197
  # Add error handler
198
198
  #
199
199
  # @param [Array] exceptions
@@ -1,68 +1,84 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module FiniteMachine
4
- # A class responsible for event notification
4
+ # A class representing event with transitions
5
5
  class Event
6
6
  include Threadable
7
7
 
8
- MESSAGE = :trigger
8
+ # The name of this event
9
+ attr_threadsafe :name
9
10
 
10
- # Event state
11
- attr_threadsafe :state
11
+ # The state transitions for this event
12
+ attr_threadsafe :state_transitions
12
13
 
13
- # Event type
14
- attr_threadsafe :type
14
+ # The reference to the state machine for this event
15
+ attr_threadsafe :machine
15
16
 
16
- # Data associated with the event
17
- attr_threadsafe :data
18
-
19
- # Transition associated with the event
20
- attr_threadsafe :transition
21
-
22
- def initialize(state, transition, *data, &block)
23
- @state = state
24
- @transition = transition
25
- @data = *data
26
- @type = self.class.event_name
17
+ # @api private
18
+ def initialize(machine, attrs = {})
19
+ @machine = machine
20
+ @name = attrs.fetch(:name, DEFAULT_STATE)
21
+ @state_transitions = []
22
+ # TODO: add event conditions
27
23
  end
28
24
 
29
- def notify(subscriber, *args, &block)
30
- if subscriber.respond_to? MESSAGE
31
- subscriber.public_send(MESSAGE, self, *args, &block)
25
+ # Add transition for this event
26
+ #
27
+ # @param [FiniteMachine::Transition] transition
28
+ #
29
+ # @example
30
+ # event << FiniteMachine::Transition.new machine, :a => :b
31
+ #
32
+ # @return [nil]
33
+ #
34
+ # @api public
35
+ def <<(transition)
36
+ sync_exclusive do
37
+ Array(transition).flatten.each { |trans| state_transitions << trans }
32
38
  end
33
39
  end
40
+ alias_method :add, :<<
41
+
42
+ # Find next transition
43
+ #
44
+ # @return [FiniteMachine::Transition]
45
+ #
46
+ # @api private
47
+ def next_transition
48
+ state_transitions.find do |transition|
49
+ transition.from_state == machine.current ||
50
+ transition.from_state == ANY_STATE
51
+ end || state_transitions.first
52
+ end
34
53
 
35
- class Anystate < Event; end
36
-
37
- class Enterstate < Anystate; end
38
-
39
- class Transitionstate < Anystate; end
40
-
41
- class Exitstate < Anystate; end
42
-
43
- class Anyaction < Event; end
44
-
45
- class Enteraction < Anyaction; end
46
-
47
- class Transitionaction < Anyaction; end
48
-
49
- class Exitaction < Anyaction; end
50
-
51
- EVENTS = Anystate, Enterstate, Transitionstate, Exitstate,
52
- Anyaction, Enteraction, Transitionaction, Exitaction
53
-
54
- def self.event_name
55
- name.split('::').last.downcase.to_sym
54
+ # Trigger this event
55
+ #
56
+ # @return [nil]
57
+ #
58
+ # @api public
59
+ def call(*args, &block)
60
+ sync_exclusive do
61
+ _transition = next_transition
62
+ machine.send(:transition, _transition, *args, &block)
63
+ end
56
64
  end
57
65
 
58
- def self.to_s
59
- event_name
66
+ # Return event name
67
+ #
68
+ # @return [String]
69
+ #
70
+ # @api public
71
+ def to_s
72
+ name.to_s
60
73
  end
61
74
 
62
- EVENTS.each do |event|
63
- (class << self; self; end).class_eval do
64
- define_method(event.event_name) { event }
65
- end
75
+ # Return string representation
76
+ #
77
+ # @return [String]
78
+ #
79
+ # @api public
80
+ def inspect
81
+ "<##{self.class} @name=#{@name}, @transitions=#{state_transitions.inspect}>"
66
82
  end
67
83
  end # Event
68
84
  end # FiniteMachine