state_machine 0.9.4 → 0.10.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 +20 -0
- data/LICENSE +1 -1
- data/README.rdoc +74 -4
- data/Rakefile +3 -3
- data/lib/state_machine.rb +51 -24
- data/lib/state_machine/{guard.rb → branch.rb} +34 -40
- data/lib/state_machine/callback.rb +13 -18
- data/lib/state_machine/error.rb +13 -0
- data/lib/state_machine/eval_helpers.rb +3 -0
- data/lib/state_machine/event.rb +67 -30
- data/lib/state_machine/event_collection.rb +20 -3
- data/lib/state_machine/extensions.rb +3 -3
- data/lib/state_machine/integrations.rb +7 -0
- data/lib/state_machine/integrations/active_model.rb +149 -59
- data/lib/state_machine/integrations/active_model/versions.rb +30 -0
- data/lib/state_machine/integrations/active_record.rb +74 -148
- data/lib/state_machine/integrations/active_record/locale.rb +0 -7
- data/lib/state_machine/integrations/active_record/versions.rb +149 -0
- data/lib/state_machine/integrations/base.rb +64 -0
- data/lib/state_machine/integrations/data_mapper.rb +50 -39
- data/lib/state_machine/integrations/data_mapper/observer.rb +47 -12
- data/lib/state_machine/integrations/data_mapper/versions.rb +62 -0
- data/lib/state_machine/integrations/mongo_mapper.rb +37 -64
- data/lib/state_machine/integrations/mongo_mapper/locale.rb +4 -0
- data/lib/state_machine/integrations/mongo_mapper/versions.rb +102 -0
- data/lib/state_machine/integrations/mongoid.rb +297 -0
- data/lib/state_machine/integrations/mongoid/locale.rb +4 -0
- data/lib/state_machine/integrations/mongoid/versions.rb +18 -0
- data/lib/state_machine/integrations/sequel.rb +99 -55
- data/lib/state_machine/integrations/sequel/versions.rb +40 -0
- data/lib/state_machine/machine.rb +273 -136
- data/lib/state_machine/machine_collection.rb +21 -13
- data/lib/state_machine/node_collection.rb +6 -1
- data/lib/state_machine/path.rb +120 -0
- data/lib/state_machine/path_collection.rb +90 -0
- data/lib/state_machine/state.rb +28 -9
- data/lib/state_machine/state_collection.rb +1 -1
- data/lib/state_machine/transition.rb +65 -6
- data/lib/state_machine/transition_collection.rb +1 -1
- data/test/files/en.yml +8 -0
- data/test/functional/state_machine_test.rb +15 -2
- data/test/unit/branch_test.rb +890 -0
- data/test/unit/callback_test.rb +9 -36
- data/test/unit/error_test.rb +43 -0
- data/test/unit/event_collection_test.rb +67 -33
- data/test/unit/event_test.rb +165 -38
- data/test/unit/integrations/active_model_test.rb +103 -3
- data/test/unit/integrations/active_record_test.rb +90 -43
- data/test/unit/integrations/base_test.rb +87 -0
- data/test/unit/integrations/data_mapper_test.rb +105 -44
- data/test/unit/integrations/mongo_mapper_test.rb +261 -64
- data/test/unit/integrations/mongoid_test.rb +1529 -0
- data/test/unit/integrations/sequel_test.rb +33 -49
- data/test/unit/integrations_test.rb +4 -0
- data/test/unit/invalid_event_test.rb +15 -2
- data/test/unit/invalid_parallel_transition_test.rb +18 -0
- data/test/unit/invalid_transition_test.rb +72 -2
- data/test/unit/machine_collection_test.rb +55 -61
- data/test/unit/machine_test.rb +388 -26
- data/test/unit/node_collection_test.rb +14 -4
- data/test/unit/path_collection_test.rb +266 -0
- data/test/unit/path_test.rb +485 -0
- data/test/unit/state_collection_test.rb +30 -0
- data/test/unit/state_test.rb +82 -35
- data/test/unit/transition_collection_test.rb +48 -44
- data/test/unit/transition_test.rb +198 -41
- metadata +111 -74
- data/test/unit/guard_test.rb +0 -909
data/CHANGELOG.rdoc
CHANGED
@@ -1,5 +1,25 @@
|
|
1
1
|
== master
|
2
2
|
|
3
|
+
== 0.10.0 / 2011-03-19
|
4
|
+
|
5
|
+
* Support callback terminators in MongoMapper 0.9.0+
|
6
|
+
* Fix pluralization integration on DataMapper 1.0.0 and 1.1.0
|
7
|
+
* Allow transition guards to be bypassed for event / transition / path helpers
|
8
|
+
* Allow state / condition requirements to be specified for all event / transition / path helpers
|
9
|
+
* Add the ability to skip automatically initializing state machines on #initialize
|
10
|
+
* Add #{name}_paths for walking the available paths in a state machine
|
11
|
+
* Add Mongoid 2.0.0+ support
|
12
|
+
* Use around hooks to improve compatibility with other libraries in ActiveModel / ActiveRecord / MongoMapper integrations
|
13
|
+
* Add support for MassAssignmentSecurity feature in ActiveModel integrations
|
14
|
+
* Add support for more observer hooks within MongoMapper integrations
|
15
|
+
* Add i18n support for MongoMapper validation errors
|
16
|
+
* Update support for MongoMapper integration based on rails3 branch
|
17
|
+
* Fix objects not getting marked as dirty in all integrations when #{name}_event is set
|
18
|
+
* Generate warnings when conflicting state / event names are detected
|
19
|
+
* Allow fallback to generic state predicates when individual predicates are already defined in the owner class
|
20
|
+
* Replace :include_failures after_transition option with new after_failure callback
|
21
|
+
* Provide access to transition context when raising InvalidEvent / InvalidTransition exceptions
|
22
|
+
|
3
23
|
== 0.9.4 / 2010-08-01
|
4
24
|
|
5
25
|
* Fix validation / save hooks in Sequel 3.14.0+
|
data/LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -7,7 +7,7 @@ Ruby class.
|
|
7
7
|
|
8
8
|
API
|
9
9
|
|
10
|
-
* http://rdoc.info/
|
10
|
+
* http://rdoc.info/github/pluginaweek/state_machine/master/frames
|
11
11
|
|
12
12
|
Bugs
|
13
13
|
|
@@ -37,14 +37,15 @@ Some brief, high-level features include:
|
|
37
37
|
* Defining state machines on any Ruby class
|
38
38
|
* Multiple state machines on a single class
|
39
39
|
* Namespaced state machines
|
40
|
-
* before/after/around transition hooks with explicit transition requirements
|
41
|
-
* Integration with ActiveModel, ActiveRecord, DataMapper, MongoMapper, and Sequel
|
40
|
+
* before/after/around/failure transition hooks with explicit transition requirements
|
41
|
+
* Integration with ActiveModel, ActiveRecord, DataMapper, Mongoid, MongoMapper, and Sequel
|
42
42
|
* State predicates
|
43
43
|
* State-driven instance / class behavior
|
44
44
|
* State values of any data type
|
45
45
|
* Dynamically-generated state values
|
46
46
|
* Event parallelization
|
47
47
|
* Attribute-based event transitions
|
48
|
+
* Path analysis
|
48
49
|
* Inheritance
|
49
50
|
* Internationalization
|
50
51
|
* GraphViz visualization creator
|
@@ -64,6 +65,7 @@ Below is an example of many of the features offered by this plugin, including:
|
|
64
65
|
* State-driven instance behavior
|
65
66
|
* Customized state values
|
66
67
|
* Parallel events
|
68
|
+
* Path analysis
|
67
69
|
|
68
70
|
Class definition:
|
69
71
|
|
@@ -79,6 +81,8 @@ Class definition:
|
|
79
81
|
vehicle.seatbelt_on = false
|
80
82
|
end
|
81
83
|
|
84
|
+
after_failure :on => :ignite, :do => :log_start_failure
|
85
|
+
|
82
86
|
around_transition do |vehicle, transition, block|
|
83
87
|
start = Time.now
|
84
88
|
block.call
|
@@ -169,6 +173,10 @@ Class definition:
|
|
169
173
|
def fix
|
170
174
|
# get the vehicle fixed by a mechanic
|
171
175
|
end
|
176
|
+
|
177
|
+
def log_start_failure
|
178
|
+
# log a failed attempt to start the vehicle
|
179
|
+
end
|
172
180
|
end
|
173
181
|
|
174
182
|
*Note* the comment made on the +initialize+ method in the class. In order for
|
@@ -235,7 +243,18 @@ like so:
|
|
235
243
|
Vehicle.human_alarm_state_name(:active) # => "active"
|
236
244
|
|
237
245
|
Vehicle.human_state_event_name(:shift_down) # => "shift down"
|
238
|
-
Vehicle.human_alarm_state_event_name(:
|
246
|
+
Vehicle.human_alarm_state_event_name(:enable) # => "enable"
|
247
|
+
|
248
|
+
# Available transition paths can be analyzed for an object
|
249
|
+
vehicle.state_paths # => [[#<StateMachine::Transition ...], [#<StateMachine::Transition ...], ...]
|
250
|
+
vehicle.state_paths.to_states # => [:parked, :idling, :first_gear, :stalled, :second_gear, :third_gear]
|
251
|
+
vehicle.state_paths.events # => [:park, :ignite, :shift_up, :idle, :crash, :repair, :shift_down]
|
252
|
+
|
253
|
+
# Find all paths that start and end on certain states
|
254
|
+
vehicle.state_paths(:from => :parked, :to => :first_gear) # => [[
|
255
|
+
# #<StateMachine::Transition attribute=:state event=:ignite from="parked" ...>,
|
256
|
+
# #<StateMachine::Transition attribute=:state event=:shift_up from="idling" ...>
|
257
|
+
# ]]
|
239
258
|
|
240
259
|
== Integrations
|
241
260
|
|
@@ -248,6 +267,7 @@ The integrations currently available include:
|
|
248
267
|
* ActiveModel classes
|
249
268
|
* ActiveRecord models
|
250
269
|
* DataMapper resources
|
270
|
+
* Mongoid models
|
251
271
|
* MongoMapper models
|
252
272
|
* Sequel models
|
253
273
|
|
@@ -305,6 +325,11 @@ observers. For example,
|
|
305
325
|
def after_transition(vehicle, transition)
|
306
326
|
Audit.log(vehicle, transition)
|
307
327
|
end
|
328
|
+
|
329
|
+
# Generic callback after the transition fails to perform
|
330
|
+
def after_failure_to_transition(vehicle, transition)
|
331
|
+
Audit.error(vehicle, transition)
|
332
|
+
end
|
308
333
|
end
|
309
334
|
|
310
335
|
For more information about the various behaviors added for ActiveModel state
|
@@ -418,6 +443,11 @@ callbacks, validation errors, and observers. For example,
|
|
418
443
|
block.call
|
419
444
|
# mark stop time
|
420
445
|
end
|
446
|
+
|
447
|
+
# Generic callback after the transition fails to perform
|
448
|
+
after_transition_failure do |transition|
|
449
|
+
Audit.log(self, transition) # self is the record
|
450
|
+
end
|
421
451
|
end
|
422
452
|
|
423
453
|
*Note* that the DataMapper::Observer integration is optional and only available
|
@@ -426,6 +456,44 @@ when the dm-observer library is installed.
|
|
426
456
|
For more information about the various behaviors added for DataMapper state
|
427
457
|
machines, see StateMachine::Integrations::DataMapper.
|
428
458
|
|
459
|
+
=== Mongoid
|
460
|
+
|
461
|
+
The Mongoid integration adds support for automatically saving the record,
|
462
|
+
basic scopes, validation errors and callbacks. For example,
|
463
|
+
|
464
|
+
class Vehicle
|
465
|
+
include Mongoid::Document
|
466
|
+
|
467
|
+
state_machine :initial => :parked do
|
468
|
+
before_transition :parked => any - :parked, :do => :put_on_seatbelt
|
469
|
+
after_transition any => :parked do |vehicle, transition|
|
470
|
+
vehicle.seatbelt = 'off' # self is the record
|
471
|
+
end
|
472
|
+
around_transition :benchmark
|
473
|
+
|
474
|
+
event :ignite do
|
475
|
+
transition :parked => :idling
|
476
|
+
end
|
477
|
+
|
478
|
+
state :first_gear, :second_gear do
|
479
|
+
validates_presence_of :seatbelt_on
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
def put_on_seatbelt
|
484
|
+
...
|
485
|
+
end
|
486
|
+
|
487
|
+
def benchmark
|
488
|
+
...
|
489
|
+
yield
|
490
|
+
...
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
For more information about the various behaviors added for Mongoid state
|
495
|
+
machines, see StateMachine::Integrations::Mongoid.
|
496
|
+
|
429
497
|
=== MongoMapper
|
430
498
|
|
431
499
|
The MongoMapper integration adds support for automatically saving the record,
|
@@ -607,6 +675,7 @@ Test specific versions of integrations like so:
|
|
607
675
|
rake test INTEGRATION=active_model VERSION=3.0.0
|
608
676
|
rake test INTEGRATION=active_record VERSION=2.0.0
|
609
677
|
rake test INTEGRATION=data_mapper VERSION=0.9.4
|
678
|
+
rake test INTEGRATION=mongoid VERSION=2.0.0
|
610
679
|
rake test INTEGRATION=mongo_mapper VERSION=0.5.5
|
611
680
|
rake test INTEGRATION=sequel VERSION=2.8.0
|
612
681
|
|
@@ -627,6 +696,7 @@ If using specific integrations:
|
|
627
696
|
* ActiveModel[http://rubyonrails.org] integration: 3.0.0 or later
|
628
697
|
* ActiveRecord[http://rubyonrails.org] integration: 2.0.0 or later
|
629
698
|
* DataMapper[http://datamapper.org] integration: 0.9.4 or later
|
699
|
+
* Mongoid[http://mongoid.org] integration: 2.0.0 or later
|
630
700
|
* MongoMapper[http://mongomapper.com] integration: 0.5.5 or later
|
631
701
|
* Sequel[http://sequel.rubyforge.org] integration: 2.8.0 or later
|
632
702
|
|
data/Rakefile
CHANGED
@@ -6,7 +6,7 @@ require 'rake/gempackagetask'
|
|
6
6
|
|
7
7
|
spec = Gem::Specification.new do |s|
|
8
8
|
s.name = 'state_machine'
|
9
|
-
s.version = '0.
|
9
|
+
s.version = '0.10.0'
|
10
10
|
s.platform = Gem::Platform::RUBY
|
11
11
|
s.summary = 'Adds support for creating state machines for attributes on any Ruby class'
|
12
12
|
s.description = s.summary
|
@@ -28,7 +28,7 @@ task :default => :test
|
|
28
28
|
desc "Test the #{spec.name} plugin."
|
29
29
|
Rake::TestTask.new(:test) do |t|
|
30
30
|
t.libs << 'lib'
|
31
|
-
t.test_files = ENV['INTEGRATION'] ? Dir["test/unit/integrations/#{ENV['INTEGRATION']}_test.rb"] : Dir['test/{functional,unit}/*_test.rb']
|
31
|
+
t.test_files = ENV['INTEGRATION'] ? Dir["test/unit/integrations/#{ENV['INTEGRATION']}_test.rb"] : Dir['test/{functional,unit}/*_test.rb'] + ['test/unit/integrations/base_test.rb']
|
32
32
|
t.verbose = true
|
33
33
|
end
|
34
34
|
|
@@ -51,7 +51,7 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
|
|
51
51
|
rdoc.rdoc_dir = 'rdoc'
|
52
52
|
rdoc.title = spec.name
|
53
53
|
rdoc.template = '../rdoc_template.rb'
|
54
|
-
rdoc.options << '--line-numbers' << '--inline-source'
|
54
|
+
rdoc.options << '--line-numbers' << '--inline-source' << '--main=README.rdoc'
|
55
55
|
rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG.rdoc', 'LICENSE', 'lib/**/*.rb')
|
56
56
|
end
|
57
57
|
|
data/lib/state_machine.rb
CHANGED
@@ -15,6 +15,8 @@ module StateMachine
|
|
15
15
|
# static state or a lambda block which will be evaluated at runtime
|
16
16
|
# (e.g. lambda {|vehicle| vehicle.speed == 0 ? :parked : :idling}).
|
17
17
|
# Default is nil.
|
18
|
+
# * <tt>:initialize</tt> - Whether to automatically initialize the attribute
|
19
|
+
# by hooking into #initialize on the owner class. Default is true.
|
18
20
|
# * <tt>:action</tt> - The instance method to invoke when an object
|
19
21
|
# transitions. Default is nil unless otherwise specified by the
|
20
22
|
# configured integration.
|
@@ -28,10 +30,9 @@ module StateMachine
|
|
28
30
|
# :sequel. By default, this is determined automatically.
|
29
31
|
#
|
30
32
|
# Configuration options relevant to ORM integrations:
|
31
|
-
# * <tt>:plural</tt> - The pluralized
|
32
|
-
#
|
33
|
-
#
|
34
|
-
# generating scopes.
|
33
|
+
# * <tt>:plural</tt> - The pluralized version of the name. By default, this
|
34
|
+
# will attempt to call +pluralize+ on the name. If this method is not
|
35
|
+
# available, an "s" is appended. This is used for generating scopes.
|
35
36
|
# * <tt>:messages</tt> - The error messages to use when invalidating
|
36
37
|
# objects due to failed transitions. Messages include:
|
37
38
|
# * <tt>:invalid</tt>
|
@@ -133,13 +134,25 @@ module StateMachine
|
|
133
134
|
# * <tt>state_name</tt> - Gets the name of the state for the current value
|
134
135
|
# * <tt>human_state_name</tt> - Gets the human-readable name of the state
|
135
136
|
# for the current value
|
136
|
-
# * <tt>state_events</tt> - Gets the list of events that
|
137
|
-
# the current object's state (uses the *unqualified* event
|
138
|
-
#
|
139
|
-
#
|
140
|
-
#
|
141
|
-
#
|
142
|
-
#
|
137
|
+
# * <tt>state_events(requirements = {})</tt> - Gets the list of events that
|
138
|
+
# can be fired on the current object's state (uses the *unqualified* event
|
139
|
+
# names)
|
140
|
+
# * <tt>state_transitions(requirements = {})</tt> - Gets the list of
|
141
|
+
# transitions that can be made on the current object's state
|
142
|
+
# * <tt>state_paths(requirements = {})</tt> - Gets the list of sequences of
|
143
|
+
# transitions that can be run from the current object's state
|
144
|
+
#
|
145
|
+
# The <tt>state_events</tt>, <tt>state_transitions</tt>, and <tt>state_paths</tt>
|
146
|
+
# helpers all take an optional set of requirements for determining what's
|
147
|
+
# available for the current object. These requirements include:
|
148
|
+
# * <tt>:from</tt> - One or more states to transition from. If none are
|
149
|
+
# specified, then this will be the object's current state.
|
150
|
+
# * <tt>:to</tt> - One or more states to transition to. If none are
|
151
|
+
# specified, then this will match any to state.
|
152
|
+
# * <tt>:on</tt> - One or more events to transition on. If none are
|
153
|
+
# specified, then this will match any event.
|
154
|
+
# * <tt>:guard</tt> - Whether to guard transitions with the if/unless
|
155
|
+
# conditionals defined for each one. Default is true.
|
143
156
|
#
|
144
157
|
# For example,
|
145
158
|
#
|
@@ -156,25 +169,39 @@ module StateMachine
|
|
156
169
|
# end
|
157
170
|
#
|
158
171
|
# vehicle = Vehicle.new
|
159
|
-
# vehicle.state
|
160
|
-
# vehicle.state_name
|
161
|
-
# vehicle.human_state_name
|
162
|
-
# vehicle.state?(:parked)
|
172
|
+
# vehicle.state # => "parked"
|
173
|
+
# vehicle.state_name # => :parked
|
174
|
+
# vehicle.human_state_name # => "parked"
|
175
|
+
# vehicle.state?(:parked) # => true
|
163
176
|
#
|
164
177
|
# # Changing state
|
165
178
|
# vehicle.state = 'idling'
|
166
|
-
# vehicle.state
|
167
|
-
# vehicle.state_name
|
168
|
-
# vehicle.state?(:parked)
|
179
|
+
# vehicle.state # => "idling"
|
180
|
+
# vehicle.state_name # => :idling
|
181
|
+
# vehicle.state?(:parked) # => false
|
169
182
|
#
|
170
183
|
# # Getting current event / transition availability
|
171
|
-
# vehicle.state_events
|
172
|
-
# vehicle.park
|
173
|
-
# vehicle.state_events
|
184
|
+
# vehicle.state_events # => [:park]
|
185
|
+
# vehicle.park # => true
|
186
|
+
# vehicle.state_events # => [:ignite]
|
187
|
+
# vehicle.state_events(:from => :idling) # => [:park]
|
188
|
+
# vehicle.state_events(:to => :parked) # => []
|
174
189
|
#
|
175
|
-
# vehicle.state_transitions
|
190
|
+
# vehicle.state_transitions # => [#<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
|
176
191
|
# vehicle.ignite
|
177
|
-
# vehicle.state_transitions
|
192
|
+
# vehicle.state_transitions # => [#<StateMachine::Transition attribute=:state event=:park from="idling" from_name=:idling to="parked" to_name=:parked>]
|
193
|
+
#
|
194
|
+
# vehicle.state_transitions(:on => :ignite) # => []
|
195
|
+
#
|
196
|
+
# # Getting current path availability
|
197
|
+
# vehicle.state_paths # => [
|
198
|
+
# # [#<StateMachine::Transition attribute=:state event=:park from="idling" from_name=:idling to="parked" to_name=:parked>,
|
199
|
+
# # #<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
|
200
|
+
# # ]
|
201
|
+
# vehicle.state_paths(:guard => false) # =>
|
202
|
+
# # [#<StateMachine::Transition attribute=:state event=:park from="idling" from_name=:idling to="parked" to_name=:parked>,
|
203
|
+
# # #<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
|
204
|
+
# # ]
|
178
205
|
#
|
179
206
|
# == Attribute initialization
|
180
207
|
#
|
@@ -330,7 +357,7 @@ module StateMachine
|
|
330
357
|
# Within the +state_machine+ block, you can also define callbacks for
|
331
358
|
# transitions. For more information about defining these callbacks,
|
332
359
|
# see StateMachine::Machine#before_transition, StateMachine::Machine#after_transition,
|
333
|
-
# and StateMachine::Machine#around_transition.
|
360
|
+
# and StateMachine::Machine#around_transition, and StateMachine::Machine#after_failure.
|
334
361
|
#
|
335
362
|
# == Namespaces
|
336
363
|
#
|
@@ -4,10 +4,10 @@ require 'state_machine/assertions'
|
|
4
4
|
|
5
5
|
module StateMachine
|
6
6
|
# Represents a set of requirements that must be met in order for a transition
|
7
|
-
# or callback to occur.
|
7
|
+
# or callback to occur. Branches verify that the event, from state, and to
|
8
8
|
# state of the transition match, in addition to if/unless conditionals for
|
9
9
|
# an object's state.
|
10
|
-
class
|
10
|
+
class Branch
|
11
11
|
include Assertions
|
12
12
|
include EvalHelpers
|
13
13
|
|
@@ -17,23 +17,20 @@ module StateMachine
|
|
17
17
|
# The condition that must *not* be met on an object
|
18
18
|
attr_reader :unless_condition
|
19
19
|
|
20
|
-
# The requirement for verifying the event being
|
20
|
+
# The requirement for verifying the event being matched
|
21
21
|
attr_reader :event_requirement
|
22
22
|
|
23
|
-
# One or more requirements for verifying the states being
|
23
|
+
# One or more requirements for verifying the states being matched. All
|
24
24
|
# requirements contain a mapping of {:from => matcher, :to => matcher}.
|
25
25
|
attr_reader :state_requirements
|
26
26
|
|
27
|
-
#
|
28
|
-
attr_reader :success_requirement
|
29
|
-
|
30
|
-
# A list of all of the states known to this guard. This will pull states
|
27
|
+
# A list of all of the states known to this branch. This will pull states
|
31
28
|
# from the following options (in the same order):
|
32
29
|
# * +from+ / +except_from+
|
33
30
|
# * +to+ / +except_to+
|
34
31
|
attr_reader :known_states
|
35
32
|
|
36
|
-
# Creates a new
|
33
|
+
# Creates a new branch
|
37
34
|
def initialize(options = {}) #:nodoc:
|
38
35
|
# Build conditionals
|
39
36
|
@if_condition = options.delete(:if)
|
@@ -42,9 +39,6 @@ module StateMachine
|
|
42
39
|
# Build event requirement
|
43
40
|
@event_requirement = build_matcher(options, :on, :except_on)
|
44
41
|
|
45
|
-
# Build success requirement
|
46
|
-
@success_requirement = options.delete(:include_failures) ? AllMatcher.instance : WhitelistMatcher.new([true])
|
47
|
-
|
48
42
|
if (options.keys - [:from, :to, :on, :except_from, :except_to, :except_on]).empty?
|
49
43
|
# Explicit from/to requirements specified
|
50
44
|
@state_requirements = [{:from => build_matcher(options, :from, :except_from), :to => build_matcher(options, :to, :except_to)}]
|
@@ -70,34 +64,34 @@ module StateMachine
|
|
70
64
|
end
|
71
65
|
|
72
66
|
# Determines whether the given object / query matches the requirements
|
73
|
-
# configured for this
|
67
|
+
# configured for this branch. In addition to matching the event, from state,
|
74
68
|
# and to state, this will also check whether the configured :if/:unless
|
75
69
|
# conditions pass on the given object.
|
76
70
|
#
|
77
71
|
# == Examples
|
78
72
|
#
|
79
|
-
#
|
73
|
+
# branch = StateMachine::Branch.new(:parked => :idling, :on => :ignite)
|
80
74
|
#
|
81
75
|
# # Successful
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
87
|
-
#
|
76
|
+
# branch.matches?(object, :on => :ignite) # => true
|
77
|
+
# branch.matches?(object, :from => nil) # => true
|
78
|
+
# branch.matches?(object, :from => :parked) # => true
|
79
|
+
# branch.matches?(object, :to => :idling) # => true
|
80
|
+
# branch.matches?(object, :from => :parked, :to => :idling) # => true
|
81
|
+
# branch.matches?(object, :on => :ignite, :from => :parked, :to => :idling) # => true
|
88
82
|
#
|
89
83
|
# # Unsuccessful
|
90
|
-
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
84
|
+
# branch.matches?(object, :on => :park) # => false
|
85
|
+
# branch.matches?(object, :from => :idling) # => false
|
86
|
+
# branch.matches?(object, :to => :first_gear) # => false
|
87
|
+
# branch.matches?(object, :from => :parked, :to => :first_gear) # => false
|
88
|
+
# branch.matches?(object, :on => :park, :from => :parked, :to => :idling) # => false
|
95
89
|
def matches?(object, query = {})
|
96
90
|
!match(object, query).nil?
|
97
91
|
end
|
98
92
|
|
99
93
|
# Attempts to match the given object / query against the set of requirements
|
100
|
-
# configured for this
|
94
|
+
# configured for this branch. In addition to matching the event, from state,
|
101
95
|
# and to state, this will also check whether the configured :if/:unless
|
102
96
|
# conditions pass on the given object.
|
103
97
|
#
|
@@ -112,21 +106,25 @@ module StateMachine
|
|
112
106
|
# specified, then this will always match.
|
113
107
|
# * <tt>:on</tt> - One or more events that fired the transition. If none
|
114
108
|
# are specified, then this will always match.
|
109
|
+
# * <tt>:guard</tt> - Whether to guard matches with the if/unless
|
110
|
+
# conditionals defined for this branch. Default is true.
|
115
111
|
#
|
116
112
|
# == Examples
|
117
113
|
#
|
118
|
-
#
|
114
|
+
# branch = StateMachine::Branch.new(:parked => :idling, :on => :ignite)
|
119
115
|
#
|
120
|
-
#
|
121
|
-
#
|
116
|
+
# branch.match(object, :on => :ignite) # => {:to => ..., :from => ..., :on => ...}
|
117
|
+
# branch.match(object, :on => :park) # => nil
|
122
118
|
def match(object, query = {})
|
123
|
-
|
119
|
+
assert_valid_keys(query, :from, :to, :on, :guard)
|
120
|
+
|
121
|
+
if (match = match_query(query)) && matches_conditions?(object, query)
|
124
122
|
match
|
125
123
|
end
|
126
124
|
end
|
127
125
|
|
128
|
-
# Draws a representation of this
|
129
|
-
# an edge between every state this
|
126
|
+
# Draws a representation of this branch on the given graph. This will draw
|
127
|
+
# an edge between every state this branch matches *from* to either the
|
130
128
|
# configured to state or, if none specified, then a loopback to the from
|
131
129
|
# state.
|
132
130
|
#
|
@@ -191,16 +189,11 @@ module StateMachine
|
|
191
189
|
def match_query(query)
|
192
190
|
query ||= {}
|
193
191
|
|
194
|
-
if
|
192
|
+
if match_event(query) && (state_requirement = match_states(query))
|
195
193
|
state_requirement.merge(:on => event_requirement)
|
196
194
|
end
|
197
195
|
end
|
198
196
|
|
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
|
-
|
204
197
|
# Verifies that the event requirement matches the given query
|
205
198
|
def match_event(query)
|
206
199
|
matches_requirement?(query, :on, event_requirement)
|
@@ -220,9 +213,10 @@ module StateMachine
|
|
220
213
|
!query.include?(option) || requirement.matches?(query[option], query)
|
221
214
|
end
|
222
215
|
|
223
|
-
# Verifies that the conditionals for this
|
216
|
+
# Verifies that the conditionals for this branch evaluate to true for the
|
224
217
|
# given object
|
225
|
-
def matches_conditions?(object)
|
218
|
+
def matches_conditions?(object, query)
|
219
|
+
query[:guard] == false ||
|
226
220
|
Array(if_condition).all? {|condition| evaluate_method(object, condition)} &&
|
227
221
|
!Array(unless_condition).any? {|condition| evaluate_method(object, condition)}
|
228
222
|
end
|