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.
- 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
|
|