state_machine 0.7.4 → 0.7.5

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