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.
Files changed (68) hide show
  1. data/CHANGELOG.rdoc +20 -0
  2. data/LICENSE +1 -1
  3. data/README.rdoc +74 -4
  4. data/Rakefile +3 -3
  5. data/lib/state_machine.rb +51 -24
  6. data/lib/state_machine/{guard.rb → branch.rb} +34 -40
  7. data/lib/state_machine/callback.rb +13 -18
  8. data/lib/state_machine/error.rb +13 -0
  9. data/lib/state_machine/eval_helpers.rb +3 -0
  10. data/lib/state_machine/event.rb +67 -30
  11. data/lib/state_machine/event_collection.rb +20 -3
  12. data/lib/state_machine/extensions.rb +3 -3
  13. data/lib/state_machine/integrations.rb +7 -0
  14. data/lib/state_machine/integrations/active_model.rb +149 -59
  15. data/lib/state_machine/integrations/active_model/versions.rb +30 -0
  16. data/lib/state_machine/integrations/active_record.rb +74 -148
  17. data/lib/state_machine/integrations/active_record/locale.rb +0 -7
  18. data/lib/state_machine/integrations/active_record/versions.rb +149 -0
  19. data/lib/state_machine/integrations/base.rb +64 -0
  20. data/lib/state_machine/integrations/data_mapper.rb +50 -39
  21. data/lib/state_machine/integrations/data_mapper/observer.rb +47 -12
  22. data/lib/state_machine/integrations/data_mapper/versions.rb +62 -0
  23. data/lib/state_machine/integrations/mongo_mapper.rb +37 -64
  24. data/lib/state_machine/integrations/mongo_mapper/locale.rb +4 -0
  25. data/lib/state_machine/integrations/mongo_mapper/versions.rb +102 -0
  26. data/lib/state_machine/integrations/mongoid.rb +297 -0
  27. data/lib/state_machine/integrations/mongoid/locale.rb +4 -0
  28. data/lib/state_machine/integrations/mongoid/versions.rb +18 -0
  29. data/lib/state_machine/integrations/sequel.rb +99 -55
  30. data/lib/state_machine/integrations/sequel/versions.rb +40 -0
  31. data/lib/state_machine/machine.rb +273 -136
  32. data/lib/state_machine/machine_collection.rb +21 -13
  33. data/lib/state_machine/node_collection.rb +6 -1
  34. data/lib/state_machine/path.rb +120 -0
  35. data/lib/state_machine/path_collection.rb +90 -0
  36. data/lib/state_machine/state.rb +28 -9
  37. data/lib/state_machine/state_collection.rb +1 -1
  38. data/lib/state_machine/transition.rb +65 -6
  39. data/lib/state_machine/transition_collection.rb +1 -1
  40. data/test/files/en.yml +8 -0
  41. data/test/functional/state_machine_test.rb +15 -2
  42. data/test/unit/branch_test.rb +890 -0
  43. data/test/unit/callback_test.rb +9 -36
  44. data/test/unit/error_test.rb +43 -0
  45. data/test/unit/event_collection_test.rb +67 -33
  46. data/test/unit/event_test.rb +165 -38
  47. data/test/unit/integrations/active_model_test.rb +103 -3
  48. data/test/unit/integrations/active_record_test.rb +90 -43
  49. data/test/unit/integrations/base_test.rb +87 -0
  50. data/test/unit/integrations/data_mapper_test.rb +105 -44
  51. data/test/unit/integrations/mongo_mapper_test.rb +261 -64
  52. data/test/unit/integrations/mongoid_test.rb +1529 -0
  53. data/test/unit/integrations/sequel_test.rb +33 -49
  54. data/test/unit/integrations_test.rb +4 -0
  55. data/test/unit/invalid_event_test.rb +15 -2
  56. data/test/unit/invalid_parallel_transition_test.rb +18 -0
  57. data/test/unit/invalid_transition_test.rb +72 -2
  58. data/test/unit/machine_collection_test.rb +55 -61
  59. data/test/unit/machine_test.rb +388 -26
  60. data/test/unit/node_collection_test.rb +14 -4
  61. data/test/unit/path_collection_test.rb +266 -0
  62. data/test/unit/path_test.rb +485 -0
  63. data/test/unit/state_collection_test.rb +30 -0
  64. data/test/unit/state_test.rb +82 -35
  65. data/test/unit/transition_collection_test.rb +48 -44
  66. data/test/unit/transition_test.rb +198 -41
  67. metadata +111 -74
  68. 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
@@ -1,4 +1,4 @@
1
- Copyright (c) 2006-2010 Aaron Pfefier
1
+ Copyright (c) 2006-2011 Aaron Pfefier
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -7,7 +7,7 @@ Ruby class.
7
7
 
8
8
  API
9
9
 
10
- * http://rdoc.info/projects/pluginaweek/state_machine
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(:enable_alarm) # => "enable alarm"
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.4'
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 name of the attribute. By default,
32
- # this will attempt to call +pluralize+ on the attribute. If this
33
- # method is not available, an "s" is appended. This is used for
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 can be fired on
137
- # the current object's state (uses the *unqualified* event names)
138
- # * <tt>state_transitions(requirements = {})</tt> - Gets the list of possible
139
- # transitions that can be made on the current object's state. Additional
140
- # requirements, such as the :from / :to state and :on event can be specified
141
- # to restrict the transitions to select. By default, the current state
142
- # will be used for the :from state.
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 # => "parked"
160
- # vehicle.state_name # => :parked
161
- # vehicle.human_state_name # => "parked"
162
- # vehicle.state?(:parked) # => true
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 # => "idling"
167
- # vehicle.state_name # => :idling
168
- # vehicle.state?(:parked) # => false
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 # => [:park]
172
- # vehicle.park # => true
173
- # vehicle.state_events # => [:ignite]
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 # => [#<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
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 # => [#<StateMachine::Transition attribute=:state event=:park from="idling" from_name=:idling to="parked" to_name=:parked>]
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. Guards verify that the event, from state, and to
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 Guard
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 guarded
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 guarded. All
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
- # The requirement for verifying the success of the event
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 guard
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 guard. In addition to matching the event, from state,
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
- # guard = StateMachine::Guard.new(:parked => :idling, :on => :ignite)
73
+ # branch = StateMachine::Branch.new(:parked => :idling, :on => :ignite)
80
74
  #
81
75
  # # Successful
82
- # guard.matches?(object, :on => :ignite) # => true
83
- # guard.matches?(object, :from => nil) # => true
84
- # guard.matches?(object, :from => :parked) # => true
85
- # guard.matches?(object, :to => :idling) # => true
86
- # guard.matches?(object, :from => :parked, :to => :idling) # => true
87
- # guard.matches?(object, :on => :ignite, :from => :parked, :to => :idling) # => true
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
- # guard.matches?(object, :on => :park) # => false
91
- # guard.matches?(object, :from => :idling) # => false
92
- # guard.matches?(object, :to => :first_gear) # => false
93
- # guard.matches?(object, :from => :parked, :to => :first_gear) # => false
94
- # guard.matches?(object, :on => :park, :from => :parked, :to => :idling) # => false
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 guard. In addition to matching the event, from state,
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
- # guard = StateMachine::Guard.new(:parked => :idling, :on => :ignite)
114
+ # branch = StateMachine::Branch.new(:parked => :idling, :on => :ignite)
119
115
  #
120
- # guard.match(object, :on => :ignite) # => {:to => ..., :from => ..., :on => ...}
121
- # guard.match(object, :on => :park) # => nil
116
+ # branch.match(object, :on => :ignite) # => {:to => ..., :from => ..., :on => ...}
117
+ # branch.match(object, :on => :park) # => nil
122
118
  def match(object, query = {})
123
- if (match = match_query(query)) && matches_conditions?(object)
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 guard on the given graph. This will draw
129
- # an edge between every state this guard matches *from* to either the
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 match_success(query) && match_event(query) && (state_requirement = match_states(query))
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 guard evaluate to true for the
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