hifsm 0.2.0 → 0.3.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 25b5e3332cdc03d7cdc7efee6fa52b83a2a85881
4
- data.tar.gz: 52f1ffd9779265883292b018ed76ee480032cbf5
3
+ metadata.gz: 2dd540fa33580b8e82c96d82a1746aa5016ab3c6
4
+ data.tar.gz: ab0492f94873e14a2f75d1e3fd32ae764ed652c1
5
5
  SHA512:
6
- metadata.gz: ecec06c7780e4ab19a6c98fa6191d5a3aeea238ba5c790d0b9a3a02a87b68838ec36c166bbdec15f8d75f9d125d0197f6b79d1833f2e1bcb73f96b110df702e2
7
- data.tar.gz: 4e113ea01e29154d6cf2b62a08243a0b7a4054a9c254f59dffc468874f94b843e94c7586de327d0743dfda2c2f98c0e6f775c6168a968011ec3ef62f9d3d20c7
6
+ metadata.gz: 258cd2e2f5a18942a0ee40a96779246ba0b99adc4659834460ac482f96882cef9c2d5ca4f7e49301df4cb759ea12f4969a8f2aeb00554af5f092664c419b089e
7
+ data.tar.gz: 56cf2f726e53af3fdfa98067030448cbcd86a9306937e30d3ab75ab0c35bfc24246371df1fb1b7e202c7ccf54a443762bb1e126582d2462f36c5139438a244f7
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 2.1.0
5
+ gemfile:
6
+ - Gemfile
7
+ script: "bundle exec rake test"
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Hierarchical Finite State Machine in Ruby
2
2
 
3
+ [![Build Status](https://travis-ci.org/stiff/hifsm.svg?branch=master)](https://travis-ci.org/stiff/hifsm)
4
+ [![Coverage Status](https://coveralls.io/repos/stiff/hifsm/badge.png?branch=master)](https://coveralls.io/r/stiff/hifsm?branch=master)
5
+
3
6
  This library was created from the desire to have nested states inspired by [rFSM](https://github.com/kmarkus/rFSM).
4
7
 
5
8
  It can be used in plain old ruby objects, but works well with `ActiveRecord`s too.
@@ -39,7 +42,7 @@ Here is how to use it to model a monster in a Quake-like game. It covers most Hi
39
42
  require 'hifsm'
40
43
 
41
44
  class Monster
42
- @@fsm = Hifsm::FSM.new do
45
+ include Hifsm.fsm_module {
43
46
  state :idle, :initial => true
44
47
  state :attacking do
45
48
  state :acquiring_target, :initial => true do
@@ -89,22 +92,20 @@ class Monster
89
92
  self.target = nil
90
93
  end
91
94
  end
92
- end
95
+ }
93
96
 
94
97
  attr_accessor :target, :low_hp, :debug
95
- attr_reader :state
96
98
 
97
99
  def initialize
98
100
  @debug = false
99
101
  @home = 'home'
100
- @state = @@fsm.instantiate(self) # or @@fsm.instantiate(self, 'attacking.pursuing')
101
102
  @tick = 1
102
103
  @low_hp = false
103
104
  end
104
105
 
105
106
  def act!
106
- debug && puts("Acting @#{@state}")
107
- @state.act!(@tick)
107
+ debug && puts("Acting @#{state}")
108
+ state_machine.act! @tick
108
109
  @tick = @tick + 1
109
110
  end
110
111
 
@@ -132,35 +133,43 @@ class Monster
132
133
  end
133
134
 
134
135
  ogre = Monster.new
135
- ogre.debug = true # Console output:
136
- ogre.act! # -> Acting @idle
137
- ogre.sight 'player' # -> Setting target to player
138
- ogre.act! # -> Acting @attacking.acquiring_target
139
- # -> planning...
140
- # -> AARGHH!
136
+ ogre.debug = true ### Console output:
137
+ ogre.act! # Acting @idle
138
+ ogre.sight 'player' # Setting target to player
139
+ ogre.act! # Acting @attacking.acquiring_target
140
+ # 2: Attack! <- parent state act! first
141
+ # planning...
142
+ # AARGHH!
141
143
  # ogre.acquire -> Hifsm::MissingTransition, already acquired in act!
142
- ogre.act! # -> Acting @attacking.pursuing
143
- # -> step step player
144
- ogre.enemy_dead # -> Woohoo!
145
- ogre.act! # -> Acting @coming_back
146
-
147
- ogre.sight 'player2' # -> Setting target to player2
148
- ogre.acquire # -> AARGHH!
149
- ogre.act! # -> Acting @attacking.pursuing
150
- # -> step step player2
144
+ ogre.act! # Acting @attacking.pursuing
145
+ # 3: Attack!
146
+ # step step player
147
+ ogre.enemy_dead # Woohoo!
148
+ ogre.act! # Acting @coming_back
149
+ # step step home
150
+
151
+ ogre.sight 'player2' # Setting target to player2
152
+ ogre.acquire # AARGHH!
153
+ ogre.act! # Acting @attacking.pursuing
154
+ # 5: Attack!
155
+ # step step player2
151
156
  ogre.reached
152
- puts ogre.state # -> attacking.fighting
153
- ogre.act! # -> ~~> player2
154
- 5.times { ogre.act! } # -> ...
155
- ogre.enemy_dead # -> Woohoo!
156
- ogre.act! # -> Acting @coming_back
157
- # -> step step home
157
+ puts ogre.state # attacking.fighting
158
+ ogre.act! # Acting @attacking.fighting
159
+ # 6: Attack!
160
+ # ~~> player2
161
+ 5.times { ogre.act! } # ...
162
+ ogre.enemy_dead # Woohoo!
163
+ ogre.act! # Acting @coming_back
164
+ # step step home
158
165
  ogre.low_hp = true
159
166
  ogre.sight 'player3'
160
- ogre.act! # -> Acting @runaway
167
+ ogre.act! # Acting @runaway
161
168
 
162
169
  ```
163
170
 
171
+ Note the use of `{..}` construct instead of `do..end` in `include`. `do..end` is treated as block for include itself, instead of `fsm_module`.
172
+
164
173
  ## Guards
165
174
 
166
175
  Events are tried in order they were defined, if guard callback returns `false` then event is skipped.
@@ -181,6 +190,29 @@ If any of `before...` callbacks returns `false` then no further processing is do
181
190
 
182
191
  On `act!` just calls action block if it was given.
183
192
 
193
+ ## ActiveRecord integration
194
+
195
+ Add column to your database which would hold the state, and then:
196
+
197
+ ```ruby
198
+ class Order < ActiveRecord::Base
199
+ hifsm do
200
+ state :draft, :initial => true
201
+ state :processing do
202
+ state :packaging, :initial => true
203
+ state :delivering
204
+ end
205
+ state :done
206
+ state :cancelled
207
+
208
+ event :start_processing, :from => :draft, :to => :processing
209
+ event :cancel, :to => :cancelled
210
+ end
211
+ end
212
+ Order.new # draft
213
+
214
+ ```
215
+
184
216
  ## Testing
185
217
 
186
218
  Only 'public' API is unit-tested, internal implementation may be freely changed, so don't rely on it.
data/hifsm.gemspec CHANGED
@@ -21,4 +21,7 @@ Gem::Specification.new do |spec|
21
21
  spec.add_development_dependency "bundler", "~> 1.3"
22
22
  spec.add_development_dependency "rake"
23
23
  spec.add_development_dependency "minitest"
24
+ spec.add_development_dependency "coveralls"
25
+ spec.add_development_dependency "activerecord"
26
+ spec.add_development_dependency "sqlite3"
24
27
  end
@@ -0,0 +1,34 @@
1
+ module Hifsm
2
+ module Adapters
3
+ module ActiveRecordAdapter
4
+ def self.included(base)
5
+ base.class_eval do
6
+ extend ClassMethods
7
+ end
8
+ end
9
+
10
+ module ClassMethods
11
+ def hifsm(column, &block)
12
+ include Hifsm.fsm_module(column, &block)
13
+ before_save "hifsm_write_#{column}_attribute"
14
+
15
+ define_method "#{column}=" do |value|
16
+ raise 'not (sure will be) implemented'
17
+ end
18
+
19
+ define_method "initial_#{column}" do
20
+ read_attribute(column)
21
+ end
22
+
23
+ define_method "hifsm_write_#{column}_attribute" do
24
+ write_attribute(column, send(column))
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ ActiveRecord::Base.class_eval do
33
+ include Hifsm::Adapters::ActiveRecordAdapter
34
+ end
data/lib/hifsm/fsm.rb CHANGED
@@ -1,17 +1,26 @@
1
1
  module Hifsm
2
+
3
+ # This class holds immutable state machine definition
2
4
  class FSM
3
- attr_reader :states, :transitions
5
+ attr_reader :name, :states, :transitions
4
6
 
5
- def initialize(parent = nil, &block)
7
+ def initialize(name = :state, parent = nil, &block)
8
+ @name = name
6
9
  @parent = parent
7
10
  @states = {}
8
11
  @initial_state = nil
9
12
 
10
13
  instance_eval(&block) if block
14
+
15
+ @fsm_module = fsm_module = initialize_module
16
+ @machine_class = Class.new(Hifsm::Machine) do
17
+ include fsm_module
18
+ define_method("#{name}_machine") { self }
19
+ end
11
20
  end
12
21
 
13
22
  def instantiate(target = nil, initial_state = nil)
14
- Hifsm::Machine.new(self, target, initial_state)
23
+ @machine_class.new(self, target, initial_state)
15
24
  end
16
25
 
17
26
  def all_events
@@ -44,15 +53,48 @@ module Hifsm
44
53
  end
45
54
 
46
55
  def state(name, options = {}, &block)
47
- st = @states[name.to_s] = Hifsm::State.new(name, @parent)
56
+ st = @states[name.to_s] = Hifsm::State.new(self, name, @parent)
48
57
  @initial_state = st if options[:initial]
49
58
  st.instance_eval(&block) if block
50
59
  end
51
60
 
61
+ def to_module
62
+ @fsm_module
63
+ end
64
+
52
65
  private
53
66
  # like in ActiveSupport
54
67
  def array_wrap(anything)
55
68
  anything.is_a?(Array) ? anything : [anything].compact
56
69
  end
70
+
71
+ def initialize_module
72
+ fsm = self # capture self
73
+ machine_var = "@#{name}_machine"
74
+ machine_name = "#{name}_machine"
75
+
76
+ Module.new.module_exec do
77
+
78
+ # <state>_machine returns machine instance
79
+ define_method(machine_name) do
80
+ if instance_variable_defined?(machine_var)
81
+ instance_variable_get(machine_var)
82
+ else
83
+ machine = fsm.instantiate(self)
84
+ instance_variable_set(machine_var, machine)
85
+ end
86
+ end
87
+
88
+ # <state> returns string representation of the current state
89
+ define_method(fsm.name) { send(machine_name).to_s }
90
+
91
+ # <event> fires event
92
+ fsm.all_events.each do |event_name, event|
93
+ define_method(event_name) {|*args| send(machine_name).fire(event_name, *args) }
94
+ end
95
+
96
+ self # return module
97
+ end
98
+ end
57
99
  end
58
100
  end
data/lib/hifsm/machine.rb CHANGED
@@ -1,17 +1,16 @@
1
1
  module Hifsm
2
+ # This is just a storage of current state
2
3
  class Machine
3
4
  def initialize(fsm, target, initial_state = nil)
4
5
  @target = target || self
5
6
  @fsm = fsm
6
7
 
7
- @state = (initial_state && fsm.get_state!(initial_state) || fsm.initial_state!).enter!
8
+ initial_state_method_name = "initial_#{fsm.name}"
9
+ initial_state ||= target.send(initial_state_method_name) if target.respond_to?(initial_state_method_name)
10
+ initial_state &&= fsm.get_state!(initial_state)
11
+ initial_state ||= fsm.initial_state!
8
12
 
9
- mach = self
10
- fsm.all_events.each do |event_name, event|
11
- @target.singleton_class.instance_exec do
12
- define_method(event_name) {|*args| mach.fire(event_name, *args) }
13
- end
14
- end
13
+ @state = initial_state.enter!
15
14
  end
16
15
 
17
16
  def act!(*args)
data/lib/hifsm/state.rb CHANGED
@@ -2,7 +2,8 @@ module Hifsm
2
2
  class State
3
3
  CALLBACKS = [:before_enter, :before_exit, :after_enter, :after_exit].freeze
4
4
 
5
- def initialize(name, parent = nil)
5
+ def initialize(fsm, name, parent = nil)
6
+ @fsm = fsm
6
7
  @name = name
7
8
  @parent = parent
8
9
  @action = nil
@@ -92,7 +93,8 @@ module Hifsm
92
93
 
93
94
  private
94
95
  def sub_fsm!
95
- @sub_fsm ||= Hifsm::FSM.new(self)
96
+ # FIXME too much coupling
97
+ @sub_fsm ||= Hifsm::FSM.new(@fsm.name, self)
96
98
  end
97
99
  end
98
100
  end
data/lib/hifsm/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Hifsm
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/hifsm.rb CHANGED
@@ -18,4 +18,15 @@ module Hifsm
18
18
  end
19
19
  end
20
20
 
21
+ class <<self
22
+ def fsm_module(name = :state, &block)
23
+ FSM::new(name, &block).to_module
24
+ end
25
+ end
26
+ end
27
+
28
+ begin
29
+ require 'active_record'
30
+ require 'hifsm/adapters/active_record_adapter'
31
+ rescue LoadError
21
32
  end
data/test/monster.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'hifsm'
2
2
 
3
3
  class Monster
4
- @@fsm = Hifsm::FSM.new do
4
+ include Hifsm.fsm_module {
5
5
  state :idle, :initial => true
6
6
  state :attacking do
7
7
  state :acquiring_target, :initial => true do
@@ -51,22 +51,20 @@ class Monster
51
51
  self.target = nil
52
52
  end
53
53
  end
54
- end
54
+ }
55
55
 
56
56
  attr_accessor :target, :low_hp, :debug
57
- attr_reader :state
58
57
 
59
58
  def initialize
60
59
  @debug = false
61
60
  @home = 'home'
62
- @state = @@fsm.instantiate(self) # or @@fsm.instantiate(self, 'attacking.pursuing')
63
61
  @tick = 1
64
62
  @low_hp = false
65
63
  end
66
64
 
67
65
  def act!
68
- debug && puts("Acting @#{@state}")
69
- @state.act!(@tick)
66
+ debug && puts("Acting @#{state}")
67
+ state_machine.act! @tick
70
68
  @tick = @tick + 1
71
69
  end
72
70
 
@@ -95,30 +93,36 @@ end
95
93
 
96
94
  if $0 == __FILE__
97
95
  ogre = Monster.new
98
- ogre.debug = true # Console output:
99
- ogre.act! # -> Acting @idle
100
- ogre.sight 'player' # -> Setting target to player
101
- ogre.act! # -> Acting @attacking.acquiring_target
102
- # -> planning...
103
- # -> AARGHH!
96
+ ogre.debug = true ### Console output:
97
+ ogre.act! # Acting @idle
98
+ ogre.sight 'player' # Setting target to player
99
+ ogre.act! # Acting @attacking.acquiring_target
100
+ # 2: Attack! <- parent state act! first
101
+ # planning...
102
+ # AARGHH!
104
103
  # ogre.acquire -> Hifsm::MissingTransition, already acquired in act!
105
- ogre.act! # -> Acting @attacking.pursuing
106
- # -> step step player
107
- ogre.enemy_dead # -> Woohoo!
108
- ogre.act! # -> Acting @coming_back
104
+ ogre.act! # Acting @attacking.pursuing
105
+ # 3: Attack!
106
+ # step step player
107
+ ogre.enemy_dead # Woohoo!
108
+ ogre.act! # Acting @coming_back
109
+ # step step home
109
110
 
110
- ogre.sight 'player2' # -> Setting target to player2
111
- ogre.acquire # -> AARGHH!
112
- ogre.act! # -> Acting @attacking.pursuing
113
- # -> step step player2
111
+ ogre.sight 'player2' # Setting target to player2
112
+ ogre.acquire # AARGHH!
113
+ ogre.act! # Acting @attacking.pursuing
114
+ # 5: Attack!
115
+ # step step player2
114
116
  ogre.reached
115
- puts ogre.state # -> attacking.fighting
116
- ogre.act! # -> ~~> player2
117
- 5.times { ogre.act! } # -> ...
118
- ogre.enemy_dead # -> Woohoo!
119
- ogre.act! # -> Acting @coming_back
120
- # -> step step home
117
+ puts ogre.state # attacking.fighting
118
+ ogre.act! # Acting @attacking.fighting
119
+ # 6: Attack!
120
+ # ~~> player2
121
+ 5.times { ogre.act! } # ...
122
+ ogre.enemy_dead # Woohoo!
123
+ ogre.act! # Acting @coming_back
124
+ # step step home
121
125
  ogre.low_hp = true
122
126
  ogre.sight 'player3'
123
- ogre.act! # -> Acting @runaway
127
+ ogre.act! # Acting @runaway
124
128
  end
data/test/setup_tests.rb CHANGED
@@ -1,2 +1,6 @@
1
+ require 'coveralls'
2
+ Coveralls.wear! do
3
+ add_filter 'test'
4
+ end
1
5
  require "minitest/autorun"
2
6
  require "hifsm"
@@ -0,0 +1,80 @@
1
+ require 'setup_tests'
2
+ require 'active_record'
3
+
4
+ class TestActiverecrodAdapter < Minitest::Test
5
+ class SodaMachine < ActiveRecord::Base
6
+ hifsm :state do
7
+ state :off, :initial => true
8
+ state :on do
9
+ state :idle, :initial => true
10
+ state :accepting_cash
11
+ state :cooking
12
+ state :ready
13
+
14
+ event :done, :from => :accepting_cash, :to => :cooking
15
+ event :done, :from => :cooking, :to => :ready
16
+ event :done, :from => :ready, :to => :idle do
17
+ after { self.counter += 1 }
18
+ end
19
+ end
20
+ state :broken
21
+
22
+ event :toggle_power, :from => :off, :to => :on
23
+ event :toggle_power, :from => :on, :to => :off
24
+ event :break, :to => :broken
25
+ end
26
+ end
27
+
28
+ def setup
29
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
30
+ ActiveRecord::Base.connection.create_table :soda_machines do |t|
31
+ t.column :address, :string
32
+ t.column :state, :string
33
+ t.column :counter, :integer, :null => false, :default => 0
34
+ end
35
+
36
+ insert_record 'South Park', 'on.idle'
37
+ insert_record 'Springfield', 'broken'
38
+ end
39
+
40
+ def teardown
41
+ ActiveRecord::Base.connection.disconnect!
42
+ end
43
+
44
+ def test_hifsm_installed
45
+ @machine = SodaMachine.create
46
+ assert @machine.state_machine.is_a?(Hifsm::Machine), ".state should be Hifsm::Mahine"
47
+ end
48
+
49
+ def test_new_machines_saved_in_initial_state
50
+ @machine = SodaMachine.create
51
+ assert_equal 'off', @machine.state_machine.state
52
+ assert_equal 'off', @machine.state
53
+ end
54
+
55
+ def test_state_fetched_from_db
56
+ @machine = SodaMachine.where(:address => 'South Park').first
57
+ assert_equal 'on.idle', @machine.state.to_s
58
+ end
59
+
60
+ def test_events_defined_on_record
61
+ @machine = SodaMachine.first
62
+ @machine.toggle_power.save
63
+ pass # assert_nothing_raised
64
+ end
65
+
66
+ def test_state_saved_in_db
67
+ @machine = SodaMachine.where(:address => 'South Park').first
68
+ @machine.toggle_power.save
69
+ assert_equal 'off', SodaMachine.where(:id => @machine.id).pluck(:state).first
70
+ end
71
+
72
+ private
73
+ def insert_record(address, state)
74
+ insert_manager = Arel::InsertManager.new(SodaMachine)
75
+ insert_manager.insert([[SodaMachine.arel_table[:address], address], [SodaMachine.arel_table[:state], state]])
76
+ ActiveRecord::Base.connection.insert insert_manager
77
+ end
78
+
79
+ end
80
+
@@ -26,4 +26,11 @@ class TestBasicFSM < Minitest::Test
26
26
  @machine.toggle
27
27
  assert_equal 'off', @machine.state
28
28
  end
29
+
30
+ def test_instantiating_maching_in_unknown_state_raises_error
31
+ assert_raises(Hifsm::MissingState) do
32
+ @fsm.instantiate(nil, 'on.no')
33
+ end
34
+ end
35
+
29
36
  end
@@ -0,0 +1,51 @@
1
+ require 'setup_tests'
2
+
3
+ class TestDynamicInitialState < Minitest::Test
4
+ class Value < Struct.new(:value)
5
+ include Hifsm.fsm_module(:group) {
6
+ state :few do
7
+ state :very
8
+ state :almost
9
+ end
10
+ state :lots do
11
+ state :very
12
+ state :almost, :initial => true
13
+ end
14
+ state :throng
15
+ state :swarm
16
+ }
17
+
18
+ def initial_group
19
+ case value
20
+ when 1..5 then 'few.very'
21
+ when 5..10 then 'few.almost'
22
+ when 10..50 then :lots
23
+ when 50..150 then :throng
24
+ when 150..1000 then :swarm
25
+ else :unknown
26
+ end
27
+ end
28
+ end
29
+
30
+ def test_initial_group_3
31
+ assert_equal 'few.very', Value.new(3).group
32
+ end
33
+
34
+ def test_initial_group_7
35
+ assert_equal 'few.almost', Value.new(7).group
36
+ end
37
+
38
+ def test_initial_group_30
39
+ assert_equal 'lots.almost', Value.new(30).group
40
+ end
41
+
42
+ def test_initial_group_120
43
+ assert_equal 'throng', Value.new(120).group
44
+ end
45
+
46
+ def test_unknown_intiial_group
47
+ assert_raises(Hifsm::MissingState) do
48
+ Value.new(-3).group
49
+ end
50
+ end
51
+ end
@@ -1,31 +1,39 @@
1
1
  require 'setup_tests'
2
2
 
3
3
  class TestEventGuard < Minitest::Test
4
- Wall = Struct.new(:stones)
5
-
6
- def build_wall(thickness)
7
- fsm = Hifsm::FSM.new do
4
+ class Wall < Struct.new(:stones)
5
+ include Hifsm.fsm_module {
8
6
  state :constructed, :initial => true
9
7
  state :broken
10
8
 
9
+ # guards can be inline proc, or symbols
11
10
  event :break, :from => :constructed, :to => :broken, :guard => proc { stones < 5 }
11
+
12
+ # event parameters are passed to guards only if arity > 0
13
+ event :shoot, :from => :constructed, :to => :broken, :guard => :breakable?
14
+ }
15
+ def breakable?(hits)
16
+ hits * 5 > stones
12
17
  end
13
- wall = Wall.new(thickness)
14
- @machine = fsm.instantiate(wall)
15
- wall
18
+ end
19
+
20
+ def test_can_break_thin_wall
21
+ wall = Wall.new(3)
22
+ wall.break
23
+ assert_equal 'broken', wall.state
16
24
  end
17
25
 
18
26
  def test_cant_break_wall_10_stones_thick
19
- wall = build_wall 10
27
+ wall = Wall.new(10)
20
28
  assert_raises(Hifsm::MissingTransition) do
21
29
  wall.break
22
30
  end
23
31
  end
24
32
 
25
- def test_cant_break_thin_wall
26
- wall = build_wall 3
27
- wall.break
28
- assert_equal 'broken', @machine.state
33
+ def test_can_break_thick_wall_if_hit_3_times
34
+ wall = Wall.new(10)
35
+ wall.shoot 3
36
+ assert_equal 'broken', wall.state
29
37
  end
30
38
 
31
39
  end
@@ -1,30 +1,27 @@
1
1
  require 'setup_tests'
2
2
 
3
3
  class TestIflessFactorial < Minitest::Test
4
- Value = Struct.new(:value)
5
-
6
- def setup
7
- @fsm = Hifsm::FSM.new do
4
+ class Value < Struct.new(:value)
5
+ include Hifsm.fsm_module {
8
6
  state :idle, :initial => true
9
- state :counting
7
+ state :computing
10
8
 
11
- event :count, :to => :idle do
9
+ event :compute, :to => :idle do
12
10
  guard { |x| x == 0 }
13
11
  after { |x| self.value = 1 }
14
12
  end
15
- event :count, :to => :counting do
13
+ event :compute, :to => :computing do
16
14
  after do |x|
17
- count(x - 1)
15
+ compute(x - 1)
18
16
  self.value *= x
19
17
  end
20
18
  end
21
- end
19
+ }
22
20
  end
23
21
 
24
22
  def factorial(n)
25
23
  val = Value.new
26
- @fsm.instantiate(val)
27
- val.count(n).value
24
+ val.compute(n).value
28
25
  end
29
26
 
30
27
  def test_factorial_0
@@ -0,0 +1,41 @@
1
+ require 'setup_tests'
2
+
3
+ class TestTwoMachines < Minitest::Test
4
+ class ColorPrinter
5
+ include Hifsm.fsm_module(:working_state) {
6
+ state :off, :initial => true
7
+ state :on do
8
+ action do
9
+ color_machine.to_s
10
+ end
11
+ end
12
+
13
+ event :toggle, :from => :off, :to => :on
14
+ event :toggle, :from => :on, :to => :off
15
+ }
16
+ include Hifsm.fsm_module(:color) {
17
+ state :red, :initial => true
18
+ state :green
19
+ state :blue
20
+
21
+ event :cycle_color!, :from => :red, :to => :green
22
+ event :cycle_color!, :from => :green, :to => :blue
23
+ event :cycle_color!, :from => :blue, :to => :red
24
+ }
25
+ end
26
+
27
+ def setup
28
+ @color_printer = ColorPrinter.new
29
+ end
30
+
31
+ def test_two_machines_defined
32
+ assert_equal 'off', @color_printer.working_state_machine.state
33
+ assert_equal 'red', @color_printer.color_machine.color
34
+ end
35
+
36
+ def test_initial_state_is_off_and_red
37
+ assert_equal 'off', @color_printer.working_state
38
+ assert_equal 'red', @color_printer.color
39
+ end
40
+
41
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hifsm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Meremyanin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-03 00:00:00.000000000 Z
11
+ date: 2014-09-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,6 +52,48 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: coveralls
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: activerecord
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sqlite3
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
55
97
  description: FSM with support for nested states and parameterised events
56
98
  email:
57
99
  - vladimir@meremyanin.com
@@ -60,12 +102,14 @@ extensions: []
60
102
  extra_rdoc_files: []
61
103
  files:
62
104
  - ".gitignore"
105
+ - ".travis.yml"
63
106
  - Gemfile
64
107
  - LICENSE.txt
65
108
  - README.md
66
109
  - Rakefile
67
110
  - hifsm.gemspec
68
111
  - lib/hifsm.rb
112
+ - lib/hifsm/adapters/active_record_adapter.rb
69
113
  - lib/hifsm/callbacks.rb
70
114
  - lib/hifsm/event.rb
71
115
  - lib/hifsm/fsm.rb
@@ -75,13 +119,16 @@ files:
75
119
  - test/minitest_helper.rb
76
120
  - test/monster.rb
77
121
  - test/setup_tests.rb
122
+ - test/test_activerecord_adapter.rb
78
123
  - test/test_any_state_event.rb
79
124
  - test/test_basic_fsm.rb
125
+ - test/test_dynamic_initial_state.rb
80
126
  - test/test_event_guard.rb
81
127
  - test/test_hierarchical.rb
82
128
  - test/test_ifless_factorial.rb
83
129
  - test/test_many_states.rb
84
130
  - test/test_monster.rb
131
+ - test/test_two_machines.rb
85
132
  homepage: http://github.com/stiff/hifsm
86
133
  licenses:
87
134
  - MIT
@@ -110,10 +157,13 @@ test_files:
110
157
  - test/minitest_helper.rb
111
158
  - test/monster.rb
112
159
  - test/setup_tests.rb
160
+ - test/test_activerecord_adapter.rb
113
161
  - test/test_any_state_event.rb
114
162
  - test/test_basic_fsm.rb
163
+ - test/test_dynamic_initial_state.rb
115
164
  - test/test_event_guard.rb
116
165
  - test/test_hierarchical.rb
117
166
  - test/test_ifless_factorial.rb
118
167
  - test/test_many_states.rb
119
168
  - test/test_monster.rb
169
+ - test/test_two_machines.rb