pluginaweek-state_machine 0.7.6 → 0.8.0
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 +21 -1
- data/README.rdoc +1 -1
- data/Rakefile +1 -1
- data/lib/state_machine.rb +24 -65
- data/lib/state_machine/callback.rb +1 -1
- data/lib/state_machine/event.rb +10 -9
- data/lib/state_machine/event_collection.rb +21 -10
- data/lib/state_machine/extensions.rb +2 -11
- data/lib/state_machine/guard.rb +12 -1
- data/lib/state_machine/integrations/active_record.rb +51 -13
- data/lib/state_machine/integrations/active_record/locale.rb +1 -0
- data/lib/state_machine/integrations/data_mapper.rb +22 -10
- data/lib/state_machine/integrations/data_mapper/observer.rb +5 -5
- data/lib/state_machine/integrations/sequel.rb +36 -7
- data/lib/state_machine/machine.rb +91 -55
- data/lib/state_machine/machine_collection.rb +15 -13
- data/lib/state_machine/state.rb +4 -4
- data/lib/state_machine/transition.rb +41 -14
- data/test/unit/assertions_test.rb +3 -3
- data/test/unit/eval_helpers_test.rb +13 -22
- data/test/unit/event_collection_test.rb +26 -2
- data/test/unit/event_test.rb +94 -0
- data/test/unit/guard_test.rb +46 -0
- data/test/unit/integrations/active_record_test.rb +294 -11
- data/test/unit/integrations/data_mapper_test.rb +214 -1
- data/test/unit/integrations/sequel_test.rb +321 -7
- data/test/unit/machine_collection_test.rb +42 -19
- data/test/unit/machine_test.rb +104 -10
- data/test/unit/state_test.rb +1 -1
- data/test/unit/transition_test.rb +108 -9
- metadata +4 -3
data/CHANGELOG.rdoc
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
== master
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
== 0.8.0 / 2009-08-15
|
|
4
|
+
|
|
5
|
+
* Add support for DataMapper 0.10.0
|
|
6
|
+
* Always interpet nil return values from actions as failed attempts
|
|
7
|
+
* Fix loopbacks not causing records to save in ORM integrations if no other fields were changed
|
|
8
|
+
* Fix events not failing with useful errors when an object's state is invalid
|
|
9
|
+
* Use more friendly NoMethodError messages for state-driven behaviors
|
|
10
|
+
* Fix before_transition callbacks getting run twice when using event attributes in ORM integrations
|
|
11
|
+
* Add the ability to query for the availability of specific transitions on an object
|
|
12
|
+
* Allow after_transition callbacks to be explicitly run on failed attempts
|
|
13
|
+
* By default, don't run after_transition callbacks on failed attempts
|
|
14
|
+
* Fix not allowing multiple methods to be specified as arguments in callbacks
|
|
15
|
+
* Fix initial states being set when loading records from the database in Sequel integration
|
|
16
|
+
* Allow static initial states to be set earlier in the initialization of an object
|
|
17
|
+
* Use friendly validation errors for nil states
|
|
18
|
+
* Fix states not being validated properly when using custom names in ActiveRecord / DataMapper integrations
|
|
19
|
+
|
|
20
|
+
== 0.7.6 / 2009-06-17
|
|
21
|
+
|
|
22
|
+
* Allow multiple state machines on the same class to target the same attribute
|
|
23
|
+
* Add support for :attribute to customize the attribute target, assuming the name is the first argument of #state_machine
|
|
4
24
|
* Simplify reading from / writing to machine-related attributes on objects
|
|
5
25
|
* Fix locale for ActiveRecord getting added to the i18n load path multiple times [Reiner Dieterich]
|
|
6
26
|
* Fix callbacks, guards, and state-driven behaviors not always working on tainted classes [Brandon Dimcheff]
|
data/README.rdoc
CHANGED
|
@@ -412,7 +412,7 @@ To generate multiple state machine graphs:
|
|
|
412
412
|
|
|
413
413
|
*Note* that this will generate a different file for every state machine defined
|
|
414
414
|
in the class. The generated files will use an output filename of the format
|
|
415
|
-
#{class_name}_#{
|
|
415
|
+
#{class_name}_#{machine_name}.#{format}.
|
|
416
416
|
|
|
417
417
|
For examples of actual images generated using this task, see those under the
|
|
418
418
|
examples folder.
|
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.
|
|
8
|
+
s.version = '0.8.0'
|
|
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
|
data/lib/state_machine.rb
CHANGED
|
@@ -5,10 +5,12 @@ require 'state_machine/machine'
|
|
|
5
5
|
# functionality on any Ruby class.
|
|
6
6
|
module StateMachine
|
|
7
7
|
module MacroMethods
|
|
8
|
-
# Creates a new state machine
|
|
9
|
-
#
|
|
8
|
+
# Creates a new state machine with the given name. The default name, if not
|
|
9
|
+
# specified, is <tt>:state</tt>.
|
|
10
10
|
#
|
|
11
11
|
# Configuration options:
|
|
12
|
+
# * <tt>:attribute</tt> - The name of the attribute to store the state value
|
|
13
|
+
# in. By default, this is the same as the name of the machine.
|
|
12
14
|
# * <tt>:initial</tt> - The initial state of the attribute. This can be a
|
|
13
15
|
# static state or a lambda block which will be evaluated at runtime
|
|
14
16
|
# (e.g. lambda {|vehicle| vehicle.speed == 0 ? :parked : :idling}).
|
|
@@ -16,10 +18,6 @@ module StateMachine
|
|
|
16
18
|
# * <tt>:action</tt> - The instance method to invoke when an object
|
|
17
19
|
# transitions. Default is nil unless otherwise specified by the
|
|
18
20
|
# configured integration.
|
|
19
|
-
# * <tt>:as</tt> - The name to use for prefixing all generated machine
|
|
20
|
-
# instance / class methods (e.g. if the attribute is +state_id+, then
|
|
21
|
-
# "state" would generate :state_name, :state_transitions, etc. instead of
|
|
22
|
-
# :state_id_name and :state_id_transitions)
|
|
23
21
|
# * <tt>:namespace</tt> - The name to use for namespacing all generated
|
|
24
22
|
# state / event instance methods (e.g. "heater" would generate
|
|
25
23
|
# :turn_on_heater and :turn_off_heater for the :turn_on/:turn_off events).
|
|
@@ -49,12 +47,12 @@ module StateMachine
|
|
|
49
47
|
# result, you will not be able to access any class methods unless you refer
|
|
50
48
|
# to them directly (i.e. specifying the class name).
|
|
51
49
|
#
|
|
52
|
-
# For examples on the types of
|
|
50
|
+
# For examples on the types of state machine configurations and blocks, see
|
|
53
51
|
# the section below.
|
|
54
52
|
#
|
|
55
53
|
# == Examples
|
|
56
54
|
#
|
|
57
|
-
# With the default attribute and no configuration:
|
|
55
|
+
# With the default name/attribute and no configuration:
|
|
58
56
|
#
|
|
59
57
|
# class Vehicle
|
|
60
58
|
# state_machine do
|
|
@@ -64,13 +62,14 @@ module StateMachine
|
|
|
64
62
|
# end
|
|
65
63
|
# end
|
|
66
64
|
#
|
|
67
|
-
# The above example will define a state machine
|
|
68
|
-
#
|
|
65
|
+
# The above example will define a state machine named "state" that will
|
|
66
|
+
# store the value in the +state+ attribute. Every vehicle will start
|
|
67
|
+
# without an initial state.
|
|
69
68
|
#
|
|
70
|
-
# With a custom attribute:
|
|
69
|
+
# With a custom name / attribute:
|
|
71
70
|
#
|
|
72
71
|
# class Vehicle
|
|
73
|
-
# state_machine :status do
|
|
72
|
+
# state_machine :status, :attribute => :status_value do
|
|
74
73
|
# ...
|
|
75
74
|
# end
|
|
76
75
|
# end
|
|
@@ -94,7 +93,8 @@ module StateMachine
|
|
|
94
93
|
# == Instance Methods
|
|
95
94
|
#
|
|
96
95
|
# The following instance methods will be automatically generated by the
|
|
97
|
-
# state machine. Any existing methods
|
|
96
|
+
# state machine based on the *name* of the machine. Any existing methods
|
|
97
|
+
# will not be overwritten.
|
|
98
98
|
# * <tt>state</tt> - Gets the current value for the attribute
|
|
99
99
|
# * <tt>state=(value)</tt> - Sets the current value for the attribute
|
|
100
100
|
# * <tt>state?(name)</tt> - Checks the given state name against the current
|
|
@@ -102,8 +102,11 @@ module StateMachine
|
|
|
102
102
|
# * <tt>state_name</tt> - Gets the name of the state for the current value
|
|
103
103
|
# * <tt>state_events</tt> - Gets the list of events that can be fired on
|
|
104
104
|
# the current object's state (uses the *unqualified* event names)
|
|
105
|
-
# * <tt>state_transitions</tt> - Gets the list of possible
|
|
106
|
-
# that can be made on the current object's state
|
|
105
|
+
# * <tt>state_transitions(requirements = {})</tt> - Gets the list of possible
|
|
106
|
+
# transitions that can be made on the current object's state. Additional
|
|
107
|
+
# requirements, such as the :from / :to state and :on event can be specified
|
|
108
|
+
# to restrict the transitions to select. By default, the current state
|
|
109
|
+
# will be used for the :from state.
|
|
107
110
|
#
|
|
108
111
|
# For example,
|
|
109
112
|
#
|
|
@@ -253,7 +256,7 @@ module StateMachine
|
|
|
253
256
|
#
|
|
254
257
|
# class Vehicle
|
|
255
258
|
# include DataMapper::Resource
|
|
256
|
-
# property :id,
|
|
259
|
+
# property :id, Serial
|
|
257
260
|
#
|
|
258
261
|
# state_machine :initial => :parked do
|
|
259
262
|
# event :ignite do
|
|
@@ -295,55 +298,11 @@ module StateMachine
|
|
|
295
298
|
# see StateMachine::Machine#before_transition and
|
|
296
299
|
# StateMachine::Machine#after_transition.
|
|
297
300
|
#
|
|
298
|
-
# == Attribute aliases
|
|
299
|
-
#
|
|
300
|
-
# When a state machine is defined, several methods are generated scoped by
|
|
301
|
-
# the name of the attribute, such as (if the attribute were "state"):
|
|
302
|
-
# * <tt>state_name</tt>
|
|
303
|
-
# * <tt>state_event</tt>
|
|
304
|
-
# * <tt>state_transitions</tt>
|
|
305
|
-
# * etc.
|
|
306
|
-
#
|
|
307
|
-
# If the attribute for the machine were something less common, such as
|
|
308
|
-
# "state_id" or "state_value", this makes for more awkward scoped methods.
|
|
309
|
-
#
|
|
310
|
-
# Rather than scope based on the attribute, these methods can be customized
|
|
311
|
-
# using the <tt>:as</tt> option as essentially an alias.
|
|
312
|
-
#
|
|
313
|
-
# For example,
|
|
314
|
-
#
|
|
315
|
-
# class Vehicle
|
|
316
|
-
# state_machine :state_id, :as => :state do
|
|
317
|
-
# event :turn_on do
|
|
318
|
-
# transition all => :on
|
|
319
|
-
# end
|
|
320
|
-
#
|
|
321
|
-
# event :turn_off do
|
|
322
|
-
# transition all => :off
|
|
323
|
-
# end
|
|
324
|
-
#
|
|
325
|
-
# state :on, :value => 1
|
|
326
|
-
# state :off, :value => 2
|
|
327
|
-
# end
|
|
328
|
-
# end
|
|
329
|
-
#
|
|
330
|
-
# ...will generate the following methods:
|
|
331
|
-
# * <tt>state_name</tt>
|
|
332
|
-
# * <tt>state_event</tt>
|
|
333
|
-
# * <tt>state_transitions</tt>
|
|
334
|
-
#
|
|
335
|
-
# ...instead of:
|
|
336
|
-
# * <tt>state_id_name</tt>
|
|
337
|
-
# * <tt>state_id_event</tt>
|
|
338
|
-
# * <tt>state_id_transitions</tt>
|
|
339
|
-
#
|
|
340
|
-
# However, it will continue to read and write to the +state_id+ attribute.
|
|
341
|
-
#
|
|
342
301
|
# == Namespaces
|
|
343
302
|
#
|
|
344
303
|
# When a namespace is configured for a state machine, the name provided
|
|
345
304
|
# will be used in generating the instance methods for interacting with
|
|
346
|
-
# events
|
|
305
|
+
# states/events in the machine. This is particularly useful when a class
|
|
347
306
|
# has multiple state machines and it would be difficult to differentiate
|
|
348
307
|
# between the various states / events.
|
|
349
308
|
#
|
|
@@ -398,7 +357,7 @@ module StateMachine
|
|
|
398
357
|
#
|
|
399
358
|
# For integrations that support it, a group of default scope filters will
|
|
400
359
|
# be automatically created for assisting in finding objects that have the
|
|
401
|
-
# attribute set to
|
|
360
|
+
# attribute set to one of a given set of states.
|
|
402
361
|
#
|
|
403
362
|
# For example,
|
|
404
363
|
#
|
|
@@ -412,9 +371,9 @@ module StateMachine
|
|
|
412
371
|
# :with_state, :with_states, :without_state, or :without_states), then a
|
|
413
372
|
# scope will not be defined for that name.
|
|
414
373
|
#
|
|
415
|
-
# See StateMachine::Machine for more information about using
|
|
416
|
-
#
|
|
417
|
-
#
|
|
374
|
+
# See StateMachine::Machine for more information about using integrations
|
|
375
|
+
# and the individual integration docs for information about the actual
|
|
376
|
+
# scopes that are generated.
|
|
418
377
|
def state_machine(*args, &block)
|
|
419
378
|
StateMachine::Machine.find_or_create(self, *args, &block)
|
|
420
379
|
end
|
|
@@ -142,7 +142,7 @@ module StateMachine
|
|
|
142
142
|
# requirements configured for this callback.
|
|
143
143
|
#
|
|
144
144
|
# If a terminator has been configured and it matches the result from the
|
|
145
|
-
# evaluated method, then the callback chain should be halted
|
|
145
|
+
# evaluated method, then the callback chain should be halted.
|
|
146
146
|
def call(object, context = {}, *args)
|
|
147
147
|
if @guard.matches?(object, context)
|
|
148
148
|
@methods.each do |method|
|
data/lib/state_machine/event.rb
CHANGED
|
@@ -65,8 +65,8 @@ module StateMachine
|
|
|
65
65
|
# state to be +idling+ if it's current state is +parked+ or +first_gear+
|
|
66
66
|
# if it's current state is +idling+.
|
|
67
67
|
#
|
|
68
|
-
# To help
|
|
69
|
-
# for
|
|
68
|
+
# To help define these implicit transitions, a set of helpers are available
|
|
69
|
+
# for slightly more complex matching:
|
|
70
70
|
# * <tt>all</tt> - Matches every state in the machine
|
|
71
71
|
# * <tt>all - [:parked, :idling, ...]</tt> - Matches every state except those specified
|
|
72
72
|
# * <tt>any</tt> - An alias for +all+ (matches every state in the machine)
|
|
@@ -147,7 +147,7 @@ module StateMachine
|
|
|
147
147
|
# requirements
|
|
148
148
|
assert_valid_keys(options, :from, :to, :except_from, :if, :unless) if (options.keys - [:from, :to, :on, :except_from, :except_to, :except_on, :if, :unless]).empty?
|
|
149
149
|
|
|
150
|
-
guards << guard = Guard.new(options)
|
|
150
|
+
guards << guard = Guard.new(options.merge(:on => name))
|
|
151
151
|
@known_states |= guard.known_states
|
|
152
152
|
guard
|
|
153
153
|
end
|
|
@@ -162,15 +162,16 @@ module StateMachine
|
|
|
162
162
|
|
|
163
163
|
# Finds and builds the next transition that can be performed on the given
|
|
164
164
|
# object. If no transitions can be made, then this will return nil.
|
|
165
|
-
def transition_for(object)
|
|
166
|
-
from = machine.states.match(object).name
|
|
165
|
+
def transition_for(object, requirements = {})
|
|
166
|
+
requirements[:from] = machine.states.match!(object).name unless custom_from_state = requirements.include?(:from)
|
|
167
167
|
|
|
168
168
|
guards.each do |guard|
|
|
169
|
-
if match = guard.match(object,
|
|
169
|
+
if match = guard.match(object, requirements)
|
|
170
170
|
# Guard allows for the transition to occur
|
|
171
|
+
from = requirements[:from]
|
|
171
172
|
to = match[:to].values.empty? ? from : match[:to].values.first
|
|
172
173
|
|
|
173
|
-
return Transition.new(object, machine, name, from, to)
|
|
174
|
+
return Transition.new(object, machine, name, from, to, !custom_from_state)
|
|
174
175
|
end
|
|
175
176
|
end
|
|
176
177
|
|
|
@@ -190,7 +191,7 @@ module StateMachine
|
|
|
190
191
|
if transition = transition_for(object)
|
|
191
192
|
transition.perform(*args)
|
|
192
193
|
else
|
|
193
|
-
machine.invalidate(object,
|
|
194
|
+
machine.invalidate(object, :state, :invalid_transition, [[:event, name]])
|
|
194
195
|
false
|
|
195
196
|
end
|
|
196
197
|
end
|
|
@@ -244,7 +245,7 @@ module StateMachine
|
|
|
244
245
|
|
|
245
246
|
# Fires the event, raising an exception if it fails
|
|
246
247
|
machine.define_instance_method("#{qualified_name}!") do |machine, object, *args|
|
|
247
|
-
object.send(qualified_name, *args) || raise(StateMachine::InvalidTransition, "Cannot transition #{machine.name} via :#{name} from #{machine.states.match(object).name.inspect}")
|
|
248
|
+
object.send(qualified_name, *args) || raise(StateMachine::InvalidTransition, "Cannot transition #{machine.name} via :#{name} from #{machine.states.match!(object).name.inspect}")
|
|
248
249
|
end
|
|
249
250
|
end
|
|
250
251
|
end
|
|
@@ -34,6 +34,14 @@ module StateMachine
|
|
|
34
34
|
|
|
35
35
|
# Gets the list of transitions that can be run on the given object.
|
|
36
36
|
#
|
|
37
|
+
# Valid requirement options:
|
|
38
|
+
# * <tt>:from</tt> - One or more states being transitioned from. If none
|
|
39
|
+
# are specified, then this will be the object's current state.
|
|
40
|
+
# * <tt>:to</tt> - One or more states being transitioned to. If none are
|
|
41
|
+
# specified, then this will match any to state.
|
|
42
|
+
# * <tt>:on</tt> - One or more events that fire the transition. If none
|
|
43
|
+
# are specified, then this will match any event.
|
|
44
|
+
#
|
|
37
45
|
# == Examples
|
|
38
46
|
#
|
|
39
47
|
# class Vehicle
|
|
@@ -50,22 +58,25 @@ module StateMachine
|
|
|
50
58
|
#
|
|
51
59
|
# events = Vehicle.state_machine.events
|
|
52
60
|
#
|
|
53
|
-
# vehicle = Vehicle.new
|
|
54
|
-
# events.transitions_for(vehicle)
|
|
61
|
+
# vehicle = Vehicle.new # => #<Vehicle:0xb7c464b0 @state="parked">
|
|
62
|
+
# events.transitions_for(vehicle) # => [#<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
|
|
55
63
|
#
|
|
56
64
|
# vehicle.state = 'idling'
|
|
57
|
-
# events.transitions_for(vehicle)
|
|
58
|
-
|
|
59
|
-
|
|
65
|
+
# events.transitions_for(vehicle) # => [#<StateMachine::Transition attribute=:state event=:park from="idling" from_name=:idling to="parked" to_name=:parked>]
|
|
66
|
+
#
|
|
67
|
+
# # Search for explicit transitions regardless of the current state
|
|
68
|
+
# events.transitions_for(vehicle, :from => :parked) # => [#<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
|
|
69
|
+
def transitions_for(object, requirements = {})
|
|
70
|
+
map {|event| event.transition_for(object, requirements)}.compact
|
|
60
71
|
end
|
|
61
72
|
|
|
62
73
|
# Gets the transition that should be performed for the event stored in the
|
|
63
74
|
# given object's event attribute. This also takes an additional parameter
|
|
64
|
-
# for automatically invalidating the object if the event or transition
|
|
65
|
-
#
|
|
75
|
+
# for automatically invalidating the object if the event or transition are
|
|
76
|
+
# invalid. By default, this is turned off.
|
|
66
77
|
#
|
|
67
|
-
# *Note* that if a transition has already been generated for the event,
|
|
68
|
-
#
|
|
78
|
+
# *Note* that if a transition has already been generated for the event, then
|
|
79
|
+
# that transition will be used.
|
|
69
80
|
#
|
|
70
81
|
# == Examples
|
|
71
82
|
#
|
|
@@ -97,7 +108,7 @@ module StateMachine
|
|
|
97
108
|
if event = self[event_name.to_sym, :name]
|
|
98
109
|
unless result = machine.read(object, :event_transition) || event.transition_for(object)
|
|
99
110
|
# No valid transition: invalidate
|
|
100
|
-
machine.invalidate(object, :event, :invalid_event, [[:state, machine.states.match!(object).name]]) if invalidate
|
|
111
|
+
machine.invalidate(object, :event, :invalid_event, [[:state, machine.states.match!(object).name || 'nil']]) if invalidate
|
|
101
112
|
result = false
|
|
102
113
|
end
|
|
103
114
|
else
|
|
@@ -22,15 +22,6 @@ module StateMachine
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
module InstanceMethods
|
|
25
|
-
# Defines the initial values for state machine attributes. The values
|
|
26
|
-
# will be set *after* the original initialize method is invoked. This is
|
|
27
|
-
# necessary in order to ensure that the object is initialized before
|
|
28
|
-
# dynamic initial attributes are evaluated.
|
|
29
|
-
def initialize(*args, &block)
|
|
30
|
-
super
|
|
31
|
-
initialize_state_machines
|
|
32
|
-
end
|
|
33
|
-
|
|
34
25
|
# Runs one or more events in parallel. All events will run through the
|
|
35
26
|
# following steps:
|
|
36
27
|
# * Before callbacks
|
|
@@ -151,8 +142,8 @@ module StateMachine
|
|
|
151
142
|
end
|
|
152
143
|
|
|
153
144
|
protected
|
|
154
|
-
def initialize_state_machines #:nodoc:
|
|
155
|
-
self.class.state_machines.initialize_states(self)
|
|
145
|
+
def initialize_state_machines(options = {}) #:nodoc:
|
|
146
|
+
self.class.state_machines.initialize_states(self, options)
|
|
156
147
|
end
|
|
157
148
|
end
|
|
158
149
|
end
|
data/lib/state_machine/guard.rb
CHANGED
|
@@ -24,6 +24,9 @@ module StateMachine
|
|
|
24
24
|
# requirements contain a mapping of {:from => matcher, :to => matcher}.
|
|
25
25
|
attr_reader :state_requirements
|
|
26
26
|
|
|
27
|
+
# The requirement for verifying the success of the event
|
|
28
|
+
attr_reader :success_requirement
|
|
29
|
+
|
|
27
30
|
# A list of all of the states known to this guard. This will pull states
|
|
28
31
|
# from the following options (in the same order):
|
|
29
32
|
# * +from+ / +except_from+
|
|
@@ -39,6 +42,9 @@ module StateMachine
|
|
|
39
42
|
# Build event requirement
|
|
40
43
|
@event_requirement = build_matcher(options, :on, :except_on)
|
|
41
44
|
|
|
45
|
+
# Build success requirement
|
|
46
|
+
@success_requirement = options.delete(:include_failures) ? AllMatcher.instance : WhitelistMatcher.new([true])
|
|
47
|
+
|
|
42
48
|
if (options.keys - [:from, :to, :on, :except_from, :except_to, :except_on]).empty?
|
|
43
49
|
# Explicit from/to requirements specified
|
|
44
50
|
@state_requirements = [{:from => build_matcher(options, :from, :except_from), :to => build_matcher(options, :to, :except_to)}]
|
|
@@ -185,11 +191,16 @@ module StateMachine
|
|
|
185
191
|
def match_query(query)
|
|
186
192
|
query ||= {}
|
|
187
193
|
|
|
188
|
-
if match_event(query) && (state_requirement = match_states(query))
|
|
194
|
+
if match_success(query) && match_event(query) && (state_requirement = match_states(query))
|
|
189
195
|
state_requirement.merge(:on => event_requirement)
|
|
190
196
|
end
|
|
191
197
|
end
|
|
192
198
|
|
|
199
|
+
# Verifies that the success requirement matches the given query
|
|
200
|
+
def match_success(query)
|
|
201
|
+
matches_requirement?(query, :success, success_requirement)
|
|
202
|
+
end
|
|
203
|
+
|
|
193
204
|
# Verifies that the event requirement matches the given query
|
|
194
205
|
def match_event(query)
|
|
195
206
|
matches_requirement?(query, :on, event_requirement)
|
|
@@ -84,14 +84,14 @@ module StateMachine
|
|
|
84
84
|
# you can build two state machines (one public and one protected) like so:
|
|
85
85
|
#
|
|
86
86
|
# class Vehicle < ActiveRecord::Base
|
|
87
|
-
# alias_attribute :public_state # Allow both machines to share the same state
|
|
88
87
|
# attr_protected :state_event # Prevent access to events in the first machine
|
|
89
88
|
#
|
|
90
89
|
# state_machine do
|
|
91
90
|
# # Define private events here
|
|
92
91
|
# end
|
|
93
92
|
#
|
|
94
|
-
#
|
|
93
|
+
# # Public machine targets the same state as the private machine
|
|
94
|
+
# state_machine :public_state, :attribute => :state do
|
|
95
95
|
# # Define public events here
|
|
96
96
|
# end
|
|
97
97
|
# end
|
|
@@ -281,6 +281,14 @@ module StateMachine
|
|
|
281
281
|
end
|
|
282
282
|
end
|
|
283
283
|
|
|
284
|
+
# Forces the change in state to be recognized regardless of whether the
|
|
285
|
+
# state value actually changed
|
|
286
|
+
def write(object, attribute, value)
|
|
287
|
+
result = super
|
|
288
|
+
object.send("#{self.attribute}_will_change!") if attribute == :state && object.respond_to?("#{self.attribute}_will_change!")
|
|
289
|
+
result
|
|
290
|
+
end
|
|
291
|
+
|
|
284
292
|
# Adds a validation error to the given object
|
|
285
293
|
def invalidate(object, attribute, message, values = [])
|
|
286
294
|
attribute = self.attribute(attribute)
|
|
@@ -308,11 +316,41 @@ module StateMachine
|
|
|
308
316
|
callbacks[:after] << Callback.new {|object, transition| notify(:after, object, transition)}
|
|
309
317
|
end
|
|
310
318
|
|
|
319
|
+
# Defines an initialization hook into the owner class for setting the
|
|
320
|
+
# initial state of the machine *before* any attributes are set on the
|
|
321
|
+
# object
|
|
322
|
+
def define_state_initializer
|
|
323
|
+
@instance_helper_module.class_eval <<-end_eval, __FILE__, __LINE__
|
|
324
|
+
# Ensure that the attributes setter gets used to force initialization
|
|
325
|
+
# of the state machines
|
|
326
|
+
def initialize(attributes = nil, *args)
|
|
327
|
+
attributes ||= {}
|
|
328
|
+
super
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Hooks in to attribute initialization to set the states *prior*
|
|
332
|
+
# to the attributes being set
|
|
333
|
+
def attributes=(*args)
|
|
334
|
+
if new_record? && !@initialized_state_machines
|
|
335
|
+
@initialized_state_machines = true
|
|
336
|
+
|
|
337
|
+
initialize_state_machines(:dynamic => false)
|
|
338
|
+
super
|
|
339
|
+
initialize_state_machines(:dynamic => true)
|
|
340
|
+
else
|
|
341
|
+
super
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
end_eval
|
|
345
|
+
end
|
|
346
|
+
|
|
311
347
|
# Skips defining reader/writer methods since this is done automatically
|
|
312
348
|
def define_state_accessor
|
|
349
|
+
name = self.name
|
|
350
|
+
|
|
313
351
|
owner_class.validates_each(attribute) do |record, attr, value|
|
|
314
|
-
machine = record.class.state_machine(
|
|
315
|
-
machine.invalidate(record,
|
|
352
|
+
machine = record.class.state_machine(name)
|
|
353
|
+
machine.invalidate(record, :state, :invalid) unless machine.states.match(record)
|
|
316
354
|
end
|
|
317
355
|
end
|
|
318
356
|
|
|
@@ -321,13 +359,12 @@ module StateMachine
|
|
|
321
359
|
# *anything* is set for the attribute's value
|
|
322
360
|
def define_state_predicate
|
|
323
361
|
name = self.name
|
|
324
|
-
attribute = self.attribute
|
|
325
362
|
|
|
326
363
|
# Still use class_eval here instance of define_instance_method since
|
|
327
364
|
# we need to be able to call +super+
|
|
328
365
|
@instance_helper_module.class_eval do
|
|
329
366
|
define_method("#{name}?") do |*args|
|
|
330
|
-
args.empty? ? super(*args) : self.class.state_machine(
|
|
367
|
+
args.empty? ? super(*args) : self.class.state_machine(name).states.matches?(self, *args)
|
|
331
368
|
end
|
|
332
369
|
end
|
|
333
370
|
end
|
|
@@ -388,17 +425,18 @@ module StateMachine
|
|
|
388
425
|
# inheritance is respected properly.
|
|
389
426
|
def define_scope(name, scope)
|
|
390
427
|
name = name.to_sym
|
|
391
|
-
|
|
428
|
+
machine_name = self.name
|
|
392
429
|
|
|
393
|
-
#
|
|
430
|
+
# Create the scope and then override it with state translation
|
|
394
431
|
owner_class.named_scope(name)
|
|
395
432
|
owner_class.scopes[name] = lambda do |klass, *states|
|
|
396
|
-
machine_states = klass.state_machine(
|
|
433
|
+
machine_states = klass.state_machine(machine_name).states
|
|
397
434
|
values = states.flatten.map {|state| machine_states.fetch(state).value}
|
|
398
435
|
|
|
399
436
|
::ActiveRecord::NamedScope::Scope.new(klass, scope.call(values))
|
|
400
437
|
end
|
|
401
438
|
|
|
439
|
+
# Prevent the Machine class from wrapping the scope
|
|
402
440
|
false
|
|
403
441
|
end
|
|
404
442
|
|
|
@@ -409,10 +447,10 @@ module StateMachine
|
|
|
409
447
|
# * #{type}_#{qualified_event}_from_#{from}
|
|
410
448
|
# * #{type}_#{qualified_event}_to_#{to}
|
|
411
449
|
# * #{type}_#{qualified_event}
|
|
412
|
-
# * #{type}_transition_#{
|
|
413
|
-
# * #{type}_transition_#{
|
|
414
|
-
# * #{type}_transition_#{
|
|
415
|
-
# * #{type}_transition_#{
|
|
450
|
+
# * #{type}_transition_#{machine_name}_from_#{from}_to_#{to}
|
|
451
|
+
# * #{type}_transition_#{machine_name}_from_#{from}
|
|
452
|
+
# * #{type}_transition_#{machine_name}_to_#{to}
|
|
453
|
+
# * #{type}_transition_#{machine_name}
|
|
416
454
|
# * #{type}_transition
|
|
417
455
|
#
|
|
418
456
|
# This will always return true regardless of the results of the
|