simplificator-fsm 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.markdown CHANGED
@@ -5,6 +5,7 @@ FSM is a simple finite state machine
5
5
  ## Usage
6
6
  class Water
7
7
  include FSM
8
+ # The state machine is specified as a block in define_fsm.
8
9
  define_fsm do
9
10
  # now define all the states
10
11
  # you can add :enter / :exit callbacks (callback can be a String, Symbol or Proc)
@@ -14,12 +15,12 @@ FSM is a simple finite state machine
14
15
  state(:liquid)
15
16
  state(:solid, :enter => :on_enter_solid, :exit => :on_exit_solid)
16
17
 
17
- # define all valid transitions (name, from, to)
18
+ # define all valid transitions (arguments are name of transition, from state name, to state name)
18
19
  # you can define callbacks which are called only on this transition
19
- transition(:heat, :solid, :liquid)
20
- transition(:heat, :liquid, :gas) # look mam.... two transitions with same name
21
- transition(:cooldown, :gas, :liquid)
22
- transition(:cooldown, :liquid, :solid)
20
+ transition(:heat_up, :solid, :liquid, :event => :liquified)
21
+ transition(:heat_up, :liquid, :gas) # look mam.... two transitions with same name
22
+ transition(:cool_down, :gas, :liquid)
23
+ transition(:cool_down, :liquid, :solid)
23
24
 
24
25
  # define the attribute which is used to store the state (defaults to :state)
25
26
  state(:state_of_material)
@@ -27,6 +28,19 @@ FSM is a simple finite state machine
27
28
  # define the initial state (defaults to the first state defined - :gas in this sample)
28
29
  initial(:liquid)
29
30
  end
31
+
32
+ private
33
+ # callbacks here...
34
+ def ...
30
35
  end
36
+
37
+ # then you can call these methods
38
+ w = Water.new
39
+ w.heat # the name of the transition is the name of the method
40
+ w.reachable_state_names
41
+ w.available_transition_names
42
+ w.cool_down # again... it's the name of the transition
43
+ w.state_of_material
44
+
31
45
  ## Copyright
32
46
  Copyright (c) 2009 simplificator GmbH. See LICENSE for details.
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
3
  :minor: 2
4
- :patch: 1
4
+ :patch: 2
data/lib/fsm/builder.rb CHANGED
@@ -16,6 +16,7 @@ module FSM
16
16
  def process(&block)
17
17
  raise ArgumentError.new('Block expected') unless block_given?
18
18
  self.instance_eval(&block)
19
+ @machine.build_transition_methods
19
20
  @machine
20
21
  end
21
22
 
data/lib/fsm/machine.rb CHANGED
@@ -1,10 +1,11 @@
1
1
  module FSM
2
2
  class Machine
3
- attr_accessor(:initial_state_name, :current_state_attribute_name, :states)
3
+ attr_accessor(:initial_state_name, :current_state_attribute_name, :states, :transitions)
4
4
 
5
5
  def initialize(target_class)
6
6
  @target_class = target_class
7
- self.states = {}
7
+ self.states = []
8
+ self.transitions = []
8
9
  self.current_state_attribute_name = :state
9
10
  end
10
11
 
@@ -17,29 +18,24 @@ module FSM
17
18
  end
18
19
 
19
20
  def state(name, options = {})
20
- raise "State is already defined: '#{name}'" if self.states[name]
21
- self.states[name] = State.new(name, options)
21
+ raise "State is already defined: '#{name}'" if self.state_for_name(name, true)
22
+ self.states << State.new(name, options)
22
23
  self.initial_state_name=(name) unless self.initial_state_name
23
24
  end
24
25
 
25
26
  def initial_state_name=(value)
26
- raise UnknownState.new("Unknown state '#{value}'. Define states first with state(name)") unless self.states[value]
27
+ raise UnknownState.new("Unknown state '#{value}'. Define states first with state(name)") unless self.state_for_name(value, true)
27
28
  @initial_state_name = value
28
29
  end
29
30
 
30
31
  def transition(name, from_name, to_name, options = {})
31
32
  raise ArgumentError.new("name, from_name and to_name are required") if name.nil? || from_name.nil? || to_name.nil?
32
- raise UnknownState.new("Unknown source state '#{from}'") unless self.states[from_name]
33
- raise UnknownState.new("Unknown target state '#{to}'") unless self.states[to_name]
34
-
35
- from_state = self.states[from_name]
36
- to_state = self.states[to_name]
37
33
 
34
+ from_state = self.state_for_name(from_name)
35
+ to_state = self.state_for_name(to_name)
38
36
  transition = Transition.new(name, from_state, to_state, options)
39
37
  from_state.add_transition(transition)
40
-
41
- define_transition_method(name)
42
-
38
+ self.transitions << transition
43
39
  end
44
40
 
45
41
  def self.get_current_state_name(target)
@@ -55,24 +51,45 @@ module FSM
55
51
  def reachable_states(target)
56
52
  reachables = []
57
53
  current_state_name = Machine.get_current_state_name(target)
58
- self.states.map do |name, state|
54
+ self.states.map do |state|
59
55
  reachables += state.to_states if state.name == current_state_name
60
56
  end
61
57
  reachables
62
58
  end
63
59
 
64
60
  def available_transitions(target)
65
- self.states[Machine.get_current_state_name(target)].transitions.values
61
+ current_state_name = Machine.get_current_state_name(target)
62
+ state = state_for_name(current_state_name)
63
+ state.transitions.values
64
+ end
65
+
66
+
67
+ def build_transition_methods
68
+ names = self.transitions.map() {|transition| transition.name}.uniq
69
+ names.each do |name|
70
+ define_transition_method(name)
71
+ end
72
+ end
73
+
74
+ # Lookup a State by it's name
75
+ # raises ArgumentError if state can not be found unless quiet is set to true
76
+ def state_for_name(name, quiet = false)
77
+ state = self.states.detect() {|state| state.name == name}
78
+ raise ArgumentError.new("Unknonw state '#{name}'") unless quiet || state
79
+ state
66
80
  end
67
81
 
68
82
  private
69
83
 
84
+ # defines a transition method on the target class.
85
+ # this is the method triggering the state change.
86
+ #
70
87
  def define_transition_method(name)
71
88
  @target_class.instance_eval do
72
89
  define_method(name) do |*args|
73
90
  machine = Machine[self.class]
74
91
  from_name = Machine.get_current_state_name(self)
75
- from_state = machine.states[from_name]
92
+ from_state = machine.state_for_name(from_name)
76
93
 
77
94
  entry = from_state.transitions.detect() {|to_name, tr| tr.name == name}
78
95
  transition = entry.last if entry
@@ -83,19 +100,7 @@ module FSM
83
100
  transition.fire_event(self, args)
84
101
  to_state.enter(self)
85
102
  Machine.set_current_state_name(self, to_state.name)
86
- true
87
-
88
-
89
-
90
- #to_state = machine.states[to_name]
91
- #transition = from_state.transitions[to_name]
92
- #raise InvalidStateTransition.new("No transition defined from #{from_name} -> #{to_name}") unless transition
93
-
94
- #from_state.exit(self)
95
- #transition.fire_event(self, args)
96
- #to_state.enter(self)
97
- #Machine.set_current_state_name(self, to_name)
98
- #true # at the moment always return true ... as soon as we have guards or thelike this could be false as well
103
+ true # at the moment always return true ... as soon as we have guards or thelike this could be false as well
99
104
  end
100
105
  end
101
106
  end
data/lib/fsm/options.rb CHANGED
@@ -12,6 +12,5 @@ module FSM
12
12
  end
13
13
  end
14
14
  end
15
-
16
15
  end
17
16
  end
@@ -2,37 +2,43 @@ require 'test_helper'
2
2
 
3
3
  class TransitionTest < Test::Unit::TestCase
4
4
  context 'Initializer' do
5
+
6
+
5
7
  should 'require name, from and to' do
8
+ bli_state = FSM::State.new('bli')
9
+ blo_state = FSM::State.new('blo')
6
10
  assert_raise(ArgumentError) do
7
11
  FSM::Transition.new(nil, nil, nil)
8
12
  end
9
13
  assert_raise(ArgumentError) do
10
- FSM::Transition.new('bla', nil, nil)
14
+ FSM::Transition.new(:name, nil, nil)
11
15
  end
12
16
  assert_raise(ArgumentError) do
13
- FSM::Transition.new(nil, 'bli', nil)
17
+ FSM::Transition.new(nil, bli_state, nil)
14
18
  end
15
19
  assert_raise(ArgumentError) do
16
- FSM::Transition.new(nil, nil, 'blo')
20
+ FSM::Transition.new(nil, nil, blo_state)
17
21
  end
18
22
  assert_raise(ArgumentError) do
19
- FSM::Transition.new('bli', 'bli', nil)
23
+ FSM::Transition.new(:name, bli_state, nil)
20
24
  end
21
25
  assert_raise(ArgumentError) do
22
- FSM::Transition.new(nil, 'blo', 'blo')
26
+ FSM::Transition.new(nil, blo_state, blo_state)
23
27
  end
24
28
  assert_raise(ArgumentError) do
25
- FSM::Transition.new('blo', nil, 'bli')
29
+ FSM::Transition.new(:name, nil, bli_state)
26
30
  end
27
31
 
28
- FSM::Transition.new('bla', 'bli', 'blo')
32
+ FSM::Transition.new(:name, bli_state, blo_state)
29
33
  end
30
34
 
31
35
  should 'allow only valid options' do
36
+ bli_state = FSM::State.new('bli')
37
+ blo_state = FSM::State.new('blo')
32
38
  assert_raise(ArgumentError) do
33
- FSM::Transition.new('bla', 'bli', 'blo', :foo => 12)
39
+ FSM::Transition.new(:name, bli_state, blo_state, :foo => 12)
34
40
  end
35
- FSM::Transition.new('bla', 'bli', 'blo', :event => :some)
41
+ FSM::Transition.new(:name, bli_state, blo_state, :event => :some)
36
42
  end
37
43
  end
38
44
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simplificator-fsm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - simplificator
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-05-18 00:00:00 -07:00
12
+ date: 2009-05-19 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15