simplificator-fsm 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
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