state_machine 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +0 -2
  3. data/.yardopts +3 -2
  4. data/Appraisals +48 -0
  5. data/{CHANGELOG.rdoc → CHANGELOG.md} +63 -46
  6. data/README.md +1029 -0
  7. data/gemfiles/active_model-3.0.0.gemfile.lock +1 -3
  8. data/gemfiles/active_model-3.0.5.gemfile.lock +1 -3
  9. data/gemfiles/active_model-3.1.1.gemfile +7 -0
  10. data/gemfiles/active_model-3.1.1.gemfile.lock +32 -0
  11. data/gemfiles/active_record-2.0.0.gemfile.lock +1 -3
  12. data/gemfiles/active_record-2.0.5.gemfile.lock +1 -3
  13. data/gemfiles/active_record-2.1.0.gemfile.lock +1 -3
  14. data/gemfiles/active_record-2.1.2.gemfile.lock +1 -3
  15. data/gemfiles/active_record-2.2.3.gemfile.lock +1 -3
  16. data/gemfiles/active_record-2.3.12.gemfile.lock +1 -3
  17. data/gemfiles/active_record-3.0.0.gemfile.lock +1 -3
  18. data/gemfiles/active_record-3.0.5.gemfile.lock +1 -3
  19. data/gemfiles/active_record-3.1.1.gemfile +8 -0
  20. data/gemfiles/active_record-3.1.1.gemfile.lock +43 -0
  21. data/gemfiles/data_mapper-0.10.2.gemfile.lock +1 -3
  22. data/gemfiles/data_mapper-0.9.11.gemfile.lock +1 -3
  23. data/gemfiles/data_mapper-0.9.4.gemfile.lock +1 -3
  24. data/gemfiles/data_mapper-0.9.7.gemfile.lock +1 -3
  25. data/gemfiles/data_mapper-1.0.0.gemfile.lock +1 -3
  26. data/gemfiles/data_mapper-1.0.1.gemfile.lock +1 -3
  27. data/gemfiles/data_mapper-1.0.2.gemfile.lock +1 -3
  28. data/gemfiles/data_mapper-1.1.0.gemfile.lock +1 -3
  29. data/gemfiles/data_mapper-1.2.0.gemfile +12 -0
  30. data/gemfiles/data_mapper-1.2.0.gemfile.lock +49 -0
  31. data/gemfiles/default.gemfile.lock +1 -3
  32. data/gemfiles/graphviz-0.9.0.gemfile +7 -0
  33. data/gemfiles/graphviz-0.9.0.gemfile.lock +24 -0
  34. data/gemfiles/graphviz-0.9.21.gemfile +7 -0
  35. data/gemfiles/graphviz-0.9.21.gemfile.lock +24 -0
  36. data/gemfiles/graphviz-1.0.0.gemfile +7 -0
  37. data/gemfiles/graphviz-1.0.0.gemfile.lock +24 -0
  38. data/gemfiles/mongo_mapper-0.10.0.gemfile +7 -0
  39. data/gemfiles/mongo_mapper-0.10.0.gemfile.lock +41 -0
  40. data/gemfiles/mongo_mapper-0.5.5.gemfile.lock +1 -3
  41. data/gemfiles/mongo_mapper-0.5.8.gemfile.lock +1 -3
  42. data/gemfiles/mongo_mapper-0.6.0.gemfile.lock +1 -3
  43. data/gemfiles/mongo_mapper-0.6.10.gemfile.lock +1 -3
  44. data/gemfiles/mongo_mapper-0.7.0.gemfile.lock +1 -3
  45. data/gemfiles/mongo_mapper-0.7.5.gemfile.lock +1 -3
  46. data/gemfiles/mongo_mapper-0.8.0.gemfile.lock +1 -3
  47. data/gemfiles/mongo_mapper-0.8.3.gemfile.lock +1 -3
  48. data/gemfiles/mongo_mapper-0.8.4.gemfile.lock +1 -3
  49. data/gemfiles/mongo_mapper-0.8.6.gemfile.lock +1 -3
  50. data/gemfiles/mongo_mapper-0.9.0.gemfile.lock +1 -3
  51. data/gemfiles/mongoid-2.0.0.gemfile.lock +1 -3
  52. data/gemfiles/mongoid-2.1.4.gemfile.lock +1 -3
  53. data/gemfiles/mongoid-2.2.4.gemfile +7 -0
  54. data/gemfiles/mongoid-2.2.4.gemfile.lock +40 -0
  55. data/gemfiles/mongoid-2.3.3.gemfile +7 -0
  56. data/gemfiles/mongoid-2.3.3.gemfile.lock +40 -0
  57. data/gemfiles/sequel-2.11.0.gemfile.lock +1 -3
  58. data/gemfiles/sequel-2.12.0.gemfile.lock +1 -3
  59. data/gemfiles/sequel-2.8.0.gemfile.lock +1 -3
  60. data/gemfiles/sequel-3.0.0.gemfile.lock +1 -3
  61. data/gemfiles/sequel-3.13.0.gemfile.lock +1 -3
  62. data/gemfiles/sequel-3.14.0.gemfile.lock +1 -3
  63. data/gemfiles/sequel-3.23.0.gemfile.lock +1 -3
  64. data/gemfiles/sequel-3.24.0.gemfile.lock +1 -3
  65. data/gemfiles/sequel-3.29.0.gemfile +8 -0
  66. data/gemfiles/sequel-3.29.0.gemfile.lock +26 -0
  67. data/lib/state_machine.rb +45 -0
  68. data/lib/state_machine/event.rb +18 -3
  69. data/lib/state_machine/event_collection.rb +1 -1
  70. data/lib/state_machine/integrations/active_model.rb +59 -16
  71. data/lib/state_machine/integrations/active_model/observer.rb +3 -15
  72. data/lib/state_machine/integrations/active_record.rb +46 -9
  73. data/lib/state_machine/integrations/data_mapper.rb +42 -2
  74. data/lib/state_machine/integrations/data_mapper/versions.rb +22 -10
  75. data/lib/state_machine/integrations/mongo_mapper.rb +55 -0
  76. data/lib/state_machine/integrations/mongo_mapper/versions.rb +3 -3
  77. data/lib/state_machine/integrations/mongoid.rb +57 -12
  78. data/lib/state_machine/integrations/mongoid/versions.rb +22 -4
  79. data/lib/state_machine/integrations/sequel.rb +45 -0
  80. data/lib/state_machine/integrations/sequel/versions.rb +3 -0
  81. data/lib/state_machine/machine.rb +148 -34
  82. data/lib/state_machine/node_collection.rb +36 -3
  83. data/lib/state_machine/state.rb +6 -3
  84. data/lib/state_machine/state_collection.rb +1 -1
  85. data/lib/state_machine/version.rb +1 -1
  86. data/lib/tasks/state_machine.rb +11 -9
  87. data/state_machine.gemspec +2 -3
  88. data/test/functional/state_machine_test.rb +54 -1
  89. data/test/unit/event_collection_test.rb +4 -0
  90. data/test/unit/event_test.rb +34 -1
  91. data/test/unit/integrations/active_model_test.rb +80 -0
  92. data/test/unit/integrations/active_record_test.rb +105 -2
  93. data/test/unit/integrations/data_mapper_test.rb +27 -25
  94. data/test/unit/integrations/mongo_mapper_test.rb +80 -25
  95. data/test/unit/integrations/mongoid_test.rb +61 -6
  96. data/test/unit/integrations/sequel_test.rb +8 -2
  97. data/test/unit/machine_test.rb +87 -9
  98. data/test/unit/node_collection_test.rb +129 -12
  99. data/test/unit/state_collection_test.rb +4 -0
  100. data/test/unit/state_test.rb +2 -2
  101. metadata +30 -24
  102. 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)[key]
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
- private
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module StateMachine
2
- VERSION = '1.0.2'
2
+ VERSION = '1.0.3'
3
3
  end
@@ -1,9 +1,19 @@
1
1
  namespace :state_machine do
2
- desc 'Draws a set of state machines using GraphViz. Target files to load with FILE=x,y,z; Machine class with CLASS=x,y,z; Font name with FONT=x; Image format with FORMAT=x; Orientation with ORIENTATION=x'
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
@@ -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.rdoc)
16
- s.extra_rdoc_files = %w(README.rdoc CHANGELOG.rdoc LICENSE)
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
@@ -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'}}}}