state_machine 0.7.5 → 0.7.6

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.
@@ -24,6 +24,7 @@ module StateMachine
24
24
  # are known:
25
25
  # * +parked+
26
26
  # * +stalled+
27
+ # * +idling+
27
28
  def all
28
29
  AllMatcher.instance
29
30
  end
@@ -119,7 +119,9 @@ module StateMachine
119
119
  def value(eval = true)
120
120
  if @value.is_a?(Proc) && eval
121
121
  if cache_value?
122
- instance_variable_defined?('@cached_value') ? @cached_value : @cached_value = @value.call
122
+ @value = @value.call
123
+ machine.states.update(self)
124
+ @value
123
125
  else
124
126
  @value.call
125
127
  end
@@ -154,11 +156,11 @@ module StateMachine
154
156
  # a new module will be included in the owner class.
155
157
  def context(&block)
156
158
  owner_class = machine.owner_class
157
- attribute = machine.attribute
159
+ machine_name = machine.name
158
160
  name = self.name
159
161
 
160
162
  # Evaluate the method definitions
161
- context = ConditionProxy.new(owner_class, lambda {|object| object.send("#{attribute}_name") == name})
163
+ context = ConditionProxy.new(owner_class, lambda {|object| object.class.state_machine(machine_name).states.matches?(object, name)})
162
164
  context.class_eval(&block)
163
165
  context.instance_methods.each do |method|
164
166
  methods[method.to_sym] = context.instance_method(method)
@@ -166,7 +168,7 @@ module StateMachine
166
168
  # Calls the method defined by the current state of the machine
167
169
  context.class_eval <<-end_eval, __FILE__, __LINE__
168
170
  def #{method}(*args, &block)
169
- self.class.state_machine(#{attribute.inspect}).states.match!(self).call(self, #{method.inspect}, *args, &block)
171
+ self.class.state_machine(#{machine_name.inspect}).states.match!(self).call(self, #{method.inspect}, *args, &block)
170
172
  end
171
173
  end_eval
172
174
  end
@@ -27,7 +27,7 @@ module StateMachine
27
27
  # states.matches?(vehicle, :idling) # => false
28
28
  # states.matches?(vehicle, :invalid) # => IndexError: :invalid is an invalid key for :name index
29
29
  def matches?(object, name)
30
- fetch(name).matches?(machine.read(object))
30
+ fetch(name).matches?(machine.read(object, :state))
31
31
  end
32
32
 
33
33
  # Determines the current state of the given object as configured by this
@@ -53,7 +53,7 @@ module StateMachine
53
53
  # vehicle.state = 'invalid'
54
54
  # states.match(vehicle) # => nil
55
55
  def match(object)
56
- value = machine.read(object)
56
+ value = machine.read(object, :state)
57
57
  self[value, :value] || detect {|state| state.matches?(value)}
58
58
  end
59
59
 
@@ -77,7 +77,7 @@ module StateMachine
77
77
  # vehicle.state = 'invalid'
78
78
  # states.match!(vehicle) # => ArgumentError: "invalid" is not a known state value
79
79
  def match!(object)
80
- match(object) || raise(ArgumentError, "#{machine.read(object).inspect} is not a known #{machine.attribute} value")
80
+ match(object) || raise(ArgumentError, "#{machine.read(object, :state).inspect} is not a known #{machine.name} value")
81
81
  end
82
82
 
83
83
  # Gets the order in which states should be displayed based on where they
@@ -136,7 +136,7 @@ module StateMachine
136
136
 
137
137
  # From state information
138
138
  from_state = machine.states.fetch(from_name)
139
- @from = machine.read(object)
139
+ @from = machine.read(object, :state)
140
140
  @from_name = from_state.name
141
141
  @qualified_from_name = from_state.qualified_name
142
142
 
@@ -259,7 +259,7 @@ module StateMachine
259
259
  #
260
260
  # vehicle.state # => 'idling'
261
261
  def persist
262
- machine.write(object, to)
262
+ machine.write(object, :state, to)
263
263
  end
264
264
 
265
265
  # Runs the machine's +after+ callbacks for this transition. Only
@@ -326,7 +326,7 @@ module StateMachine
326
326
  # transition.rollback
327
327
  # vehicle.state # => "parked"
328
328
  def rollback
329
- machine.write(object, from)
329
+ machine.write(object, :state, from)
330
330
  end
331
331
 
332
332
  # Generates a nicely formatted description of this transitions's contents.
@@ -45,6 +45,24 @@ class EvalHelpersSymbolWithArgumentsTest < Test::Unit::TestCase
45
45
  end
46
46
  end
47
47
 
48
+ class EvalHelpersSymbolTaintedMethodTest < Test::Unit::TestCase
49
+ include StateMachine::EvalHelpers
50
+
51
+ def setup
52
+ class << (@object = Object.new)
53
+ def callback
54
+ true
55
+ end
56
+
57
+ taint
58
+ end
59
+ end
60
+
61
+ def test_should_not_raise_security_error
62
+ assert_nothing_raised { evaluate_method(@object, :callback, 1, 2, 3) }
63
+ end
64
+ end
65
+
48
66
  class EvalHelpersStringTest < Test::Unit::TestCase
49
67
  include StateMachine::EvalHelpers
50
68
 
@@ -261,3 +261,33 @@ class EventCollectionWithValidationsTest < Test::Unit::TestCase
261
261
  StateMachine::Integrations.send(:remove_const, 'Custom')
262
262
  end
263
263
  end
264
+
265
+ class EventCollectionWithCustomMachineAttributeTest < Test::Unit::TestCase
266
+ def setup
267
+ @klass = Class.new do
268
+ def save
269
+ end
270
+ end
271
+
272
+ @machine = StateMachine::Machine.new(@klass, :state, :attribute => :state_id, :initial => :parked, :action => :save)
273
+ @events = StateMachine::EventCollection.new(@machine)
274
+
275
+ @machine.event :ignite
276
+ @machine.state :parked, :idling
277
+ @events << @ignite = StateMachine::Event.new(@machine, :ignite)
278
+
279
+ @object = @klass.new
280
+ end
281
+
282
+ def test_should_not_have_transition_if_nil
283
+ @object.state_event = nil
284
+ assert_nil @events.attribute_transition_for(@object)
285
+ end
286
+
287
+ def test_should_have_valid_transition_if_event_can_be_fired
288
+ @ignite.transition :parked => :idling
289
+ @object.state_event = 'ignite'
290
+
291
+ assert_instance_of StateMachine::Transition, @events.attribute_transition_for(@object)
292
+ end
293
+ end
@@ -172,6 +172,13 @@ begin
172
172
  assert_equal 'cannot transition via "park"', record.errors.on(:state)
173
173
  end
174
174
 
175
+ def test_should_auto_prefix_custom_attributes_on_invalidation
176
+ record = @model.new
177
+ @machine.invalidate(record, :event, :invalid)
178
+
179
+ assert_equal 'is invalid', record.errors.on(:state_event)
180
+ end
181
+
175
182
  def test_should_clear_errors_on_reset
176
183
  record = @model.new
177
184
  record.state = 'parked'
@@ -360,6 +367,60 @@ begin
360
367
  end
361
368
  end
362
369
 
370
+ class MachineWithOwnerSubclassTest < ActiveRecord::TestCase
371
+ def setup
372
+ @model = new_model
373
+ @machine = StateMachine::Machine.new(@model, :state)
374
+
375
+ @subclass = Class.new(@model)
376
+ @subclass_machine = @subclass.state_machine(:state) {}
377
+ @subclass_machine.state :parked, :idling, :first_gear
378
+ end
379
+
380
+ def test_should_only_include_records_with_subclass_states_in_with_scope
381
+ parked = @subclass.create :state => 'parked'
382
+ idling = @subclass.create :state => 'idling'
383
+
384
+ assert_equal [parked, idling], @subclass.with_states(:parked, :idling)
385
+ end
386
+
387
+ def test_should_only_include_records_without_subclass_states_in_without_scope
388
+ parked = @subclass.create :state => 'parked'
389
+ idling = @subclass.create :state => 'idling'
390
+ first_gear = @subclass.create :state => 'first_gear'
391
+
392
+ assert_equal [parked, idling], @subclass.without_states(:first_gear)
393
+ end
394
+ end
395
+
396
+ class MachineWithCustomAttributeTest < ActiveRecord::TestCase
397
+ def setup
398
+ @model = new_model
399
+ @machine = StateMachine::Machine.new(@model, :status, :attribute => :state)
400
+ @machine.state :parked
401
+
402
+ @record = @model.new
403
+ end
404
+
405
+ def test_should_add_validation_errors_to_custom_attribute
406
+ @record.state = 'invalid'
407
+
408
+ assert !@record.valid?
409
+ assert_equal ['State is invalid'], @record.errors.full_messages
410
+
411
+ @record.state = 'parked'
412
+ assert @record.valid?
413
+ end
414
+
415
+ def test_should_check_custom_attribute_for_predicate
416
+ @record.state = nil
417
+ assert !@record.status?(:parked)
418
+
419
+ @record.state = 'parked'
420
+ assert @record.status?(:parked)
421
+ end
422
+ end
423
+
363
424
  class MachineWithCallbacksTest < ActiveRecord::TestCase
364
425
  def setup
365
426
  @model = new_model
@@ -965,16 +1026,25 @@ begin
965
1026
  machine.invalidate(record, :state, :invalid_transition, [[:event, :ignite]])
966
1027
  assert_equal 'cannot ignite', record.errors.on(:state)
967
1028
  end
968
- end
969
-
970
- def test_should_invalidate_using_customized_i18n_string_if_specified
971
- machine = StateMachine::Machine.new(@model, :messages => {:invalid_transition => 'cannot {{event}}'})
972
- machine.state :parked, :idling
973
1029
 
974
- record = @model.new(:state => 'idling')
1030
+ def test_should_invalidate_using_customized_i18n_string_if_specified
1031
+ machine = StateMachine::Machine.new(@model, :messages => {:invalid_transition => 'cannot {{event}}'})
1032
+ machine.state :parked, :idling
1033
+
1034
+ record = @model.new(:state => 'idling')
1035
+
1036
+ machine.invalidate(record, :state, :invalid_transition, [[:event, :ignite]])
1037
+ assert_equal 'cannot ignite', record.errors.on(:state)
1038
+ end
975
1039
 
976
- machine.invalidate(record, :state, :invalid_transition, [[:event, :ignite]])
977
- assert_equal 'cannot ignite', record.errors.on(:state)
1040
+ def test_should_only_add_locale_once_in_load_path
1041
+ assert_equal 1, I18n.load_path.select {|path| path =~ %r{state_machine/integrations/active_record/locale\.rb$}}.length
1042
+
1043
+ # Create another ActiveRecord model that will triger the i18n feature
1044
+ new_model
1045
+
1046
+ assert_equal 1, I18n.load_path.select {|path| path =~ %r{state_machine/integrations/active_record/locale\.rb$}}.length
1047
+ end
978
1048
  end
979
1049
  else
980
1050
  $stderr.puts 'Skipping ActiveRecord I18n tests. `gem install active_record` >= v2.2.0 and try again.'
@@ -232,6 +232,57 @@ begin
232
232
  end
233
233
  end
234
234
 
235
+ class MachineWithOwnerSubclassTest < BaseTestCase
236
+ def setup
237
+ @resource = new_resource
238
+ @machine = StateMachine::Machine.new(@resource, :state)
239
+
240
+ @subclass = Class.new(@resource)
241
+ @subclass_machine = @subclass.state_machine(:state) {}
242
+ @subclass_machine.state :parked, :idling, :first_gear
243
+ end
244
+
245
+ def test_should_only_include_records_with_subclass_states_in_with_scope
246
+ parked = @subclass.create :state => 'parked'
247
+ idling = @subclass.create :state => 'idling'
248
+
249
+ assert_equal [parked, idling], @subclass.with_states(:parked, :idling)
250
+ end
251
+
252
+ def test_should_only_include_records_without_subclass_states_in_without_scope
253
+ parked = @subclass.create :state => 'parked'
254
+ idling = @subclass.create :state => 'idling'
255
+ first_gear = @subclass.create :state => 'first_gear'
256
+
257
+ assert_equal [parked, idling], @subclass.without_states(:first_gear)
258
+ end
259
+ end
260
+
261
+ class MachineWithTransactionsTest < BaseTestCase
262
+ def setup
263
+ @resource = new_resource
264
+ @machine = StateMachine::Machine.new(@resource, :use_transactions => true)
265
+ end
266
+
267
+ def test_should_rollback_transaction_if_false
268
+ @machine.within_transaction(@resource.new) do
269
+ @resource.create
270
+ false
271
+ end
272
+
273
+ assert_equal 0, @resource.all.size
274
+ end
275
+
276
+ def test_should_not_rollback_transaction_if_true
277
+ @machine.within_transaction(@resource.new) do
278
+ @resource.create
279
+ true
280
+ end
281
+
282
+ assert_equal 1, @resource.all.size
283
+ end
284
+ end
285
+
235
286
  class MachineWithCallbacksTest < BaseTestCase
236
287
  def setup
237
288
  @resource = new_resource
@@ -544,6 +595,12 @@ begin
544
595
  assert_equal ['cannot transition via "park"'], @record.errors.on(:state)
545
596
  end
546
597
 
598
+ def test_should_auto_prefix_custom_attributes_on_invalidation
599
+ @machine.invalidate(@record, :event, :invalid)
600
+
601
+ assert_equal ['is invalid'], @record.errors.on(:state_event)
602
+ end
603
+
547
604
  def test_should_clear_errors_on_reset
548
605
  @record.state = 'parked'
549
606
  @record.errors.add(:state, 'is invalid')
@@ -566,6 +623,26 @@ begin
566
623
  end
567
624
  end
568
625
 
626
+ class MachineWithValidationsAndCustomAttributeTest < BaseTestCase
627
+ def setup
628
+ @resource = new_resource
629
+ @machine = StateMachine::Machine.new(@resource, :status, :attribute => :state)
630
+ @machine.state :parked
631
+
632
+ @record = @resource.new
633
+ end
634
+
635
+ def test_should_add_validation_errors_to_custom_attribute
636
+ @record.state = 'invalid'
637
+
638
+ assert !@record.valid?
639
+ assert_equal ['is invalid'], @record.errors.on(:state)
640
+
641
+ @record.state = 'parked'
642
+ assert @record.valid?
643
+ end
644
+ end
645
+
569
646
  class MachineWithStateDrivenValidationsTest < BaseTestCase
570
647
  def setup
571
648
  @resource = new_resource do
@@ -25,10 +25,9 @@ begin
25
25
  end if auto_migrate
26
26
  model = Class.new(Sequel::Model(:foo)) do
27
27
  self.raise_on_save_failure = false
28
- plugin :validation_class_methods if respond_to?(:plugin)
29
-
30
28
  def self.name; 'SequelTest::Foo'; end
31
29
  end
30
+ model.plugin(:validation_class_methods) if model.respond_to?(:plugin)
32
31
  model.class_eval(&block) if block_given?
33
32
  model
34
33
  end
@@ -146,6 +145,13 @@ begin
146
145
  assert_equal ['cannot transition via "park"'], record.errors.on(:state)
147
146
  end
148
147
 
148
+ def test_should_auto_prefix_custom_attributes_on_invalidation
149
+ record = @model.new
150
+ @machine.invalidate(record, :event, :invalid)
151
+
152
+ assert_equal ['is invalid'], record.errors.on(:state_event)
153
+ end
154
+
149
155
  def test_should_clear_errors_on_reset
150
156
  record = @model.new
151
157
  record.state = 'parked'
@@ -245,6 +251,52 @@ begin
245
251
  end
246
252
  end
247
253
 
254
+ class MachineWithOwnerSubclassTest < BaseTestCase
255
+ def setup
256
+ @model = new_model
257
+ @machine = StateMachine::Machine.new(@model, :state)
258
+
259
+ @subclass = Class.new(@model)
260
+ @subclass_machine = @subclass.state_machine(:state) {}
261
+ @subclass_machine.state :parked, :idling, :first_gear
262
+ end
263
+
264
+ def test_should_only_include_records_with_subclass_states_in_with_scope
265
+ parked = @subclass.create :state => 'parked'
266
+ idling = @subclass.create :state => 'idling'
267
+
268
+ assert_equal [parked, idling], @subclass.with_states(:parked, :idling).all
269
+ end
270
+
271
+ def test_should_only_include_records_without_subclass_states_in_without_scope
272
+ parked = @subclass.create :state => 'parked'
273
+ idling = @subclass.create :state => 'idling'
274
+ first_gear = @subclass.create :state => 'first_gear'
275
+
276
+ assert_equal [parked, idling], @subclass.without_states(:first_gear).all
277
+ end
278
+ end
279
+
280
+ class MachineWithCustomAttributeTest < BaseTestCase
281
+ def setup
282
+ @model = new_model
283
+ @machine = StateMachine::Machine.new(@model, :status, :attribute => :state)
284
+ @machine.state :parked
285
+
286
+ @record = @model.new
287
+ end
288
+
289
+ def test_should_add_validation_errors_to_custom_attribute
290
+ @record.state = 'invalid'
291
+
292
+ assert !@record.valid?
293
+ assert_equal ['is invalid'], @record.errors.on(:state)
294
+
295
+ @record.state = 'parked'
296
+ assert @record.valid?
297
+ end
298
+ end
299
+
248
300
  class MachineWithCallbacksTest < BaseTestCase
249
301
  def setup
250
302
  @model = new_model
@@ -688,3 +688,23 @@ class MachineCollectionFireImplicitWithValidationsTest < Test::Unit::TestCase
688
688
  StateMachine::Integrations.send(:remove_const, 'Custom')
689
689
  end
690
690
  end
691
+
692
+ class MachineCollectionFireImplicitWithCustomMachineNameTest < MachineCollectionFireImplicitTest
693
+ def setup
694
+ super
695
+
696
+ @object.state_event = 'ignite'
697
+ end
698
+
699
+ def test_should_be_successful_on_complete_file
700
+ assert @machines.fire_event_attributes(@object, :save) { true }
701
+ assert_equal 'idling', @object.state
702
+ assert_nil @object.state_event
703
+ end
704
+
705
+ def test_should_be_successful_on_partial_fire
706
+ @machines.fire_event_attributes(@object, :save, false) { true }
707
+ assert_equal 'idling', @object.state
708
+ assert_equal :ignite, @object.state_event
709
+ end
710
+ end