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.
- data/CHANGELOG.rdoc +8 -0
- data/Rakefile +1 -1
- data/lib/state_machine/integrations/active_record.rb +8 -4
- data/lib/state_machine/integrations/data_mapper.rb +1 -1
- data/lib/state_machine/integrations/sequel.rb +1 -1
- data/lib/state_machine/machine.rb +66 -6
- data/lib/state_machine/machine_collection.rb +11 -8
- data/lib/state_machine/node_collection.rb +7 -2
- data/lib/state_machine/state.rb +27 -7
- data/lib/state_machine/state_collection.rb +6 -0
- data/test/unit/integrations/active_record_test.rb +42 -2
- data/test/unit/integrations/data_mapper_test.rb +36 -1
- data/test/unit/integrations/sequel_test.rb +36 -1
- data/test/unit/machine_collection_test.rb +53 -26
- data/test/unit/machine_test.rb +35 -0
- data/test/unit/state_test.rb +49 -2
- metadata +2 -2
data/CHANGELOG.rdoc
CHANGED
@@ -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.
|
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
|
331
|
-
|
332
|
-
|
333
|
-
|
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.
|
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.
|
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
|
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
|
-
|
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
|
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
|
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.
|
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.
|
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.
|
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
|
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
|
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
|
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
|
data/lib/state_machine/state.rb
CHANGED
@@ -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.
|
105
|
-
#
|
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
|
111
|
-
# State.new(machine, :parked, :value => lambda {Time.now}).value
|
112
|
-
|
113
|
-
|
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, :
|
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
|
-
|
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, :
|
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, :
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
503
|
+
@machines.fire_event_attributes(@object, :save) { true }
|
477
504
|
|
478
505
|
@object.state = 'idling'
|
479
506
|
@object.state_event = 'ignite'
|
480
|
-
assert !@machines.
|
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.
|
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.
|
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.
|
520
|
+
@machines.fire_event_attributes(@object, :save) { false }
|
494
521
|
|
495
522
|
@object.state = 'idling'
|
496
|
-
assert !@machines.
|
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.
|
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.
|
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.
|
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.
|
524
|
-
@partial_result = @machines.
|
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.
|
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.
|
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.
|
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.
|
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.
|
682
|
+
@machines.fire_event_attributes(@object, :save) { true }
|
656
683
|
|
657
684
|
assert @object.errors.empty?
|
658
685
|
end
|
data/test/unit/machine_test.rb
CHANGED
@@ -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
|
data/test/unit/state_test.rb
CHANGED
@@ -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
|
-
@
|
226
|
+
@value = lambda {|*args| @args = args; :parked}
|
227
|
+
@state = StateMachine::State.new(@machine, :parked, :value => @value)
|
227
228
|
end
|
228
229
|
|
229
|
-
def
|
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
|
+
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-
|
12
|
+
date: 2009-05-25 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|