finite_machine 0.9.0 → 0.9.1

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: 605485a3bc03f37c758232982a53c6a1454f3f75
4
- data.tar.gz: ea94531b99ed2901d14a801388ab26e220f0b6df
3
+ metadata.gz: 5ce0812460235da2deebb8aafc2d542b33392804
4
+ data.tar.gz: 89bb6ab79f200d7efaf004ffe9704bbbeb86b5c0
5
5
  SHA512:
6
- metadata.gz: ac08b6ac004a47a21c6ff3c104b470ebb50b083c3f692f6ce5d865ed2b6527cf0e58dce4983bfd08ed2d000ea5b80277895befada19e26e326506b6326d1bf1f
7
- data.tar.gz: b00888c384d18a8f1654b11b26e53a6024c8da9127139ee4f6e3f906c1c0a03f7a5f85982f2af28f494b6097d4ddcdce86401f6bf532ead9a7bda2084418c01c
6
+ metadata.gz: ed665ca255296e317a683106444e9e4ebc8044f927ea9067c92709fe777787de8b26cfa969e5c42bb2089f16c184d69774729fb06cd6c26d93b645dd1d96b133
7
+ data.tar.gz: 88538af514156c8a5e899b876e747a0716ab4439c271395418364830cc3372bafcbc772383aaeb3cbecf75c07b0e4087961092eec824c42709d1e40656cfd75f
@@ -1,3 +1,11 @@
1
+ 0.9.1 (August 10, 2014)
2
+
3
+ * Add TransitionBuilder to internally build transitions from states
4
+ * Fix #choice to allow for multiple from states
5
+ * Add #current? to Transition to determine if matches from state
6
+ * Add #select_choice_transition to EventsChain to determine matching choice transition
7
+ * Fix #choice to work with same named events
8
+
1
9
  0.9.0 (August 3, 2014)
2
10
 
3
11
  * Add Definition class to allow to define standalone state machine
data/README.md CHANGED
@@ -55,15 +55,18 @@ Or install it yourself as:
55
55
  * [2.1 Performing transitions](#21-performing-transitions)
56
56
  * [2.2 Forcing transitions](#22-forcing-transitions)
57
57
  * [2.3 Asynchronous transitions](#23-asynchronous-transitions)
58
- * [2.4 Single event with multiple from states](#24-single-event-with-multiple-from-states)
59
- * [2.5 Grouping states under single event](#25-grouping-states-under-single-event)
60
- * [2.6 Silent transitions](#26-silent-transitions)
58
+ * [2.4 Multiple from states](#24-multiple-from-states)
59
+ * [2.5 From :any state](#25-from-any-state)
60
+ * [2.6 Grouping states under single event](#26-grouping-states-under-single-event)
61
+ * [2.7 Silent transitions](#27-silent-transitions)
61
62
  * [3. Conditional transitions](#3-conditional-transitions)
62
63
  * [3.1 Using a Proc](#31-using-a-proc)
63
64
  * [3.2 Using a Symbol](#32-using-a-symbol)
64
65
  * [3.3 Using a String](#33-using-a-string)
65
66
  * [3.4 Combining transition conditions](#34-combining-transition-conditions)
66
67
  * [4. Choice pseudostates](#4-choice-pseudostates)
68
+ * [4.1 Dynamic choice conditions](#41-dynamic-choice-conditions)
69
+ * [4.2 Multiple from states](#42-multiple-from-states)
67
70
  * [5. Callbacks](#5-callbacks)
68
71
  * [5.1 on_enter](#51-on_enter)
69
72
  * [5.2 on_transition](#52-on_transition)
@@ -436,7 +439,7 @@ In order to fire the event transition asynchronously use the `async` scope like
436
439
  fm.async.ready # => executes in separate Thread
437
440
  ```
438
441
 
439
- ### 2.4 Single event with multiple from states
442
+ ### 2.4 Multiple from states
440
443
 
441
444
  If an event transitions from multiple states to the same state then all the states can be grouped into an array.
442
445
  Alternatively, you can create separate events under the same name for each transition that needs combining.
@@ -455,7 +458,29 @@ fm = FiniteMachine.define do
455
458
  end
456
459
  ```
457
460
 
458
- ### 2.5 Grouping states under single event
461
+ ### 2.5 From :any state
462
+
463
+ The **FiniteMachine** offers few ways to transition out of any state. This is parrticularly useful when the machine already defines many states.
464
+
465
+ You can pass `:any` for the name of the state, for instance:
466
+
467
+ ```ruby
468
+ event :run, from: :any, to: :green
469
+
470
+ or
471
+
472
+ event :run, :any => :green
473
+ ```
474
+
475
+ Alternatively, you can skip the `:any` state definition and just specify `to` state:
476
+
477
+ ```ruby
478
+ event :run, to: :green
479
+ ```
480
+
481
+ All the above `run` event definitions will always transition the state machine into `:green` state.
482
+
483
+ ### 2.6 Grouping states under single event
459
484
 
460
485
  Another way to specify state transitions under single event name is to group all your state transitions into a single hash like so:
461
486
 
@@ -483,7 +508,7 @@ fm = FiniteMachine.define do
483
508
  }
484
509
  ```
485
510
 
486
- ### 2.6 Silent transitions
511
+ ### 2.7 Silent transitions
487
512
 
488
513
  The **FiniteMachine** allows to selectively silence events and thus prevent any callbacks from firing. Using the `silent` option passed to event definition like so:
489
514
 
@@ -655,6 +680,8 @@ fsm.next
655
680
  fsm.current # => :red
656
681
  ```
657
682
 
683
+ ### 4.1 Dynamic choice conditions
684
+
658
685
  Just as with event conditions you can make conditional logic dynamic and dependent on parameters passed in:
659
686
 
660
687
  ```ruby
@@ -677,6 +704,38 @@ fsm.current # => :yellow
677
704
 
678
705
  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.
679
706
 
707
+ ### 4.2 Multiple from states
708
+
709
+ Similarly to event definitions, you can specify the event to transition from a group of states:
710
+
711
+ ```ruby
712
+ FiniteMachine.define do
713
+ initial :red
714
+
715
+ events {
716
+ event :next, from: [:yellow, :red] do
717
+ choice :pink, if: -> { false }
718
+ choice :green
719
+ end
720
+ }
721
+ end
722
+ ```
723
+
724
+ or from any state using the `:any` state name like so:
725
+
726
+ ```ruby
727
+ FiniteMachine.define do
728
+ initial :red
729
+
730
+ events {
731
+ event :next, from: :any do
732
+ choice :pink, if: -> { false }
733
+ choice :green
734
+ end
735
+ }
736
+ end
737
+ ```
738
+
680
739
  ## 5 Callbacks
681
740
 
682
741
  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**:
@@ -22,6 +22,7 @@ require "finite_machine/events_chain"
22
22
  require "finite_machine/hooks"
23
23
  require "finite_machine/logger"
24
24
  require "finite_machine/transition"
25
+ require "finite_machine/transition_builder"
25
26
  require "finite_machine/transition_event"
26
27
  require "finite_machine/dsl"
27
28
  require "finite_machine/definition"
@@ -36,8 +36,8 @@ module FiniteMachine
36
36
  def choice(to, attrs = {})
37
37
  opts = options.dup
38
38
  opts.merge!(attrs)
39
- opts.merge!(parsed_states: { options[:from] => to })
40
- Transition.create(context.machine, opts)
39
+ transition_builder = TransitionBuilder.new(context.machine, opts)
40
+ transition_builder.call(options[:from] => to)
41
41
  end
42
42
  alias_method :default, :choice
43
43
  end # ChoiceMerger
@@ -226,14 +226,12 @@ module FiniteMachine
226
226
  def event(name, attrs = {}, &block)
227
227
  sync_exclusive do
228
228
  attributes = attrs.merge!(name: name)
229
- FiniteMachine::StateParser.new(attrs).parse_states do |from, to|
230
- if block_given?
231
- merger = ChoiceMerger.new(self, attributes)
232
- merger.instance_eval(&block)
233
- else
234
- attributes.merge!(parsed_states: { from => to })
235
- Transition.create(machine, attributes)
236
- end
229
+ if block_given?
230
+ merger = ChoiceMerger.new(self, attributes)
231
+ merger.instance_eval(&block)
232
+ else
233
+ transition_builder = TransitionBuilder.new(machine, attributes)
234
+ transition_builder.call(attrs)
237
235
  end
238
236
  end
239
237
  end
@@ -56,10 +56,8 @@ module FiniteMachine
56
56
  # @api private
57
57
  def next_transition
58
58
  sync_shared do
59
- state_transitions.find do |transition|
60
- transition.from_state == machine.current ||
61
- transition.from_state == ANY_STATE
62
- end || state_transitions.first
59
+ state_transitions.find { |transition| transition.current? } ||
60
+ state_transitions.first
63
61
  end
64
62
  end
65
63
 
@@ -72,7 +70,9 @@ module FiniteMachine
72
70
  # @api private
73
71
  def find_transition(*args)
74
72
  sync_shared do
75
- state_transitions.find { |trans| trans.check_conditions(*args) }
73
+ state_transitions.find do |trans|
74
+ trans.current? && trans.check_conditions(*args)
75
+ end
76
76
  end
77
77
  end
78
78
 
@@ -89,11 +89,11 @@ module FiniteMachine
89
89
  # @api public
90
90
  def call(*args, &block)
91
91
  sync_exclusive do
92
- _transition = next_transition
92
+ event_transition = next_transition
93
93
  if silent
94
- _transition.call(*args, &block)
94
+ event_transition.call(*args, &block)
95
95
  else
96
- machine.send(:transition, _transition, *args, &block)
96
+ machine.send(:transition, event_transition, *args, &block)
97
97
  end
98
98
  end
99
99
  end
@@ -70,6 +70,18 @@ module FiniteMachine
70
70
  chain[name].find_transition(*args)
71
71
  end
72
72
 
73
+ # Examine choice transitions to find one matching condition
74
+ #
75
+ # @return [FiniteMachine::Transition]
76
+ # The choice transition that matches
77
+ #
78
+ # @api public
79
+ def select_choice_transition(name, *args, &block)
80
+ chain[name].state_transitions.find do |trans|
81
+ trans.check_conditions(*args, &block)
82
+ end
83
+ end
84
+
73
85
  # Check if any of the transition constraints passes
74
86
  #
75
87
  # @param [Symbol] name
@@ -45,7 +45,8 @@ module FiniteMachine
45
45
 
46
46
  def_delegator :@events_dsl, :event
47
47
 
48
- def_delegators :@events_chain, :check_choice_conditions, :select_transition
48
+ def_delegators :@events_chain, :check_choice_conditions, :select_transition,
49
+ :select_choice_transition
49
50
 
50
51
  # Initialize state machine
51
52
  #
@@ -78,7 +78,7 @@ module FiniteMachine
78
78
  # @api public
79
79
  def to_state(*args)
80
80
  if transition_choice?
81
- found_trans = machine.select_transition(name, *args)
81
+ found_trans = machine.select_choice_transition(name, *args)
82
82
  found_trans.map[from_state]
83
83
  else
84
84
  machine.transitions[name][from_state]
@@ -116,13 +116,29 @@ module FiniteMachine
116
116
  map[state] == state || (map[ANY_STATE] == state && from_state == state)
117
117
  end
118
118
 
119
+ # Check if from matches current state
120
+ #
121
+ # @example
122
+ # transition.current? # => true
123
+ #
124
+ # @return [Boolean]
125
+ # Return true if match is found, false otherwise.
126
+ #
127
+ # @api public
128
+ def current?
129
+ [machine.current, ANY_STATE].any? { |state| state == from_state }
130
+ end
131
+
119
132
  # Check if this transition has branching choice or not
120
133
  #
121
134
  # @return [Boolean]
122
135
  #
123
136
  # @api public
124
137
  def transition_choice?
125
- machine.transitions[name][from_state].is_a?(Array)
138
+ matching = machine.transitions[name]
139
+ [matching[from_state], matching[ANY_STATE]].any? do |match|
140
+ match.is_a?(Array)
141
+ end
126
142
  end
127
143
 
128
144
  # Check if transition can be performed according to constraints
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ module FiniteMachine
4
+ # A class reponsible for building transition out of parsed states
5
+ class TransitionBuilder
6
+ include Threadable
7
+
8
+ # The current state machine
9
+ attr_threadsafe :machine
10
+
11
+ attr_threadsafe :attributes
12
+
13
+ # Initialize a TransitionBuilder
14
+ #
15
+ # @example
16
+ # TransitionBuilder.new(machine, {})
17
+ #
18
+ # @api public
19
+ def initialize(machine, attributes = {})
20
+ @machine = machine
21
+ @attributes = attributes
22
+ end
23
+
24
+ # Creates transitions for the states
25
+ #
26
+ # @example
27
+ # transition_parser.call([:green, :yellow] => :red)
28
+ #
29
+ # @param [Hash[Symbol]] states
30
+ # The states to extract
31
+ #
32
+ # @return [nil]
33
+ #
34
+ # @api public
35
+ def call(states)
36
+ FiniteMachine::StateParser.new(states).parse_states do |from, to|
37
+ attributes.merge!(parsed_states: { from => to })
38
+ Transition.create(machine, attributes)
39
+ end
40
+ end
41
+ end # TransitionBuilder
42
+ end # FiniteMachine
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module FiniteMachine
4
- VERSION = "0.9.0"
4
+ VERSION = "0.9.1"
5
5
  end
@@ -134,4 +134,54 @@ describe FiniteMachine, '#choice' do
134
134
  fsm.next
135
135
  expect(fsm.current).to eq(:company_form)
136
136
  end
137
+
138
+ it "allows to transition from multiple states to choice pseudostate" do
139
+ fsm = FiniteMachine.define do
140
+ initial :red
141
+
142
+ event :go, from: [:yellow, :red] do
143
+ choice :pink, if: -> { false }
144
+ choice :green
145
+ end
146
+ end
147
+ expect(fsm.current).to eq(:red)
148
+ fsm.go
149
+ expect(fsm.current).to eq(:green)
150
+ fsm.restore!(:yellow)
151
+ expect(fsm.current).to eq(:yellow)
152
+ fsm.go
153
+ expect(fsm.current).to eq(:green)
154
+ end
155
+
156
+ it "allows to transition from any state to choice pseudo state" do
157
+ fsm = FiniteMachine.define do
158
+ initial :red
159
+
160
+ event :go, from: :any do
161
+ choice :pink, if: -> { false }
162
+ choice :green
163
+ end
164
+ end
165
+ expect(fsm.current).to eq(:red)
166
+ fsm.go
167
+ expect(fsm.current).to eq(:green)
168
+ end
169
+
170
+ it "groups correctly events under the same name" do
171
+ fsm = FiniteMachine.define do
172
+ initial :red
173
+
174
+ event :next, from: :yellow, to: :green
175
+
176
+ event :next, from: :red do
177
+ choice :pink, if: -> { false }
178
+ choice :yellow
179
+ end
180
+ end
181
+ expect(fsm.current).to eq(:red)
182
+ fsm.next
183
+ expect(fsm.current).to eq(:yellow)
184
+ fsm.next
185
+ expect(fsm.current).to eq(:green)
186
+ end
137
187
  end
@@ -8,11 +8,11 @@ describe FiniteMachine::Event, '#next_transition' do
8
8
  subject(:event) { object.new(machine, name: :test) }
9
9
 
10
10
  describe "matches transition by name" do
11
- let(:machine) { double(:machine, current: :b) }
11
+ let(:machine) { double(:machine) }
12
12
 
13
13
  it "finds matching transition" do
14
- transition_a = double(:transition_a, from_state: :a)
15
- transition_b = double(:transition_b, from_state: :b)
14
+ transition_a = double(:transition_a, current?: false)
15
+ transition_b = double(:transition_b, current?: true)
16
16
  event << transition_a
17
17
  event << transition_b
18
18
 
@@ -20,25 +20,12 @@ describe FiniteMachine::Event, '#next_transition' do
20
20
  end
21
21
  end
22
22
 
23
- describe "matches :any transition" do
24
- let(:machine) { double(:machine, current: :any) }
25
-
26
- it "finds matching transition" do
27
- transition_a = double(:transition_a, from_state: :a)
28
- transition_any = double(:transition_any, from_state: :any)
29
- event << transition_a
30
- event << transition_any
31
-
32
- expect(event.next_transition).to eq(transition_any)
33
- end
34
- end
35
-
36
23
  describe "fails to find" do
37
- let(:machine) { double(:machine, current: :c) }
24
+ let(:machine) { double(:machine) }
38
25
 
39
26
  it "choses first available transition" do
40
- transition_a = double(:transition_a, from_state: :a)
41
- transition_b = double(:transition_b, from_state: :b)
27
+ transition_a = double(:transition_a, current?: false)
28
+ transition_b = double(:transition_b, current?: false)
42
29
  event << transition_a
43
30
  event << transition_b
44
31
 
@@ -111,6 +111,29 @@ describe FiniteMachine, 'events' do
111
111
  expect(fsm.current).to eql(:green)
112
112
  end
113
113
 
114
+ it "permits event from any state for hash syntax" do
115
+ fsm = FiniteMachine.define do
116
+ initial :red
117
+
118
+ events {
119
+ event :start, :red => :yellow
120
+ event :run, :yellow => :green
121
+ event :stop, :green => :red
122
+ event :go, :any => :green
123
+ }
124
+ end
125
+
126
+ expect(fsm.current).to eql(:red)
127
+
128
+ fsm.go
129
+ expect(fsm.current).to eql(:green)
130
+ fsm.stop
131
+ fsm.start
132
+ expect(fsm.current).to eql(:yellow)
133
+ fsm.go
134
+ expect(fsm.current).to eql(:green)
135
+ end
136
+
114
137
  it "permits event from any state without 'from'" do
115
138
  fsm = FiniteMachine.define do
116
139
  initial :green
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: finite_machine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Murach
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-03 00:00:00.000000000 Z
11
+ date: 2014-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -70,6 +70,7 @@ files:
70
70
  - lib/finite_machine/thread_context.rb
71
71
  - lib/finite_machine/threadable.rb
72
72
  - lib/finite_machine/transition.rb
73
+ - lib/finite_machine/transition_builder.rb
73
74
  - lib/finite_machine/transition_event.rb
74
75
  - lib/finite_machine/version.rb
75
76
  - spec/spec_helper.rb