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