state_machine 1.0.2 → 1.0.3
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/.gitignore +1 -0
- data/.travis.yml +0 -2
- data/.yardopts +3 -2
- data/Appraisals +48 -0
- data/{CHANGELOG.rdoc → CHANGELOG.md} +63 -46
- data/README.md +1029 -0
- data/gemfiles/active_model-3.0.0.gemfile.lock +1 -3
- data/gemfiles/active_model-3.0.5.gemfile.lock +1 -3
- data/gemfiles/active_model-3.1.1.gemfile +7 -0
- data/gemfiles/active_model-3.1.1.gemfile.lock +32 -0
- data/gemfiles/active_record-2.0.0.gemfile.lock +1 -3
- data/gemfiles/active_record-2.0.5.gemfile.lock +1 -3
- data/gemfiles/active_record-2.1.0.gemfile.lock +1 -3
- data/gemfiles/active_record-2.1.2.gemfile.lock +1 -3
- data/gemfiles/active_record-2.2.3.gemfile.lock +1 -3
- data/gemfiles/active_record-2.3.12.gemfile.lock +1 -3
- data/gemfiles/active_record-3.0.0.gemfile.lock +1 -3
- data/gemfiles/active_record-3.0.5.gemfile.lock +1 -3
- data/gemfiles/active_record-3.1.1.gemfile +8 -0
- data/gemfiles/active_record-3.1.1.gemfile.lock +43 -0
- data/gemfiles/data_mapper-0.10.2.gemfile.lock +1 -3
- data/gemfiles/data_mapper-0.9.11.gemfile.lock +1 -3
- data/gemfiles/data_mapper-0.9.4.gemfile.lock +1 -3
- data/gemfiles/data_mapper-0.9.7.gemfile.lock +1 -3
- data/gemfiles/data_mapper-1.0.0.gemfile.lock +1 -3
- data/gemfiles/data_mapper-1.0.1.gemfile.lock +1 -3
- data/gemfiles/data_mapper-1.0.2.gemfile.lock +1 -3
- data/gemfiles/data_mapper-1.1.0.gemfile.lock +1 -3
- data/gemfiles/data_mapper-1.2.0.gemfile +12 -0
- data/gemfiles/data_mapper-1.2.0.gemfile.lock +49 -0
- data/gemfiles/default.gemfile.lock +1 -3
- data/gemfiles/graphviz-0.9.0.gemfile +7 -0
- data/gemfiles/graphviz-0.9.0.gemfile.lock +24 -0
- data/gemfiles/graphviz-0.9.21.gemfile +7 -0
- data/gemfiles/graphviz-0.9.21.gemfile.lock +24 -0
- data/gemfiles/graphviz-1.0.0.gemfile +7 -0
- data/gemfiles/graphviz-1.0.0.gemfile.lock +24 -0
- data/gemfiles/mongo_mapper-0.10.0.gemfile +7 -0
- data/gemfiles/mongo_mapper-0.10.0.gemfile.lock +41 -0
- data/gemfiles/mongo_mapper-0.5.5.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.5.8.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.6.0.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.6.10.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.7.0.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.7.5.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.8.0.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.8.3.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.8.4.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.8.6.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.9.0.gemfile.lock +1 -3
- data/gemfiles/mongoid-2.0.0.gemfile.lock +1 -3
- data/gemfiles/mongoid-2.1.4.gemfile.lock +1 -3
- data/gemfiles/mongoid-2.2.4.gemfile +7 -0
- data/gemfiles/mongoid-2.2.4.gemfile.lock +40 -0
- data/gemfiles/mongoid-2.3.3.gemfile +7 -0
- data/gemfiles/mongoid-2.3.3.gemfile.lock +40 -0
- data/gemfiles/sequel-2.11.0.gemfile.lock +1 -3
- data/gemfiles/sequel-2.12.0.gemfile.lock +1 -3
- data/gemfiles/sequel-2.8.0.gemfile.lock +1 -3
- data/gemfiles/sequel-3.0.0.gemfile.lock +1 -3
- data/gemfiles/sequel-3.13.0.gemfile.lock +1 -3
- data/gemfiles/sequel-3.14.0.gemfile.lock +1 -3
- data/gemfiles/sequel-3.23.0.gemfile.lock +1 -3
- data/gemfiles/sequel-3.24.0.gemfile.lock +1 -3
- data/gemfiles/sequel-3.29.0.gemfile +8 -0
- data/gemfiles/sequel-3.29.0.gemfile.lock +26 -0
- data/lib/state_machine.rb +45 -0
- data/lib/state_machine/event.rb +18 -3
- data/lib/state_machine/event_collection.rb +1 -1
- data/lib/state_machine/integrations/active_model.rb +59 -16
- data/lib/state_machine/integrations/active_model/observer.rb +3 -15
- data/lib/state_machine/integrations/active_record.rb +46 -9
- data/lib/state_machine/integrations/data_mapper.rb +42 -2
- data/lib/state_machine/integrations/data_mapper/versions.rb +22 -10
- data/lib/state_machine/integrations/mongo_mapper.rb +55 -0
- data/lib/state_machine/integrations/mongo_mapper/versions.rb +3 -3
- data/lib/state_machine/integrations/mongoid.rb +57 -12
- data/lib/state_machine/integrations/mongoid/versions.rb +22 -4
- data/lib/state_machine/integrations/sequel.rb +45 -0
- data/lib/state_machine/integrations/sequel/versions.rb +3 -0
- data/lib/state_machine/machine.rb +148 -34
- data/lib/state_machine/node_collection.rb +36 -3
- data/lib/state_machine/state.rb +6 -3
- data/lib/state_machine/state_collection.rb +1 -1
- data/lib/state_machine/version.rb +1 -1
- data/lib/tasks/state_machine.rb +11 -9
- data/state_machine.gemspec +2 -3
- data/test/functional/state_machine_test.rb +54 -1
- data/test/unit/event_collection_test.rb +4 -0
- data/test/unit/event_test.rb +34 -1
- data/test/unit/integrations/active_model_test.rb +80 -0
- data/test/unit/integrations/active_record_test.rb +105 -2
- data/test/unit/integrations/data_mapper_test.rb +27 -25
- data/test/unit/integrations/mongo_mapper_test.rb +80 -25
- data/test/unit/integrations/mongoid_test.rb +61 -6
- data/test/unit/integrations/sequel_test.rb +8 -2
- data/test/unit/machine_test.rb +87 -9
- data/test/unit/node_collection_test.rb +129 -12
- data/test/unit/state_collection_test.rb +4 -0
- data/test/unit/state_test.rb +2 -2
- metadata +30 -24
- data/README.rdoc +0 -844
|
@@ -24,6 +24,7 @@ module StateMachine
|
|
|
24
24
|
@nodes = []
|
|
25
25
|
@indices = Array(options[:index]).inject({}) {|indices, attribute| indices[attribute] = {}; indices}
|
|
26
26
|
@default_index = Array(options[:index]).first
|
|
27
|
+
@contexts = []
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
# Creates a copy of this collection such that modifications don't affect
|
|
@@ -32,9 +33,15 @@ module StateMachine
|
|
|
32
33
|
super
|
|
33
34
|
|
|
34
35
|
nodes = @nodes
|
|
36
|
+
contexts = @contexts
|
|
35
37
|
@nodes = []
|
|
38
|
+
@contexts = []
|
|
36
39
|
@indices = @indices.inject({}) {|indices, (name, index)| indices[name] = {}; indices}
|
|
40
|
+
|
|
41
|
+
# Add nodes *prior* to copying over the contexts so that they don't get
|
|
42
|
+
# evaluated multiple times
|
|
37
43
|
concat(nodes.map {|n| n.dup})
|
|
44
|
+
@contexts = contexts.dup
|
|
38
45
|
end
|
|
39
46
|
|
|
40
47
|
# Changes the current machine associated with the collection. In turn, this
|
|
@@ -54,11 +61,26 @@ module StateMachine
|
|
|
54
61
|
index(index_name).keys
|
|
55
62
|
end
|
|
56
63
|
|
|
64
|
+
# Tracks a context that should be evaluated for any nodes that get added
|
|
65
|
+
# which match the given set of nodes. Matchers can be used so that the
|
|
66
|
+
# context can get added once and evaluated after multiple adds.
|
|
67
|
+
def context(nodes, &block)
|
|
68
|
+
nodes = nodes.first.is_a?(Matcher) ? nodes.first : WhitelistMatcher.new(nodes)
|
|
69
|
+
@contexts << context = {:nodes => nodes, :block => block}
|
|
70
|
+
|
|
71
|
+
# Evaluate the new context for existing nodes
|
|
72
|
+
each {|node| eval_context(context, node)}
|
|
73
|
+
|
|
74
|
+
context
|
|
75
|
+
end
|
|
76
|
+
|
|
57
77
|
# Adds a new node to the collection. By doing so, this will also add it to
|
|
58
|
-
# the configured indices.
|
|
78
|
+
# the configured indices. This will also evaluate any existings contexts
|
|
79
|
+
# that match the new node.
|
|
59
80
|
def <<(node)
|
|
60
81
|
@nodes << node
|
|
61
82
|
@indices.each {|attribute, index| index[value(node, attribute)] = node}
|
|
83
|
+
@contexts.each {|context| eval_context(context, node)}
|
|
62
84
|
self
|
|
63
85
|
end
|
|
64
86
|
|
|
@@ -122,7 +144,12 @@ module StateMachine
|
|
|
122
144
|
#
|
|
123
145
|
# If the key cannot be found, then nil will be returned.
|
|
124
146
|
def [](key, index_name = @default_index)
|
|
125
|
-
index(index_name)
|
|
147
|
+
index = self.index(index_name)
|
|
148
|
+
if index.include?(key)
|
|
149
|
+
index[key]
|
|
150
|
+
elsif @indices.include?(:"#{index_name}_to_s")
|
|
151
|
+
self[key.to_s, :"#{index_name}_to_s"]
|
|
152
|
+
end
|
|
126
153
|
end
|
|
127
154
|
|
|
128
155
|
# Gets the node indexed by the given key. By default, this will look up the
|
|
@@ -141,7 +168,7 @@ module StateMachine
|
|
|
141
168
|
self[key, index_name] || raise(IndexError, "#{key.inspect} is an invalid #{index_name}")
|
|
142
169
|
end
|
|
143
170
|
|
|
144
|
-
|
|
171
|
+
protected
|
|
145
172
|
# Gets the given index. If the index does not exist, then an ArgumentError
|
|
146
173
|
# is raised.
|
|
147
174
|
def index(name)
|
|
@@ -153,5 +180,11 @@ module StateMachine
|
|
|
153
180
|
def value(node, attribute)
|
|
154
181
|
node.send(attribute)
|
|
155
182
|
end
|
|
183
|
+
|
|
184
|
+
# Evaluates the given context for a particular node. This will only
|
|
185
|
+
# evaluate the context if the node matches.
|
|
186
|
+
def eval_context(context, node)
|
|
187
|
+
node.context(&context[:block]) if context[:nodes].matches?(node.name)
|
|
188
|
+
end
|
|
156
189
|
end
|
|
157
190
|
end
|
data/lib/state_machine/state.rb
CHANGED
|
@@ -96,6 +96,11 @@ module StateMachine
|
|
|
96
96
|
@methods = methods.dup
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
+
# Converts the name of this state to a string
|
|
100
|
+
def name_to_s
|
|
101
|
+
name.to_s
|
|
102
|
+
end
|
|
103
|
+
|
|
99
104
|
# Determines whether there are any states that can be transitioned to from
|
|
100
105
|
# this state. If there are none, then this state is considered *final*.
|
|
101
106
|
# Any objects in a final state will remain so forever given the current
|
|
@@ -179,9 +184,7 @@ module StateMachine
|
|
|
179
184
|
# This can be called multiple times. Each time a new context is created,
|
|
180
185
|
# a new module will be included in the owner class.
|
|
181
186
|
def context(&block)
|
|
182
|
-
owner_class = machine.owner_class
|
|
183
187
|
machine_name = machine.name
|
|
184
|
-
name = self.name
|
|
185
188
|
|
|
186
189
|
# Evaluate the method definitions
|
|
187
190
|
context = StateContext.new(self)
|
|
@@ -199,7 +202,7 @@ module StateMachine
|
|
|
199
202
|
|
|
200
203
|
# Include the context so that it can be bound to the owner class (the
|
|
201
204
|
# context is considered an ancestor, so it's allowed to be bound)
|
|
202
|
-
owner_class.class_eval { include context }
|
|
205
|
+
machine.owner_class.class_eval { include context }
|
|
203
206
|
|
|
204
207
|
context
|
|
205
208
|
end
|
|
@@ -4,7 +4,7 @@ module StateMachine
|
|
|
4
4
|
# Represents a collection of states in a state machine
|
|
5
5
|
class StateCollection < NodeCollection
|
|
6
6
|
def initialize(machine) #:nodoc:
|
|
7
|
-
super(machine, :index => [:name, :qualified_name, :value])
|
|
7
|
+
super(machine, :index => [:name, :name_to_s, :qualified_name, :value])
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
# Determines whether the given object is in a specific state. If the
|
data/lib/tasks/state_machine.rb
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
namespace :state_machine do
|
|
2
|
-
desc 'Draws
|
|
2
|
+
desc 'Draws state machines using GraphViz (options: CLASS=User,Vehicle; FILE=user.rb,vehicle.rb [not required in Rails / Merb]; FONT=Arial; FORMAT=png; ORIENTATION=portrait'
|
|
3
3
|
task :draw do
|
|
4
|
+
# Build drawing options
|
|
5
|
+
options = {}
|
|
6
|
+
options[:file] = ENV['FILE'] if ENV['FILE']
|
|
7
|
+
options[:path] = ENV['TARGET'] if ENV['TARGET']
|
|
8
|
+
options[:format] = ENV['FORMAT'] if ENV['FORMAT']
|
|
9
|
+
options[:font] = ENV['FONT'] if ENV['FONT']
|
|
10
|
+
options[:orientation] = ENV['ORIENTATION'] if ENV['ORIENTATION']
|
|
11
|
+
|
|
4
12
|
if defined?(Rails)
|
|
13
|
+
puts "Files are automatically loaded in Rails; ignoring FILE option" if options.delete(:file)
|
|
5
14
|
Rake::Task['environment'].invoke
|
|
6
15
|
elsif defined?(Merb)
|
|
16
|
+
puts "Files are automatically loaded in Merb; ignoring FILE option" if options.delete(:file)
|
|
7
17
|
Rake::Task['merb_env'].invoke
|
|
8
18
|
|
|
9
19
|
# Fix ruby-graphviz being incompatible with Merb's process title
|
|
@@ -14,14 +24,6 @@ namespace :state_machine do
|
|
|
14
24
|
require 'state_machine'
|
|
15
25
|
end
|
|
16
26
|
|
|
17
|
-
# Build drawing options
|
|
18
|
-
options = {}
|
|
19
|
-
options[:file] = ENV['FILE'] if ENV['FILE']
|
|
20
|
-
options[:path] = ENV['TARGET'] if ENV['TARGET']
|
|
21
|
-
options[:format] = ENV['FORMAT'] if ENV['FORMAT']
|
|
22
|
-
options[:font] = ENV['FONT'] if ENV['FONT']
|
|
23
|
-
options[:orientation] = ENV['ORIENTATION'] if ENV['ORIENTATION']
|
|
24
|
-
|
|
25
27
|
StateMachine::Machine.draw(ENV['CLASS'], options)
|
|
26
28
|
end
|
|
27
29
|
end
|
data/state_machine.gemspec
CHANGED
|
@@ -12,11 +12,10 @@ Gem::Specification.new do |s|
|
|
|
12
12
|
s.require_paths = ["lib"]
|
|
13
13
|
s.files = `git ls-files`.split("\n")
|
|
14
14
|
s.test_files = `git ls-files -- test/*`.split("\n")
|
|
15
|
-
s.rdoc_options = %w(--line-numbers --inline-source --title state_machine --main README.
|
|
16
|
-
s.extra_rdoc_files = %w(README.
|
|
15
|
+
s.rdoc_options = %w(--line-numbers --inline-source --title state_machine --main README.md)
|
|
16
|
+
s.extra_rdoc_files = %w(README.md CHANGELOG.md LICENSE)
|
|
17
17
|
|
|
18
18
|
s.add_development_dependency("rake")
|
|
19
19
|
s.add_development_dependency("rcov")
|
|
20
20
|
s.add_development_dependency("appraisal", "~> 0.3.8")
|
|
21
|
-
s.add_development_dependency("ruby-graphviz", "~> 1.0")
|
|
22
21
|
end
|
|
@@ -74,6 +74,10 @@ class Vehicle < ModelBase
|
|
|
74
74
|
vehicle.time_elapsed = Time.now - time
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
+
event all do
|
|
78
|
+
transition :locked => :parked
|
|
79
|
+
end
|
|
80
|
+
|
|
77
81
|
event :park do
|
|
78
82
|
transition [:idling, :first_gear] => :parked
|
|
79
83
|
end
|
|
@@ -191,7 +195,7 @@ end
|
|
|
191
195
|
class TrafficLight
|
|
192
196
|
state_machine :initial => :stop do
|
|
193
197
|
event :cycle do
|
|
194
|
-
transition :stop => :proceed, :proceed=> :caution, :caution => :stop
|
|
198
|
+
transition :stop => :proceed, :proceed => :caution, :caution => :stop
|
|
195
199
|
end
|
|
196
200
|
|
|
197
201
|
state :stop do
|
|
@@ -208,10 +212,20 @@ class TrafficLight
|
|
|
208
212
|
end
|
|
209
213
|
end
|
|
210
214
|
|
|
215
|
+
state all - :proceed do
|
|
216
|
+
def capture_violations?
|
|
217
|
+
true
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
211
221
|
state :proceed do
|
|
212
222
|
def color(transform)
|
|
213
223
|
'green'
|
|
214
224
|
end
|
|
225
|
+
|
|
226
|
+
def capture_violations?
|
|
227
|
+
false
|
|
228
|
+
end
|
|
215
229
|
end
|
|
216
230
|
|
|
217
231
|
state :caution do
|
|
@@ -713,6 +727,33 @@ class VehicleRepairedTest < Test::Unit::TestCase
|
|
|
713
727
|
end
|
|
714
728
|
end
|
|
715
729
|
|
|
730
|
+
class VehicleLockedTest < Test::Unit::TestCase
|
|
731
|
+
def setup
|
|
732
|
+
@vehicle = Vehicle.new
|
|
733
|
+
@vehicle.state = 'locked'
|
|
734
|
+
end
|
|
735
|
+
|
|
736
|
+
def test_should_be_parked_after_park
|
|
737
|
+
@vehicle.park
|
|
738
|
+
assert @vehicle.parked?
|
|
739
|
+
end
|
|
740
|
+
|
|
741
|
+
def test_should_be_parked_after_ignite
|
|
742
|
+
@vehicle.ignite
|
|
743
|
+
assert @vehicle.parked?
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
def test_should_be_parked_after_shift_up
|
|
747
|
+
@vehicle.shift_up
|
|
748
|
+
assert @vehicle.parked?
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
def test_should_be_parked_after_shift_down
|
|
752
|
+
@vehicle.shift_down
|
|
753
|
+
assert @vehicle.parked?
|
|
754
|
+
end
|
|
755
|
+
end
|
|
756
|
+
|
|
716
757
|
class VehicleWithParallelEventsTest < Test::Unit::TestCase
|
|
717
758
|
def setup
|
|
718
759
|
@vehicle = Vehicle.new
|
|
@@ -968,6 +1009,10 @@ class TrafficLightStopTest < Test::Unit::TestCase
|
|
|
968
1009
|
color = @light.color {|value| value.upcase!}
|
|
969
1010
|
assert_equal 'RED', color
|
|
970
1011
|
end
|
|
1012
|
+
|
|
1013
|
+
def test_should_use_stop_capture_violations
|
|
1014
|
+
assert_equal true, @light.capture_violations?
|
|
1015
|
+
end
|
|
971
1016
|
end
|
|
972
1017
|
|
|
973
1018
|
class TrafficLightProceedTest < Test::Unit::TestCase
|
|
@@ -979,6 +1024,10 @@ class TrafficLightProceedTest < Test::Unit::TestCase
|
|
|
979
1024
|
def test_should_use_proceed_color
|
|
980
1025
|
assert_equal 'green', @light.color
|
|
981
1026
|
end
|
|
1027
|
+
|
|
1028
|
+
def test_should_use_proceed_capture_violations
|
|
1029
|
+
assert_equal false, @light.capture_violations?
|
|
1030
|
+
end
|
|
982
1031
|
end
|
|
983
1032
|
|
|
984
1033
|
class TrafficLightCautionTest < Test::Unit::TestCase
|
|
@@ -990,4 +1039,8 @@ class TrafficLightCautionTest < Test::Unit::TestCase
|
|
|
990
1039
|
def test_should_use_caution_color
|
|
991
1040
|
assert_equal 'yellow', @light.color
|
|
992
1041
|
end
|
|
1042
|
+
|
|
1043
|
+
def test_should_use_caution_capture_violations
|
|
1044
|
+
assert_equal true, @light.capture_violations?
|
|
1045
|
+
end
|
|
993
1046
|
end
|
|
@@ -40,6 +40,10 @@ class EventCollectionTest < Test::Unit::TestCase
|
|
|
40
40
|
assert_equal @open, @events[:enable]
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
+
def test_should_index_by_string_name
|
|
44
|
+
assert_equal @open, @events['enable']
|
|
45
|
+
end
|
|
46
|
+
|
|
43
47
|
def test_should_index_by_qualified_name
|
|
44
48
|
assert_equal @open, @events[:enable_alarm, :qualified_name]
|
|
45
49
|
end
|
data/test/unit/event_test.rb
CHANGED
|
@@ -173,7 +173,7 @@ class EventWithConflictingHelpersBeforeDefinitionTest < Test::Unit::TestCase
|
|
|
173
173
|
|
|
174
174
|
def test_should_output_warning
|
|
175
175
|
expected = %w(can_ignite? ignite_transition ignite ignite!).map do |method|
|
|
176
|
-
"Instance method \"#{method}\" is already defined in #{@superclass.to_s}, use generic helper instead.\n"
|
|
176
|
+
"Instance method \"#{method}\" is already defined in #{@superclass.to_s}, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n"
|
|
177
177
|
end.join
|
|
178
178
|
|
|
179
179
|
assert_equal expected, $stderr.string
|
|
@@ -341,6 +341,20 @@ class EventWithNamespaceTest < Test::Unit::TestCase
|
|
|
341
341
|
end
|
|
342
342
|
end
|
|
343
343
|
|
|
344
|
+
class EventContextTest < Test::Unit::TestCase
|
|
345
|
+
def setup
|
|
346
|
+
@klass = Class.new
|
|
347
|
+
@machine = StateMachine::Machine.new(@klass)
|
|
348
|
+
@machine.events << @event = StateMachine::Event.new(@machine, :ignite, :human_name => 'start')
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def test_should_evaluate_within_the_event
|
|
352
|
+
scope = nil
|
|
353
|
+
@event.context { scope = self }
|
|
354
|
+
assert_equal @event, scope
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
344
358
|
class EventTransitionsTest < Test::Unit::TestCase
|
|
345
359
|
def setup
|
|
346
360
|
@machine = StateMachine::Machine.new(Class.new)
|
|
@@ -568,6 +582,25 @@ class EventWithMatchingDisabledTransitionsTest < Test::Unit::TestCase
|
|
|
568
582
|
assert_equal ['cannot transition via "start"'], @object.errors
|
|
569
583
|
end
|
|
570
584
|
|
|
585
|
+
def test_should_invalid_with_human_state_name_if_specified
|
|
586
|
+
klass = Class.new do
|
|
587
|
+
attr_accessor :errors
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
machine = StateMachine::Machine.new(klass, :integration => :custom, :messages => {:invalid_transition => 'cannot transition via "%s" from "%s"'})
|
|
591
|
+
parked, idling = machine.state :parked, :idling
|
|
592
|
+
parked.human_name = 'stopped'
|
|
593
|
+
|
|
594
|
+
machine.events << event = StateMachine::Event.new(machine, :ignite)
|
|
595
|
+
event.transition(:parked => :idling, :if => lambda {false})
|
|
596
|
+
|
|
597
|
+
object = @klass.new
|
|
598
|
+
object.state = 'parked'
|
|
599
|
+
|
|
600
|
+
event.fire(object)
|
|
601
|
+
assert_equal ['cannot transition via "ignite" from "stopped"'], object.errors
|
|
602
|
+
end
|
|
603
|
+
|
|
571
604
|
def test_should_reset_existing_error
|
|
572
605
|
@object.errors = ['invalid']
|
|
573
606
|
|
|
@@ -798,6 +798,54 @@ module ActiveModelTest
|
|
|
798
798
|
@transition.perform
|
|
799
799
|
assert_equal [instance], instance.notifications
|
|
800
800
|
end
|
|
801
|
+
|
|
802
|
+
def test_should_support_nil_from_states
|
|
803
|
+
callbacks = [
|
|
804
|
+
:before_ignite_from_nil_to_idling,
|
|
805
|
+
:before_ignite_from_nil,
|
|
806
|
+
:before_transition_state_from_nil_to_idling,
|
|
807
|
+
:before_transition_state_from_nil
|
|
808
|
+
]
|
|
809
|
+
|
|
810
|
+
notified = false
|
|
811
|
+
observer = new_observer(@model) do
|
|
812
|
+
callbacks.each do |callback|
|
|
813
|
+
define_method(callback) do |*args|
|
|
814
|
+
notifications << callback
|
|
815
|
+
end
|
|
816
|
+
end
|
|
817
|
+
end
|
|
818
|
+
|
|
819
|
+
instance = observer.instance
|
|
820
|
+
|
|
821
|
+
transition = StateMachine::Transition.new(@record, @machine, :ignite, nil, :idling)
|
|
822
|
+
transition.perform
|
|
823
|
+
assert_equal callbacks, instance.notifications
|
|
824
|
+
end
|
|
825
|
+
|
|
826
|
+
def test_should_support_nil_to_states
|
|
827
|
+
callbacks = [
|
|
828
|
+
:before_ignite_from_parked_to_nil,
|
|
829
|
+
:before_ignite_to_nil,
|
|
830
|
+
:before_transition_state_from_parked_to_nil,
|
|
831
|
+
:before_transition_state_to_nil
|
|
832
|
+
]
|
|
833
|
+
|
|
834
|
+
notified = false
|
|
835
|
+
observer = new_observer(@model) do
|
|
836
|
+
callbacks.each do |callback|
|
|
837
|
+
define_method(callback) do |*args|
|
|
838
|
+
notifications << callback
|
|
839
|
+
end
|
|
840
|
+
end
|
|
841
|
+
end
|
|
842
|
+
|
|
843
|
+
instance = observer.instance
|
|
844
|
+
|
|
845
|
+
transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, nil)
|
|
846
|
+
transition.perform
|
|
847
|
+
assert_equal callbacks, instance.notifications
|
|
848
|
+
end
|
|
801
849
|
end
|
|
802
850
|
|
|
803
851
|
class MachineWithNamespacedObserversTest < BaseTestCase
|
|
@@ -994,6 +1042,17 @@ module ActiveModelTest
|
|
|
994
1042
|
assert_equal 'shutdown', machine.state(:parked).human_name
|
|
995
1043
|
end
|
|
996
1044
|
|
|
1045
|
+
def test_should_allow_customized_state_key_scoped_to_class
|
|
1046
|
+
I18n.backend.store_translations(:en, {
|
|
1047
|
+
:activemodel => {:state_machines => {:'active_model_test/foo' => {:states => {:parked => 'shutdown'}}}}
|
|
1048
|
+
})
|
|
1049
|
+
|
|
1050
|
+
machine = StateMachine::Machine.new(@model)
|
|
1051
|
+
machine.state :parked
|
|
1052
|
+
|
|
1053
|
+
assert_equal 'shutdown', machine.state(:parked).human_name
|
|
1054
|
+
end
|
|
1055
|
+
|
|
997
1056
|
def test_should_allow_customized_state_key_scoped_to_machine
|
|
998
1057
|
I18n.backend.store_translations(:en, {
|
|
999
1058
|
:activemodel => {:state_machines => {:state => {:states => {:parked => 'shutdown'}}}}
|
|
@@ -1016,6 +1075,16 @@ module ActiveModelTest
|
|
|
1016
1075
|
assert_equal 'shutdown', machine.state(:parked).human_name
|
|
1017
1076
|
end
|
|
1018
1077
|
|
|
1078
|
+
def test_should_support_nil_state_key
|
|
1079
|
+
I18n.backend.store_translations(:en, {
|
|
1080
|
+
:activemodel => {:state_machines => {:states => {:nil => 'empty'}}}
|
|
1081
|
+
})
|
|
1082
|
+
|
|
1083
|
+
machine = StateMachine::Machine.new(@model)
|
|
1084
|
+
|
|
1085
|
+
assert_equal 'empty', machine.state(nil).human_name
|
|
1086
|
+
end
|
|
1087
|
+
|
|
1019
1088
|
def test_should_allow_customized_event_key_scoped_to_class_and_machine
|
|
1020
1089
|
I18n.backend.store_translations(:en, {
|
|
1021
1090
|
:activemodel => {:state_machines => {:'active_model_test/foo' => {:state => {:events => {:park => 'stop'}}}}}
|
|
@@ -1027,6 +1096,17 @@ module ActiveModelTest
|
|
|
1027
1096
|
assert_equal 'stop', machine.event(:park).human_name
|
|
1028
1097
|
end
|
|
1029
1098
|
|
|
1099
|
+
def test_should_allow_customized_event_key_scoped_to_class
|
|
1100
|
+
I18n.backend.store_translations(:en, {
|
|
1101
|
+
:activemodel => {:state_machines => {:'active_model_test/foo' => {:events => {:park => 'stop'}}}}
|
|
1102
|
+
})
|
|
1103
|
+
|
|
1104
|
+
machine = StateMachine::Machine.new(@model)
|
|
1105
|
+
machine.event :park
|
|
1106
|
+
|
|
1107
|
+
assert_equal 'stop', machine.event(:park).human_name
|
|
1108
|
+
end
|
|
1109
|
+
|
|
1030
1110
|
def test_should_allow_customized_event_key_scoped_to_machine
|
|
1031
1111
|
I18n.backend.store_translations(:en, {
|
|
1032
1112
|
:activemodel => {:state_machines => {:state => {:events => {:park => 'stop'}}}}
|