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