state_machine 0.7.4 → 0.7.5

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.
@@ -1,5 +1,13 @@
1
1
  == master
2
2
 
3
+ == 0.7.5 / 2009-05-25
4
+
5
+ * Add built-in caching for dynamic state values when the value only needs to be generated once
6
+ * Fix flawed example for using record ids as state values
7
+ * Don't evaluate state values until they're actually used in an object instance
8
+ * Make it easier to use event attributes for actions defined in the same class as the state machine
9
+ * Fix #save/save! running transitions in ActiveRecord integrations even when a machine's action is not :save
10
+
3
11
  == 0.7.4 / 2009-05-23
4
12
 
5
13
  * Fix #save! not firing event attributes properly in ActiveRecord integrations
data/Rakefile CHANGED
@@ -5,7 +5,7 @@ require 'rake/contrib/sshpublisher'
5
5
 
6
6
  spec = Gem::Specification.new do |s|
7
7
  s.name = 'state_machine'
8
- s.version = '0.7.4'
8
+ s.version = '0.7.5'
9
9
  s.platform = Gem::Platform::RUBY
10
10
  s.summary = 'Adds support for creating state machines for attributes on any Ruby class'
11
11
  s.description = s.summary
@@ -327,12 +327,16 @@ module StateMachine
327
327
 
328
328
  # Adds hooks into validation for automatically firing events
329
329
  def define_action_helpers
330
- if super(:create_or_update) && action == :save
331
- @instance_helper_module.class_eval do
332
- define_method(:valid?) do |*args|
333
- self.class.state_machines.fire_attribute_events(self, :save, false) { super(*args) }
330
+ if action == :save
331
+ if super(:create_or_update)
332
+ @instance_helper_module.class_eval do
333
+ define_method(:valid?) do |*args|
334
+ self.class.state_machines.fire_event_attributes(self, :save, false) { super(*args) }
335
+ end
334
336
  end
335
337
  end
338
+ else
339
+ super
336
340
  end
337
341
  end
338
342
 
@@ -287,7 +287,7 @@ module StateMachine
287
287
  if super && action == :save && supports_validations?
288
288
  @instance_helper_module.class_eval do
289
289
  define_method(:valid?) do |*args|
290
- self.class.state_machines.fire_attribute_events(self, :save, false) { super(*args) }
290
+ self.class.state_machines.fire_event_attributes(self, :save, false) { super(*args) }
291
291
  end
292
292
  end
293
293
  end
@@ -253,7 +253,7 @@ module StateMachine
253
253
  if super && action == :save
254
254
  @instance_helper_module.class_eval do
255
255
  define_method(:valid?) do |*args|
256
- self.class.state_machines.fire_attribute_events(self, :save, false) { super(*args) }
256
+ self.class.state_machines.fire_event_attributes(self, :save, false) { super(*args) }
257
257
  end
258
258
  end
259
259
  end
@@ -69,6 +69,46 @@ module StateMachine
69
69
  # fails. *Note* that this will also be the case if an exception is raised
70
70
  # while calling the action.
71
71
  #
72
+ # === Indirect transitions
73
+ #
74
+ # In addition to the action being run as the _result_ of an event, the action
75
+ # can also be used to run events itself. For example, using the above as an
76
+ # example:
77
+ #
78
+ # vehicle = Vehicle.new # => #<Vehicle:0xb7c27024 @state="parked">
79
+ #
80
+ # vehicle.state_event = 'ignite'
81
+ # vehicle.save # => true
82
+ # vehicle.state # => "idling"
83
+ # vehicle.state_event # => nil
84
+ #
85
+ # As can be seen, the +save+ action automatically invokes the event stored in
86
+ # the +state_event+ attribute (<tt>:ignite</tt> in this case).
87
+ #
88
+ # One important note about using this technique for running transitions is
89
+ # that if the class in which the state machine is defined *also* defines the
90
+ # action being invoked (and not a superclass), then it must manually run the
91
+ # StateMachine hook that checks for event attributes.
92
+ #
93
+ # For example, in ActiveRecord, DataMapper, and Sequel, the default action
94
+ # (+save+) is already defined in a base class. As a result, when a state
95
+ # machine is defined in a model / resource, StateMachine can automatically
96
+ # hook into the +save+ action.
97
+ #
98
+ # On the other hand, the Vehicle class from above defined its own +save+
99
+ # method (and there is no +save+ method in its superclass). As a result, it
100
+ # must be modified like so:
101
+ #
102
+ # def save
103
+ # self.class.state_machines.fire_event_attributes(self, :save) do
104
+ # @saving_state = state
105
+ # fail != true
106
+ # end
107
+ # end
108
+ #
109
+ # This will add in the functionality for firing the event stored in the
110
+ # +state_event+ attribute.
111
+ #
72
112
  # == Callbacks
73
113
  #
74
114
  # Callbacks are supported for hooking before and after every possible
@@ -527,6 +567,8 @@ module StateMachine
527
567
  # Configuration options:
528
568
  # * <tt>:value</tt> - The actual value to store when an object transitions
529
569
  # to the state. Default is the name (stringified).
570
+ # * <tt>:cache</tt> - If a dynamic value (via a lambda block) is being used,
571
+ # then setting this to true will cache the evaluated result
530
572
  # * <tt>:if</tt> - Determines whether an object's value matches the state
531
573
  # (e.g. :value => lambda {Time.now}, :if => lambda {|state| !state.nil?}).
532
574
  # By default, the configured value is matched.
@@ -579,12 +621,31 @@ module StateMachine
579
621
  # transition :parked => :idling
580
622
  # end
581
623
  #
582
- # states.each {|state| self.state(state.name, :value => VehicleState.find_by_name(state.name.to_s).id)}
624
+ # states.each do |state|
625
+ # self.state(state.name, :value => lambda { VehicleState.find_by_name(state.name.to_s).id }, :cache => true)
626
+ # end
583
627
  # end
584
628
  # end
585
629
  #
586
630
  # In the above example, each known state is configured to store it's
587
- # associated database id in the +state_id+ attribute.
631
+ # associated database id in the +state_id+ attribute. Also, notice that a
632
+ # lambda block is used to define the state's value. This is required in
633
+ # situations (like testing) where the model is loaded without any existing
634
+ # data (i.e. no VehicleState records available).
635
+ #
636
+ # One caveat to the above example is to keep performance in mind. To avoid
637
+ # constant db hits for looking up the VehicleState ids, the value is cached
638
+ # by specifying the <tt>:cache</tt> option. Alternatively, a custom
639
+ # caching strategy can be used like so:
640
+ #
641
+ # class VehicleState < ActiveRecord::Base
642
+ # cattr_accessor :cache_store
643
+ # self.cache_store = ActiveSupport::Cache::MemoryStore.new
644
+ #
645
+ # def self.find_by_name(name)
646
+ # cache_store.fetch(name) { find(:first, :conditions => {:name => name}) }
647
+ # end
648
+ # end
588
649
  #
589
650
  # === Dynamic values
590
651
  #
@@ -745,7 +806,7 @@ module StateMachine
745
806
  # options hash which contains at least <tt>:if</tt> condition support.
746
807
  def state(*names, &block)
747
808
  options = names.last.is_a?(Hash) ? names.pop : {}
748
- assert_valid_keys(options, :value, :if)
809
+ assert_valid_keys(options, :value, :cache, :if)
749
810
 
750
811
  states = add_states(names)
751
812
  states.each do |state|
@@ -754,6 +815,7 @@ module StateMachine
754
815
  self.states.update(state)
755
816
  end
756
817
 
818
+ state.cache = options[:cache] if options.include?(:cache)
757
819
  state.matcher = options[:if] if options.include?(:if)
758
820
  state.context(&block) if block_given?
759
821
  end
@@ -1277,9 +1339,7 @@ module StateMachine
1277
1339
  @instance_helper_module.class_eval do
1278
1340
  # Override the default action to invoke the before / after hooks
1279
1341
  define_method(action_hook) do |*args|
1280
- value = nil
1281
- result = self.class.state_machines.fire_attribute_events(self, action) { value = super(*args) }
1282
- value.nil? ? result : value
1342
+ self.class.state_machines.fire_event_attributes(self, action) { super(*args) }
1283
1343
  end
1284
1344
 
1285
1345
  private action_hook if private_method
@@ -44,12 +44,12 @@ module StateMachine
44
44
  end
45
45
  end
46
46
 
47
- # Runs one or more attribute events in parallel during the invocation of
47
+ # Runs one or more event attributes in parallel during the invocation of
48
48
  # an action on the given object. After transition callbacks can be
49
49
  # optionally disabled if the events are being only partially fired (for
50
50
  # example, when validating records in ORM integrations).
51
51
  #
52
- # The attribute events that will be fired are based on which machines
52
+ # The event attributes that will be fired are based on which machines
53
53
  # match the action that is being invoked.
54
54
  #
55
55
  # == Examples
@@ -77,7 +77,7 @@ module StateMachine
77
77
  # vehicle.state_event = 'ignite'
78
78
  # vehicle.alarm_state_event = 'disable'
79
79
  #
80
- # Vehicle.state_machines.fire_attribute_events(vehicle, :save) { true }
80
+ # Vehicle.state_machines.fire_event_attributes(vehicle, :save) { true }
81
81
  # vehicle.state # => "idling"
82
82
  # vehicle.state_event # => nil
83
83
  # vehicle.alarm_state # => "off"
@@ -89,7 +89,7 @@ module StateMachine
89
89
  # vehicle.state_event = 'park'
90
90
  # vehicle.alarm_state_event = 'disable'
91
91
  #
92
- # Vehicle.state_machines.fire_attribute_events(vehicle, :save) { true }
92
+ # Vehicle.state_machines.fire_event_attributes(vehicle, :save) { true }
93
93
  # vehicle.state # => "parked"
94
94
  # vehicle.state_event # => nil
95
95
  # vehicle.alarm_state # => "active"
@@ -101,22 +101,25 @@ module StateMachine
101
101
  # vehicle = Vehicle.create # => #<Vehicle id=1 state="parked" alarm_state="active">
102
102
  # vehicle.state_event = 'ignite'
103
103
  #
104
- # Vehicle.state_machines.fire_attribute_events(vehicle, :save, false) { true }
104
+ # Vehicle.state_machines.fire_event_attributes(vehicle, :save, false) { true }
105
105
  # vehicle.state # => "idling"
106
106
  # vehicle.state_event # => "ignite"
107
107
  # vehicle.state_event_transition # => #<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
108
- def fire_attribute_events(object, action, complete = true)
108
+ def fire_event_attributes(object, action, complete = true)
109
109
  # Get the transitions to fire for each applicable machine
110
110
  transitions = map {|attribute, machine| machine.action == action ? machine.events.attribute_transition_for(object, true) : nil}.compact
111
111
  return yield if transitions.empty?
112
112
 
113
+ # The value generated by the yielded block (the actual action)
114
+ action_value = nil
115
+
113
116
  # Make sure all events were valid
114
117
  if result = transitions.all? {|transition| transition != false}
115
118
  begin
116
119
  result = Transition.perform(transitions, :after => complete) do
117
120
  # Prevent events from being evaluated multiple times if actions are nested
118
121
  transitions.each {|transition| object.send("#{transition.attribute}_event=", nil)}
119
- yield
122
+ action_value = yield
120
123
  end
121
124
  rescue Exception
122
125
  # Revert attribute modifications
@@ -139,7 +142,7 @@ module StateMachine
139
142
  end
140
143
  end
141
144
 
142
- result
145
+ action_value.nil? ? result : action_value
143
146
  end
144
147
  end
145
148
  end
@@ -58,7 +58,7 @@ module StateMachine
58
58
  # the configured indices.
59
59
  def <<(node)
60
60
  @nodes << node
61
- @indices.each {|attribute, index| index[node.send(attribute)] = node}
61
+ @indices.each {|attribute, index| index[value(node, attribute)] = node}
62
62
  self
63
63
  end
64
64
 
@@ -68,7 +68,7 @@ module StateMachine
68
68
  def update(node)
69
69
  @indices.each do |attribute, index|
70
70
  old_key = RUBY_VERSION < '1.9' ? index.index(node) : index.key(node)
71
- new_key = node.send(attribute)
71
+ new_key = value(node, attribute)
72
72
 
73
73
  # Only replace the key if it's changed
74
74
  if old_key != new_key
@@ -143,5 +143,10 @@ module StateMachine
143
143
  raise ArgumentError, 'No indices configured' unless @indices.any?
144
144
  @indices[name] || raise(ArgumentError, "Invalid index: #{name.inspect}")
145
145
  end
146
+
147
+ # Gets the value for the given attribute on the node
148
+ def value(node, attribute)
149
+ node.send(attribute)
150
+ end
146
151
  end
147
152
  end
@@ -27,6 +27,9 @@ module StateMachine
27
27
  # transitions into this state
28
28
  attr_writer :value
29
29
 
30
+ # Whether this state's value should be cached after being evaluated
31
+ attr_accessor :cache
32
+
30
33
  # Whether or not this state is the initial state to use for new objects
31
34
  attr_accessor :initial
32
35
  alias_method :initial?, :initial
@@ -48,16 +51,19 @@ module StateMachine
48
51
  # machine. Default is false.
49
52
  # * <tt>:value</tt> - The value to store when an object transitions to this
50
53
  # state. Default is the name (stringified).
54
+ # * <tt>:cache</tt> - If a dynamic value (via a lambda block) is being used,
55
+ # then setting this to true will cache the evaluated result
51
56
  # * <tt>:if</tt> - Determines whether a value matches this state
52
57
  # (e.g. :value => lambda {Time.now}, :if => lambda {|state| !state.nil?}).
53
58
  # By default, the configured value is matched.
54
59
  def initialize(machine, name, options = {}) #:nodoc:
55
- assert_valid_keys(options, :initial, :value, :if)
60
+ assert_valid_keys(options, :initial, :value, :cache, :if)
56
61
 
57
62
  @machine = machine
58
63
  @name = name
59
64
  @qualified_name = name && machine.namespace ? :"#{machine.namespace}_#{name}" : name
60
65
  @value = options.include?(:value) ? options[:value] : name && name.to_s
66
+ @cache = options[:cache]
61
67
  @matcher = options[:if]
62
68
  @methods = {}
63
69
  @initial = options[:initial] == true
@@ -101,16 +107,25 @@ module StateMachine
101
107
  description
102
108
  end
103
109
 
104
- # The value that represents this state. If the value is a lambda block,
105
- # then it will be evaluated at this time. Otherwise, the static value is
110
+ # The value that represents this state. This will optionally evaluate the
111
+ # original block if it's a lambda block. Otherwise, the static value is
106
112
  # returned.
107
113
  #
108
114
  # For example,
109
115
  #
110
- # State.new(machine, :parked, :value => 1).value # => 1
111
- # State.new(machine, :parked, :value => lambda {Time.now}).value # => Tue Jan 01 00:00:00 UTC 2008
112
- def value
113
- @value.is_a?(Proc) ? @value.call : @value
116
+ # State.new(machine, :parked, :value => 1).value # => 1
117
+ # State.new(machine, :parked, :value => lambda {Time.now}).value # => Tue Jan 01 00:00:00 UTC 2008
118
+ # State.new(machine, :parked, :value => lambda {Time.now}).value(false) # => <Proc:0xb6ea7ca0@...>
119
+ def value(eval = true)
120
+ if @value.is_a?(Proc) && eval
121
+ if cache_value?
122
+ instance_variable_defined?('@cached_value') ? @cached_value : @cached_value = @value.call
123
+ else
124
+ @value.call
125
+ end
126
+ else
127
+ @value
128
+ end
114
129
  end
115
130
 
116
131
  # Determines whether this state matches the given value. If no matcher is
@@ -213,6 +228,11 @@ module StateMachine
213
228
  end
214
229
 
215
230
  private
231
+ # Should the value be cached after it's evaluated for the first time?
232
+ def cache_value?
233
+ @cache
234
+ end
235
+
216
236
  # Adds a predicate method to the owner class so long as a name has
217
237
  # actually been configured for the state
218
238
  def add_predicate
@@ -102,5 +102,11 @@ module StateMachine
102
102
  order.map! {|name| self[name]}
103
103
  order
104
104
  end
105
+
106
+ private
107
+ # Gets the value for the given attribute on the node
108
+ def value(node, attribute)
109
+ attribute == :value ? node.value(false) : super
110
+ end
105
111
  end
106
112
  end
@@ -86,7 +86,8 @@ begin
86
86
  def setup
87
87
  @model = new_model
88
88
  @machine = StateMachine::Machine.new(@model)
89
- @machine.state :parked, :idling, :first_gear
89
+ @machine.state :parked, :first_gear
90
+ @machine.state :idling, :value => lambda {'idling'}
90
91
  end
91
92
 
92
93
  def test_should_create_singular_with_scope
@@ -686,7 +687,7 @@ begin
686
687
  end
687
688
 
688
689
  def test_should_be_successful_if_event_has_transition
689
- assert @record.save!
690
+ assert_equal true, @record.save!
690
691
  end
691
692
 
692
693
  def test_should_run_before_callbacks
@@ -711,6 +712,45 @@ begin
711
712
  end
712
713
  end
713
714
 
715
+ class MachineWithEventAttributesOnCustomActionTest < ActiveRecord::TestCase
716
+ def setup
717
+ @superclass = new_model do
718
+ def persist
719
+ create_or_update
720
+ end
721
+ end
722
+ @model = Class.new(@superclass)
723
+ @machine = StateMachine::Machine.new(@model, :action => :persist)
724
+ @machine.event :ignite do
725
+ transition :parked => :idling
726
+ end
727
+
728
+ @record = @model.new
729
+ @record.state = 'parked'
730
+ @record.state_event = 'ignite'
731
+ end
732
+
733
+ def test_should_not_transition_on_valid?
734
+ @record.valid?
735
+ assert_equal 'parked', @record.state
736
+ end
737
+
738
+ def test_should_not_transition_on_save
739
+ @record.save
740
+ assert_equal 'parked', @record.state
741
+ end
742
+
743
+ def test_should_not_transition_on_save!
744
+ @record.save!
745
+ assert_equal 'parked', @record.state
746
+ end
747
+
748
+ def test_should_transition_on_custom_action
749
+ @record.persist
750
+ assert_equal 'idling', @record.state
751
+ end
752
+ end
753
+
714
754
  class MachineWithObserversTest < ActiveRecord::TestCase
715
755
  def setup
716
756
  @model = new_model
@@ -74,7 +74,8 @@ begin
74
74
  def setup
75
75
  @resource = new_resource
76
76
  @machine = StateMachine::Machine.new(@resource)
77
- @machine.state :parked, :idling, :first_gear
77
+ @machine.state :parked, :first_gear
78
+ @machine.state :idling, :value => lambda {'idling'}
78
79
  end
79
80
 
80
81
  def test_should_create_singular_with_scope
@@ -644,6 +645,40 @@ begin
644
645
  assert !ran_callback
645
646
  end
646
647
  end
648
+
649
+ class MachineWithEventAttributesOnCustomActionTest < BaseTestCase
650
+ def setup
651
+ @superclass = new_resource do
652
+ def persist
653
+ save
654
+ end
655
+ end
656
+ @resource = Class.new(@superclass)
657
+ @machine = StateMachine::Machine.new(@resource, :action => :persist)
658
+ @machine.event :ignite do
659
+ transition :parked => :idling
660
+ end
661
+
662
+ @record = @resource.new
663
+ @record.state = 'parked'
664
+ @record.state_event = 'ignite'
665
+ end
666
+
667
+ def test_should_not_transition_on_valid?
668
+ @record.valid?
669
+ assert_equal 'parked', @record.state
670
+ end
671
+
672
+ def test_should_not_transition_on_save
673
+ @record.save
674
+ assert_equal 'parked', @record.state
675
+ end
676
+
677
+ def test_should_transition_on_custom_action
678
+ @record.persist
679
+ assert_equal 'idling', @record.state
680
+ end
681
+ end
647
682
  rescue LoadError
648
683
  $stderr.puts "Skipping DataMapper Validation tests. `gem install dm-validations#{" -v #{ENV['DM_VERSION']}" if ENV['DM_VERSION']}` and try again."
649
684
  end
@@ -63,7 +63,8 @@ begin
63
63
  def setup
64
64
  @model = new_model
65
65
  @machine = StateMachine::Machine.new(@model)
66
- @machine.state :parked, :idling, :first_gear
66
+ @machine.state :parked, :first_gear
67
+ @machine.state :idling, :value => lambda {'idling'}
67
68
  end
68
69
 
69
70
  def test_should_create_singular_with_scope
@@ -438,6 +439,40 @@ begin
438
439
  assert !ran_callback
439
440
  end
440
441
  end
442
+
443
+ class MachineWithEventAttributesOnCustomActionTest < BaseTestCase
444
+ def setup
445
+ @superclass = new_model do
446
+ def persist
447
+ save
448
+ end
449
+ end
450
+ @model = Class.new(@superclass)
451
+ @machine = StateMachine::Machine.new(@model, :action => :persist)
452
+ @machine.event :ignite do
453
+ transition :parked => :idling
454
+ end
455
+
456
+ @record = @model.new
457
+ @record.state = 'parked'
458
+ @record.state_event = 'ignite'
459
+ end
460
+
461
+ def test_should_not_transition_on_valid?
462
+ @record.valid?
463
+ assert_equal 'parked', @record.state
464
+ end
465
+
466
+ def test_should_not_transition_on_save
467
+ @record.save
468
+ assert_equal 'parked', @record.state
469
+ end
470
+
471
+ def test_should_transition_on_custom_action
472
+ @record.persist
473
+ assert_equal 'idling', @record.state
474
+ end
475
+ end
441
476
  end
442
477
  rescue LoadError
443
478
  $stderr.puts "Skipping Sequel tests. `gem install sequel#{" -v #{ENV['SEQUEL_VERSION']}" if ENV['SEQUEL_VERSION']}` and try again."
@@ -26,6 +26,7 @@ class MachineCollectionStateInitializationTest < Test::Unit::TestCase
26
26
 
27
27
  @machines[:state] = StateMachine::Machine.new(@klass, :state, :initial => :parked)
28
28
  @machines[:alarm_state] = StateMachine::Machine.new(@klass, :alarm_state, :initial => :active)
29
+ @machines[:alarm_state].state :active, :value => lambda {'active'}
29
30
  end
30
31
 
31
32
  def test_should_set_states_if_nil
@@ -251,7 +252,7 @@ class MachineCollectionFireImplicitWithoutEventTest < MachineCollectionFireImpli
251
252
  super
252
253
 
253
254
  @object.state_event = nil
254
- @result = @machines.fire_attribute_events(@object, :save) { @saved = true }
255
+ @result = @machines.fire_event_attributes(@object, :save) { @saved = true }
255
256
  end
256
257
 
257
258
  def test_should_be_successful
@@ -276,7 +277,7 @@ class MachineCollectionFireImplicitWithBlankEventTest < MachineCollectionFireImp
276
277
  super
277
278
 
278
279
  @object.state_event = ''
279
- @result = @machines.fire_attribute_events(@object, :save) { @saved = true }
280
+ @result = @machines.fire_event_attributes(@object, :save) { @saved = true }
280
281
  end
281
282
 
282
283
  def test_should_be_successful
@@ -301,7 +302,7 @@ class MachineCollectionFireImplicitWithInvalidEventTest < MachineCollectionFireI
301
302
  super
302
303
 
303
304
  @object.state_event = 'invalid'
304
- @result = @machines.fire_attribute_events(@object, :save) { @saved = true }
305
+ @result = @machines.fire_event_attributes(@object, :save) { @saved = true }
305
306
  end
306
307
 
307
308
  def test_should_not_be_successful
@@ -327,7 +328,7 @@ class MachineCollectionFireImplicitWithoutTransitionTest < MachineCollectionFire
327
328
 
328
329
  @object.state = 'idling'
329
330
  @object.state_event = 'ignite'
330
- @result = @machines.fire_attribute_events(@object, :save) { @saved = true }
331
+ @result = @machines.fire_event_attributes(@object, :save) { @saved = true }
331
332
  end
332
333
 
333
334
  def test_should_not_be_successful
@@ -354,7 +355,7 @@ class MachineCollectionFireImplicitWithTransitionTest < MachineCollectionFireImp
354
355
  @state_event = nil
355
356
 
356
357
  @object.state_event = 'ignite'
357
- @result = @machines.fire_attribute_events(@object, :save) do
358
+ @result = @machines.fire_event_attributes(@object, :save) do
358
359
  @state_event = @object.state_event
359
360
  @saved = true
360
361
  end
@@ -382,7 +383,33 @@ class MachineCollectionFireImplicitWithTransitionTest < MachineCollectionFireImp
382
383
 
383
384
  def test_should_not_be_successful_if_fired_again
384
385
  @object.state_event = 'ignite'
385
- assert !@machines.fire_attribute_events(@object, :save) { true }
386
+ assert !@machines.fire_event_attributes(@object, :save) { true }
387
+ end
388
+ end
389
+
390
+ class MachineCollectionFireImplicitWithNonBooleanResultTest < MachineCollectionFireImplicitTest
391
+ def setup
392
+ super
393
+
394
+ @action_value = Object.new
395
+
396
+ @object.state_event = 'ignite'
397
+ @result = @machines.fire_event_attributes(@object, :save) do
398
+ @saved = true
399
+ @action_value
400
+ end
401
+ end
402
+
403
+ def test_should_be_successful
404
+ assert_equal @action_value, @result
405
+ end
406
+
407
+ def test_should_run_action
408
+ assert @saved
409
+ end
410
+
411
+ def test_should_transition_state
412
+ assert_equal 'idling', @object.state
386
413
  end
387
414
  end
388
415
 
@@ -391,7 +418,7 @@ class MachineCollectionFireImplicitWithActionFailureTest < MachineCollectionFire
391
418
  super
392
419
 
393
420
  @object.state_event = 'ignite'
394
- @result = @machines.fire_attribute_events(@object, :save) { false }
421
+ @result = @machines.fire_event_attributes(@object, :save) { false }
395
422
  end
396
423
 
397
424
  def test_should_not_be_successful
@@ -412,7 +439,7 @@ class MachineCollectionFireImplicitWithActionErrorTest < MachineCollectionFireIm
412
439
  super
413
440
 
414
441
  @object.state_event = 'ignite'
415
- assert_raise(ArgumentError) { @machines.fire_attribute_events(@object, :save) { raise ArgumentError } }
442
+ assert_raise(ArgumentError) { @machines.fire_event_attributes(@object, :save) { raise ArgumentError } }
416
443
  end
417
444
 
418
445
  def test_should_not_transition_state
@@ -436,7 +463,7 @@ class MachineCollectionFireImplicitPartialTest < MachineCollectionFireImplicitTe
436
463
  @state_event = nil
437
464
 
438
465
  @object.state_event = 'ignite'
439
- @result = @machines.fire_attribute_events(@object, :save, false) do
466
+ @result = @machines.fire_event_attributes(@object, :save, false) do
440
467
  @state_event = @object.state_event
441
468
  true
442
469
  end
@@ -467,49 +494,49 @@ class MachineCollectionFireImplicitPartialTest < MachineCollectionFireImplicitTe
467
494
  end
468
495
 
469
496
  def test_should_reset_event_attributes_after_next_fire_on_success
470
- assert @machines.fire_attribute_events(@object, :save) { true }
497
+ assert @machines.fire_event_attributes(@object, :save) { true }
471
498
  assert_equal 'idling', @object.state
472
499
  assert_nil @object.state_event
473
500
  end
474
501
 
475
502
  def test_should_guard_transition_after_next_fire_on_success
476
- @machines.fire_attribute_events(@object, :save) { true }
503
+ @machines.fire_event_attributes(@object, :save) { true }
477
504
 
478
505
  @object.state = 'idling'
479
506
  @object.state_event = 'ignite'
480
- assert !@machines.fire_attribute_events(@object, :save) { true }
507
+ assert !@machines.fire_event_attributes(@object, :save) { true }
481
508
  end
482
509
 
483
510
  def test_should_rollback_all_attributes_after_next_fire_on_failure
484
- assert !@machines.fire_attribute_events(@object, :save) { false }
511
+ assert !@machines.fire_event_attributes(@object, :save) { false }
485
512
  assert_equal 'parked', @object.state
486
513
  assert_equal :ignite, @object.state_event
487
514
 
488
515
  @object.state = 'idling'
489
- assert !@machines.fire_attribute_events(@object, :save) { false }
516
+ assert !@machines.fire_event_attributes(@object, :save) { false }
490
517
  end
491
518
 
492
519
  def test_should_guard_transition_after_next_fire_on_failure
493
- @machines.fire_attribute_events(@object, :save) { false }
520
+ @machines.fire_event_attributes(@object, :save) { false }
494
521
 
495
522
  @object.state = 'idling'
496
- assert !@machines.fire_attribute_events(@object, :save) { true }
523
+ assert !@machines.fire_event_attributes(@object, :save) { true }
497
524
  end
498
525
 
499
526
  def test_should_rollback_all_attributes_after_next_fire_on_error
500
- assert_raise(ArgumentError) { @machines.fire_attribute_events(@object, :save) { raise ArgumentError } }
527
+ assert_raise(ArgumentError) { @machines.fire_event_attributes(@object, :save) { raise ArgumentError } }
501
528
  assert_equal 'parked', @object.state
502
529
  assert_equal :ignite, @object.state_event
503
530
  end
504
531
 
505
532
  def test_should_guard_transition_after_next_fire_on_error
506
533
  begin
507
- @machines.fire_attribute_events(@object, :save) { raise ArgumentError }
534
+ @machines.fire_event_attributes(@object, :save) { raise ArgumentError }
508
535
  rescue ArgumentError
509
536
  end
510
537
 
511
538
  @object.state = 'idling'
512
- assert !@machines.fire_attribute_events(@object, :save) { true }
539
+ assert !@machines.fire_event_attributes(@object, :save) { true }
513
540
  end
514
541
  end
515
542
 
@@ -520,8 +547,8 @@ class MachineCollectionFireImplicitNestedPartialTest < MachineCollectionFireImpl
520
547
  @partial_result = nil
521
548
 
522
549
  @object.state_event = 'ignite'
523
- @result = @machines.fire_attribute_events(@object, :save) do
524
- @partial_result = @machines.fire_attribute_events(@object, :save, false) { true }
550
+ @result = @machines.fire_event_attributes(@object, :save) do
551
+ @partial_result = @machines.fire_event_attributes(@object, :save, false) { true }
525
552
  true
526
553
  end
527
554
  end
@@ -557,7 +584,7 @@ class MachineCollectionFireImplicitWithDifferentActionsTest < MachineCollectionF
557
584
  @object.state_event = 'ignite'
558
585
  @object.alarm_state_event = 'disable'
559
586
 
560
- @machines.fire_attribute_events(@object, :save) { true }
587
+ @machines.fire_event_attributes(@object, :save) { true }
561
588
  end
562
589
 
563
590
  def test_should_transition_states_for_action
@@ -591,7 +618,7 @@ class MachineCollectionFireImplicitWithSameActionsTest < MachineCollectionFireIm
591
618
  @object.state_event = 'ignite'
592
619
  @object.alarm_state_event = 'disable'
593
620
 
594
- @machines.fire_attribute_events(@object, :save) { true }
621
+ @machines.fire_event_attributes(@object, :save) { true }
595
622
  end
596
623
 
597
624
  def test_should_transition_all_states_for_action
@@ -637,7 +664,7 @@ class MachineCollectionFireImplicitWithValidationsTest < Test::Unit::TestCase
637
664
 
638
665
  def test_should_invalidate_if_event_is_invalid
639
666
  @object.state_event = 'invalid'
640
- @machines.fire_attribute_events(@object, :save) { true }
667
+ @machines.fire_event_attributes(@object, :save) { true }
641
668
 
642
669
  assert !@object.errors.empty?
643
670
  end
@@ -645,14 +672,14 @@ class MachineCollectionFireImplicitWithValidationsTest < Test::Unit::TestCase
645
672
  def test_should_invalidate_if_no_transition_exists
646
673
  @object.state = 'idling'
647
674
  @object.state_event = 'ignite'
648
- @machines.fire_attribute_events(@object, :save) { true }
675
+ @machines.fire_event_attributes(@object, :save) { true }
649
676
 
650
677
  assert !@object.errors.empty?
651
678
  end
652
679
 
653
680
  def test_should_not_invalidate_if_transition_exists
654
681
  @object.state_event = 'ignite'
655
- @machines.fire_attribute_events(@object, :save) { true }
682
+ @machines.fire_event_attributes(@object, :save) { true }
656
683
 
657
684
  assert @object.errors.empty?
658
685
  end
@@ -1067,6 +1067,23 @@ class MachineWithStatesWithCustomValuesTest < Test::Unit::TestCase
1067
1067
  end
1068
1068
  end
1069
1069
 
1070
+ class MachineWithStatesWithRuntimeDependenciesTest < Test::Unit::TestCase
1071
+ def setup
1072
+ @klass = Class.new
1073
+ @machine = StateMachine::Machine.new(@klass)
1074
+ @machine.state :parked
1075
+ end
1076
+
1077
+ def test_should_not_evaluate_value_during_definition
1078
+ assert_nothing_raised { @machine.state :parked, :value => lambda {raise ArgumentError} }
1079
+ end
1080
+
1081
+ def test_should_not_evaluate_if_not_initial_state
1082
+ @machine.state :parked, :value => lambda {raise ArgumentError}
1083
+ assert_nothing_raised { @klass.new }
1084
+ end
1085
+ end
1086
+
1070
1087
  class MachineWithStateWithMatchersTest < Test::Unit::TestCase
1071
1088
  def setup
1072
1089
  @klass = Class.new
@@ -1084,6 +1101,24 @@ class MachineWithStateWithMatchersTest < Test::Unit::TestCase
1084
1101
  end
1085
1102
  end
1086
1103
 
1104
+ class MachineWithCachedStateTest < Test::Unit::TestCase
1105
+ def setup
1106
+ @klass = Class.new
1107
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
1108
+ @state = @machine.state :parked, :value => lambda {Object.new}, :cache => true
1109
+
1110
+ @object = @klass.new
1111
+ end
1112
+
1113
+ def test_should_use_evaluated_value
1114
+ assert_instance_of Object, @object.state
1115
+ end
1116
+
1117
+ def test_use_same_value_across_multiple_objects
1118
+ assert_equal @object.state, @klass.new.state
1119
+ end
1120
+ end
1121
+
1087
1122
  class MachineWithStatesWithBehaviorsTest < Test::Unit::TestCase
1088
1123
  def setup
1089
1124
  @klass = Class.new
@@ -223,13 +223,18 @@ class StateWithLambdaValueTest < Test::Unit::TestCase
223
223
  @klass = Class.new
224
224
  @args = nil
225
225
  @machine = StateMachine::Machine.new(@klass)
226
- @state = StateMachine::State.new(@machine, :parked, :value => lambda {|*args| @args = args; :parked})
226
+ @value = lambda {|*args| @args = args; :parked}
227
+ @state = StateMachine::State.new(@machine, :parked, :value => @value)
227
228
  end
228
229
 
229
- def test_should_use_evaluated_value
230
+ def test_should_use_evaluated_value_by_default
230
231
  assert_equal :parked, @state.value
231
232
  end
232
233
 
234
+ def test_should_allow_access_to_original_value
235
+ assert_equal @value, @state.value(false)
236
+ end
237
+
233
238
  def test_should_include_masked_value_in_description
234
239
  assert_equal 'parked (*)', @state.description
235
240
  end
@@ -243,6 +248,48 @@ class StateWithLambdaValueTest < Test::Unit::TestCase
243
248
  object = @klass.new
244
249
  assert object.respond_to?(:parked?)
245
250
  end
251
+
252
+ def test_should_match_evaluated_value
253
+ assert @state.matches?(:parked)
254
+ end
255
+ end
256
+
257
+ class StateWithCachedLambdaValueTest < Test::Unit::TestCase
258
+ def setup
259
+ @klass = Class.new
260
+ @machine = StateMachine::Machine.new(@klass)
261
+ @state = StateMachine::State.new(@machine, :parked, :value => lambda {Object.new}, :cache => true)
262
+ end
263
+
264
+ def test_should_be_caching
265
+ assert @state.cache
266
+ end
267
+
268
+ def test_should_evaluate_value
269
+ assert_instance_of Object, @state.value
270
+ end
271
+
272
+ def test_should_only_evaluate_value_once
273
+ value = @state.value
274
+ assert_same value, @state.value
275
+ end
276
+ end
277
+
278
+ class StateWithoutCachedLambdaValueTest < Test::Unit::TestCase
279
+ def setup
280
+ @klass = Class.new
281
+ @machine = StateMachine::Machine.new(@klass)
282
+ @state = StateMachine::State.new(@machine, :parked, :value => lambda {Object.new})
283
+ end
284
+
285
+ def test_should_not_be_caching
286
+ assert !@state.cache
287
+ end
288
+
289
+ def test_should_evaluate_value_each_time
290
+ value = @state.value
291
+ assert_not_same value, @state.value
292
+ end
246
293
  end
247
294
 
248
295
  class StateWithMatcherTest < Test::Unit::TestCase
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: state_machine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.4
4
+ version: 0.7.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Pfeifer
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-05-23 00:00:00 -04:00
12
+ date: 2009-05-25 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies: []
15
15