state_machine 0.7.5 → 0.7.6

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