hsume2-state_machine 1.0.1

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 (110) hide show
  1. data/CHANGELOG.rdoc +413 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +717 -0
  4. data/Rakefile +77 -0
  5. data/examples/AutoShop_state.png +0 -0
  6. data/examples/Car_state.png +0 -0
  7. data/examples/TrafficLight_state.png +0 -0
  8. data/examples/Vehicle_state.png +0 -0
  9. data/examples/auto_shop.rb +11 -0
  10. data/examples/car.rb +19 -0
  11. data/examples/merb-rest/controller.rb +51 -0
  12. data/examples/merb-rest/model.rb +28 -0
  13. data/examples/merb-rest/view_edit.html.erb +24 -0
  14. data/examples/merb-rest/view_index.html.erb +23 -0
  15. data/examples/merb-rest/view_new.html.erb +13 -0
  16. data/examples/merb-rest/view_show.html.erb +17 -0
  17. data/examples/rails-rest/controller.rb +43 -0
  18. data/examples/rails-rest/migration.rb +11 -0
  19. data/examples/rails-rest/model.rb +23 -0
  20. data/examples/rails-rest/view_edit.html.erb +25 -0
  21. data/examples/rails-rest/view_index.html.erb +23 -0
  22. data/examples/rails-rest/view_new.html.erb +14 -0
  23. data/examples/rails-rest/view_show.html.erb +17 -0
  24. data/examples/traffic_light.rb +7 -0
  25. data/examples/vehicle.rb +31 -0
  26. data/init.rb +1 -0
  27. data/lib/state_machine.rb +448 -0
  28. data/lib/state_machine/alternate_machine.rb +79 -0
  29. data/lib/state_machine/assertions.rb +36 -0
  30. data/lib/state_machine/branch.rb +224 -0
  31. data/lib/state_machine/callback.rb +236 -0
  32. data/lib/state_machine/condition_proxy.rb +94 -0
  33. data/lib/state_machine/error.rb +13 -0
  34. data/lib/state_machine/eval_helpers.rb +86 -0
  35. data/lib/state_machine/event.rb +304 -0
  36. data/lib/state_machine/event_collection.rb +139 -0
  37. data/lib/state_machine/extensions.rb +149 -0
  38. data/lib/state_machine/initializers.rb +4 -0
  39. data/lib/state_machine/initializers/merb.rb +1 -0
  40. data/lib/state_machine/initializers/rails.rb +25 -0
  41. data/lib/state_machine/integrations.rb +110 -0
  42. data/lib/state_machine/integrations/active_model.rb +502 -0
  43. data/lib/state_machine/integrations/active_model/locale.rb +11 -0
  44. data/lib/state_machine/integrations/active_model/observer.rb +45 -0
  45. data/lib/state_machine/integrations/active_model/versions.rb +31 -0
  46. data/lib/state_machine/integrations/active_record.rb +424 -0
  47. data/lib/state_machine/integrations/active_record/locale.rb +20 -0
  48. data/lib/state_machine/integrations/active_record/versions.rb +143 -0
  49. data/lib/state_machine/integrations/base.rb +91 -0
  50. data/lib/state_machine/integrations/data_mapper.rb +392 -0
  51. data/lib/state_machine/integrations/data_mapper/observer.rb +210 -0
  52. data/lib/state_machine/integrations/data_mapper/versions.rb +62 -0
  53. data/lib/state_machine/integrations/mongo_mapper.rb +272 -0
  54. data/lib/state_machine/integrations/mongo_mapper/locale.rb +4 -0
  55. data/lib/state_machine/integrations/mongo_mapper/versions.rb +110 -0
  56. data/lib/state_machine/integrations/mongoid.rb +357 -0
  57. data/lib/state_machine/integrations/mongoid/locale.rb +4 -0
  58. data/lib/state_machine/integrations/mongoid/versions.rb +18 -0
  59. data/lib/state_machine/integrations/sequel.rb +428 -0
  60. data/lib/state_machine/integrations/sequel/versions.rb +36 -0
  61. data/lib/state_machine/machine.rb +1873 -0
  62. data/lib/state_machine/machine_collection.rb +87 -0
  63. data/lib/state_machine/matcher.rb +123 -0
  64. data/lib/state_machine/matcher_helpers.rb +54 -0
  65. data/lib/state_machine/node_collection.rb +157 -0
  66. data/lib/state_machine/path.rb +120 -0
  67. data/lib/state_machine/path_collection.rb +90 -0
  68. data/lib/state_machine/state.rb +271 -0
  69. data/lib/state_machine/state_collection.rb +112 -0
  70. data/lib/state_machine/transition.rb +458 -0
  71. data/lib/state_machine/transition_collection.rb +244 -0
  72. data/lib/tasks/state_machine.rake +1 -0
  73. data/lib/tasks/state_machine.rb +27 -0
  74. data/test/files/en.yml +17 -0
  75. data/test/files/switch.rb +11 -0
  76. data/test/functional/alternate_state_machine_test.rb +122 -0
  77. data/test/functional/state_machine_test.rb +993 -0
  78. data/test/test_helper.rb +4 -0
  79. data/test/unit/assertions_test.rb +40 -0
  80. data/test/unit/branch_test.rb +890 -0
  81. data/test/unit/callback_test.rb +701 -0
  82. data/test/unit/condition_proxy_test.rb +328 -0
  83. data/test/unit/error_test.rb +43 -0
  84. data/test/unit/eval_helpers_test.rb +222 -0
  85. data/test/unit/event_collection_test.rb +358 -0
  86. data/test/unit/event_test.rb +985 -0
  87. data/test/unit/integrations/active_model_test.rb +1097 -0
  88. data/test/unit/integrations/active_record_test.rb +2021 -0
  89. data/test/unit/integrations/base_test.rb +99 -0
  90. data/test/unit/integrations/data_mapper_test.rb +1909 -0
  91. data/test/unit/integrations/mongo_mapper_test.rb +1611 -0
  92. data/test/unit/integrations/mongoid_test.rb +1591 -0
  93. data/test/unit/integrations/sequel_test.rb +1523 -0
  94. data/test/unit/integrations_test.rb +61 -0
  95. data/test/unit/invalid_event_test.rb +20 -0
  96. data/test/unit/invalid_parallel_transition_test.rb +18 -0
  97. data/test/unit/invalid_transition_test.rb +77 -0
  98. data/test/unit/machine_collection_test.rb +599 -0
  99. data/test/unit/machine_test.rb +3043 -0
  100. data/test/unit/matcher_helpers_test.rb +37 -0
  101. data/test/unit/matcher_test.rb +155 -0
  102. data/test/unit/node_collection_test.rb +217 -0
  103. data/test/unit/path_collection_test.rb +266 -0
  104. data/test/unit/path_test.rb +485 -0
  105. data/test/unit/state_collection_test.rb +310 -0
  106. data/test/unit/state_machine_test.rb +31 -0
  107. data/test/unit/state_test.rb +924 -0
  108. data/test/unit/transition_collection_test.rb +2102 -0
  109. data/test/unit/transition_test.rb +1541 -0
  110. metadata +207 -0
@@ -0,0 +1,94 @@
1
+ require 'state_machine/eval_helpers'
2
+
3
+ module StateMachine
4
+ # Represents a type of module in which class-level methods are proxied to
5
+ # another class, injecting a custom <tt>:if</tt> condition along with method.
6
+ #
7
+ # This is used for being able to automatically include conditionals which
8
+ # check the current state in class-level methods that have configuration
9
+ # options.
10
+ #
11
+ # == Examples
12
+ #
13
+ # class Vehicle
14
+ # class << self
15
+ # attr_accessor :validations
16
+ #
17
+ # def validate(options, &block)
18
+ # validations << options
19
+ # end
20
+ # end
21
+ #
22
+ # self.validations = []
23
+ # attr_accessor :state, :simulate
24
+ #
25
+ # def moving?
26
+ # self.class.validations.all? {|validation| validation[:if].call(self)}
27
+ # end
28
+ # end
29
+ #
30
+ # In the above class, a simple set of validation behaviors have been defined.
31
+ # Each validation consists of a configuration like so:
32
+ #
33
+ # Vehicle.validate :unless => :simulate
34
+ # Vehicle.validate :if => lambda {|vehicle| ...}
35
+ #
36
+ # In order to scope conditions, a condition proxy can be created to the
37
+ # Vehicle class. For example,
38
+ #
39
+ # proxy = StateMachine::ConditionProxy.new(Vehicle, lambda {|vehicle| vehicle.state == 'first_gear'})
40
+ # proxy.validate(:unless => :simulate)
41
+ #
42
+ # vehicle = Vehicle.new # => #<Vehicle:0xb7ce491c @simulate=nil, @state=nil>
43
+ # vehicle.moving? # => false
44
+ #
45
+ # vehicle.state = 'first_gear'
46
+ # vehicle.moving? # => true
47
+ #
48
+ # vehicle.simulate = true
49
+ # vehicle.moving? # => false
50
+ class ConditionProxy < Module
51
+ include EvalHelpers
52
+
53
+ # Creates a new proxy to the given class, merging in the given condition
54
+ def initialize(klass, condition)
55
+ @klass = klass
56
+ @condition = condition
57
+ end
58
+
59
+ # Hooks in condition-merging to methods that don't exist in this module
60
+ def method_missing(*args, &block)
61
+ # Get the configuration
62
+ if args.last.is_a?(Hash)
63
+ options = args.last
64
+ else
65
+ args << options = {}
66
+ end
67
+
68
+ # Get any existing condition that may need to be merged
69
+ if_condition = options.delete(:if)
70
+ unless_condition = options.delete(:unless)
71
+
72
+ # Provide scope access to configuration in case the block is evaluated
73
+ # within the object instance
74
+ proxy = self
75
+ proxy_condition = @condition
76
+
77
+ # Replace the configuration condition with the one configured for this
78
+ # proxy, merging together any existing conditions
79
+ options[:if] = lambda do |*args|
80
+ # Block may be executed within the context of the actual object, so
81
+ # it'll either be the first argument or the executing context
82
+ object = args.first || self
83
+
84
+ proxy.evaluate_method(object, proxy_condition) &&
85
+ Array(if_condition).all? {|condition| proxy.evaluate_method(object, condition)} &&
86
+ !Array(unless_condition).any? {|condition| proxy.evaluate_method(object, condition)}
87
+ end
88
+
89
+ # Evaluate the method on the original class with the condition proxied
90
+ # through
91
+ @klass.send(*args, &block)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,13 @@
1
+ module StateMachine
2
+ # An error occurred during a state machine invocation
3
+ class Error < StandardError
4
+ # The object that failed
5
+ attr_reader :object
6
+
7
+ def initialize(object, message = nil) #:nodoc:
8
+ @object = object
9
+
10
+ super(message)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,86 @@
1
+ module StateMachine
2
+ # Provides a set of helper methods for evaluating methods within the context
3
+ # of an object.
4
+ module EvalHelpers
5
+ # Evaluates one of several different types of methods within the context
6
+ # of the given object. Methods can be one of the following types:
7
+ # * Symbol
8
+ # * Method / Proc
9
+ # * String
10
+ #
11
+ # == Examples
12
+ #
13
+ # Below are examples of the various ways that a method can be evaluated
14
+ # on an object:
15
+ #
16
+ # class Person
17
+ # def initialize(name)
18
+ # @name = name
19
+ # end
20
+ #
21
+ # def name
22
+ # @name
23
+ # end
24
+ # end
25
+ #
26
+ # class PersonCallback
27
+ # def self.run(person)
28
+ # person.name
29
+ # end
30
+ # end
31
+ #
32
+ # person = Person.new('John Smith')
33
+ #
34
+ # evaluate_method(person, :name) # => "John Smith"
35
+ # evaluate_method(person, PersonCallback.method(:run)) # => "John Smith"
36
+ # evaluate_method(person, Proc.new {|person| person.name}) # => "John Smith"
37
+ # evaluate_method(person, lambda {|person| person.name}) # => "John Smith"
38
+ # evaluate_method(person, '@name') # => "John Smith"
39
+ #
40
+ # == Additional arguments
41
+ #
42
+ # Additional arguments can be passed to the methods being evaluated. If
43
+ # the method defines additional arguments other than the object context,
44
+ # then all arguments are required.
45
+ #
46
+ # For example,
47
+ #
48
+ # person = Person.new('John Smith')
49
+ #
50
+ # evaluate_method(person, lambda {|person| person.name}, 21) # => "John Smith"
51
+ # evaluate_method(person, lambda {|person, age| "#{person.name} is #{age}"}, 21) # => "John Smith is 21"
52
+ # evaluate_method(person, lambda {|person, age| "#{person.name} is #{age}"}, 21, 'male') # => ArgumentError: wrong number of arguments (3 for 2)
53
+ def evaluate_method(object, method, *args, &block)
54
+ case method
55
+ when Symbol
56
+ object.method(method).arity == 0 ? object.send(method, &block) : object.send(method, *args, &block)
57
+ when Proc, Method
58
+ args.unshift(object)
59
+ arity = method.arity
60
+ limit = [0, 1].include?(arity) ? arity : args.length
61
+
62
+ # Procs don't support blocks in < Ruby 1.8.6, so it's tacked on as an
63
+ # argument for consistency across versions of Ruby (even though 1.9
64
+ # supports yielding within blocks)
65
+ if block_given? && Proc === method && arity != 0
66
+ if [1, 2].include?(arity)
67
+ # Force the block to be either the only argument or the 2nd one
68
+ # after the object (may mean additional arguments get discarded)
69
+ limit = arity
70
+ args.insert(limit - 1, block)
71
+ else
72
+ # Tack the block to the end of the args
73
+ limit += 1 unless limit < 0
74
+ args.push(block)
75
+ end
76
+ end
77
+
78
+ method.call(*args[0, limit], &block)
79
+ when String
80
+ eval(method, object.instance_eval {binding}, &block)
81
+ else
82
+ raise ArgumentError, 'Methods must be a symbol denoting the method to call, a block to be invoked, or a string to be evaluated'
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,304 @@
1
+ require 'state_machine/transition'
2
+ require 'state_machine/branch'
3
+ require 'state_machine/assertions'
4
+ require 'state_machine/matcher_helpers'
5
+ require 'state_machine/error'
6
+
7
+ module StateMachine
8
+ # An invalid event was specified
9
+ class InvalidEvent < Error
10
+ # The event that was attempted to be run
11
+ attr_reader :event
12
+
13
+ def initialize(object, event_name) #:nodoc:
14
+ @event = event_name
15
+
16
+ super(object, "#{event.inspect} is an unknown state machine event")
17
+ end
18
+ end
19
+
20
+ # An event defines an action that transitions an attribute from one state to
21
+ # another. The state that an attribute is transitioned to depends on the
22
+ # branches configured for the event.
23
+ class Event
24
+ include Assertions
25
+ include MatcherHelpers
26
+
27
+ # The state machine for which this event is defined
28
+ attr_accessor :machine
29
+
30
+ # The name of the event
31
+ attr_reader :name
32
+
33
+ # The fully-qualified name of the event, scoped by the machine's namespace
34
+ attr_reader :qualified_name
35
+
36
+ # The human-readable name for the event
37
+ attr_writer :human_name
38
+
39
+ # The list of branches that determine what state this event transitions
40
+ # objects to when fired
41
+ attr_reader :branches
42
+
43
+ # A list of all of the states known to this event using the configured
44
+ # branches/transitions as the source
45
+ attr_reader :known_states
46
+
47
+ # Creates a new event within the context of the given machine
48
+ #
49
+ # Configuration options:
50
+ # * <tt>:human_name</tt> - The human-readable version of this event's name
51
+ def initialize(machine, name, options = {}) #:nodoc:
52
+ assert_valid_keys(options, :human_name)
53
+
54
+ @machine = machine
55
+ @name = name
56
+ @qualified_name = machine.namespace ? :"#{name}_#{machine.namespace}" : name
57
+ @human_name = options[:human_name] || @name.to_s.tr('_', ' ')
58
+ @branches = []
59
+ @known_states = []
60
+
61
+ # Output a warning if another event has a conflicting qualified name
62
+ if conflict = machine.owner_class.state_machines.detect {|name, other_machine| other_machine != @machine && other_machine.events[qualified_name, :qualified_name]}
63
+ name, other_machine = conflict
64
+ warn "Event #{qualified_name.inspect} for #{machine.name.inspect} is already defined in #{other_machine.name.inspect}"
65
+ else
66
+ add_actions
67
+ end
68
+ end
69
+
70
+ # Creates a copy of this event in addition to the list of associated
71
+ # branches to prevent conflicts across events within a class hierarchy.
72
+ def initialize_copy(orig) #:nodoc:
73
+ super
74
+ @branches = @branches.dup
75
+ @known_states = @known_states.dup
76
+ end
77
+
78
+ # Transforms the event name into a more human-readable format, such as
79
+ # "turn on" instead of "turn_on"
80
+ def human_name(klass = @machine.owner_class)
81
+ @human_name.is_a?(Proc) ? @human_name.call(self, klass) : @human_name
82
+ end
83
+
84
+ # Creates a new transition that determines what to change the current state
85
+ # to when this event fires.
86
+ #
87
+ # == Defining transitions
88
+ #
89
+ # The options for a new transition uses the Hash syntax to map beginning
90
+ # states to ending states. For example,
91
+ #
92
+ # transition :parked => :idling, :idling => :first_gear
93
+ #
94
+ # In this case, when the event is fired, this transition will cause the
95
+ # state to be +idling+ if it's current state is +parked+ or +first_gear+
96
+ # if it's current state is +idling+.
97
+ #
98
+ # To help define these implicit transitions, a set of helpers are available
99
+ # for slightly more complex matching:
100
+ # * <tt>all</tt> - Matches every state in the machine
101
+ # * <tt>all - [:parked, :idling, ...]</tt> - Matches every state except those specified
102
+ # * <tt>any</tt> - An alias for +all+ (matches every state in the machine)
103
+ # * <tt>same</tt> - Matches the same state being transitioned from
104
+ #
105
+ # See StateMachine::MatcherHelpers for more information.
106
+ #
107
+ # Examples:
108
+ #
109
+ # transition all => nil # Transitions to nil regardless of the current state
110
+ # transition all => :idling # Transitions to :idling regardless of the current state
111
+ # transition all - [:idling, :first_gear] => :idling # Transitions every state but :idling and :first_gear to :idling
112
+ # transition nil => :idling # Transitions to :idling from the nil state
113
+ # transition :parked => :idling # Transitions to :idling if :parked
114
+ # transition [:parked, :stalled] => :idling # Transitions to :idling if :parked or :stalled
115
+ #
116
+ # transition :parked => same # Loops :parked back to :parked
117
+ # transition [:parked, :stalled] => same # Loops either :parked or :stalled back to the same state
118
+ # transition all - :parked => same # Loops every state but :parked back to the same state
119
+ #
120
+ # # Transitions to :idling if :parked, :first_gear if :idling, or :second_gear if :first_gear
121
+ # transition :parked => :idling, :idling => :first_gear, :first_gear => :second_gear
122
+ #
123
+ # == Verbose transitions
124
+ #
125
+ # Transitions can also be defined use an explicit set of deprecated
126
+ # configuration options:
127
+ # * <tt>:from</tt> - A state or array of states that can be transitioned from.
128
+ # If not specified, then the transition can occur for *any* state.
129
+ # * <tt>:to</tt> - The state that's being transitioned to. If not specified,
130
+ # then the transition will simply loop back (i.e. the state will not change).
131
+ # * <tt>:except_from</tt> - A state or array of states that *cannot* be
132
+ # transitioned from.
133
+ #
134
+ # Examples:
135
+ #
136
+ # transition :to => nil
137
+ # transition :to => :idling
138
+ # transition :except_from => [:idling, :first_gear], :to => :idling
139
+ # transition :from => nil, :to => :idling
140
+ # transition :from => [:parked, :stalled], :to => :idling
141
+ #
142
+ # transition :from => :parked
143
+ # transition :from => [:parked, :stalled]
144
+ # transition :except_from => :parked
145
+ #
146
+ # Notice that the above examples are the verbose equivalent of the examples
147
+ # described initially.
148
+ #
149
+ # == Conditions
150
+ #
151
+ # In addition to the state requirements for each transition, a condition
152
+ # can also be defined to help determine whether that transition is
153
+ # available. These options will work on both the normal and verbose syntax.
154
+ #
155
+ # Configuration options:
156
+ # * <tt>:if</tt> - A method, proc or string to call to determine if the
157
+ # transition should occur (e.g. :if => :moving?, or :if => lambda {|vehicle| vehicle.speed > 60}).
158
+ # The condition should return or evaluate to true or false.
159
+ # * <tt>:unless</tt> - A method, proc or string to call to determine if the
160
+ # transition should not occur (e.g. :unless => :stopped?, or :unless => lambda {|vehicle| vehicle.speed <= 60}).
161
+ # The condition should return or evaluate to true or false.
162
+ #
163
+ # Examples:
164
+ #
165
+ # transition :parked => :idling, :if => :moving?
166
+ # transition :parked => :idling, :unless => :stopped?
167
+ # transition :idling => :first_gear, :first_gear => :second_gear, :if => :seatbelt_on?
168
+ #
169
+ # transition :from => :parked, :to => :idling, :if => :moving?
170
+ # transition :from => :parked, :to => :idling, :unless => :stopped?
171
+ #
172
+ # == Order of operations
173
+ #
174
+ # Transitions are evaluated in the order in which they're defined. As a
175
+ # result, if more than one transition applies to a given object, then the
176
+ # first transition that matches will be performed.
177
+ def transition(options)
178
+ raise ArgumentError, 'Must specify as least one transition requirement' if options.empty?
179
+
180
+ # Only a certain subset of explicit options are allowed for transition
181
+ # requirements
182
+ assert_valid_keys(options, :from, :to, :except_from, :if, :unless) if (options.keys - [:from, :to, :on, :except_from, :except_to, :except_on, :if, :unless]).empty?
183
+
184
+ branches << branch = Branch.new(options.merge(:on => name))
185
+ @known_states |= branch.known_states
186
+ branch
187
+ end
188
+
189
+ # Determines whether any transitions can be performed for this event based
190
+ # on the current state of the given object.
191
+ #
192
+ # If the event can't be fired, then this will return false, otherwise true.
193
+ def can_fire?(object, requirements = {})
194
+ !transition_for(object, requirements).nil?
195
+ end
196
+
197
+ # Finds and builds the next transition that can be performed on the given
198
+ # object. If no transitions can be made, then this will return nil.
199
+ #
200
+ # Valid requirement options:
201
+ # * <tt>:from</tt> - One or more states being transitioned from. If none
202
+ # are specified, then this will be the object's current state.
203
+ # * <tt>:to</tt> - One or more states being transitioned to. If none are
204
+ # specified, then this will match any to state.
205
+ # * <tt>:guard</tt> - Whether to guard transitions with the if/unless
206
+ # conditionals defined for each one. Default is true.
207
+ def transition_for(object, requirements = {})
208
+ assert_valid_keys(requirements, :from, :to, :guard)
209
+ requirements[:from] = machine.states.match!(object).name unless custom_from_state = requirements.include?(:from)
210
+
211
+ branches.each do |branch|
212
+ if match = branch.match(object, requirements)
213
+ # Branch allows for the transition to occur
214
+ from = requirements[:from]
215
+ to = match[:to].values.empty? ? from : match[:to].values.first
216
+
217
+ return Transition.new(object, machine, name, from, to, !custom_from_state)
218
+ end
219
+ end
220
+
221
+ # No transition matched
222
+ nil
223
+ end
224
+
225
+ # Attempts to perform the next available transition on the given object.
226
+ # If no transitions can be made, then this will return false, otherwise
227
+ # true.
228
+ #
229
+ # Any additional arguments are passed to the StateMachine::Transition#perform
230
+ # instance method.
231
+ def fire(object, *args)
232
+ machine.reset(object)
233
+
234
+ if transition = transition_for(object)
235
+ transition.perform(*args)
236
+ else
237
+ on_failure(object)
238
+ false
239
+ end
240
+ end
241
+
242
+ # Marks the object as invalid and runs any failure callbacks associated with
243
+ # this event. This should get called anytime this event fails to transition.
244
+ def on_failure(object)
245
+ machine.invalidate(object, :state, :invalid_transition, [[:event, human_name(object.class)]])
246
+
247
+ state = machine.states.match!(object).name
248
+ Transition.new(object, machine, name, state, state).run_callbacks(:before => false)
249
+ end
250
+
251
+ # Draws a representation of this event on the given graph. This will
252
+ # create 1 or more edges on the graph for each branch (i.e. transition)
253
+ # configured.
254
+ #
255
+ # A collection of the generated edges will be returned.
256
+ def draw(graph)
257
+ valid_states = machine.states.by_priority.map {|state| state.name}
258
+ branches.collect {|branch| branch.draw(graph, name, valid_states)}.flatten
259
+ end
260
+
261
+ # Generates a nicely formatted description of this event's contents.
262
+ #
263
+ # For example,
264
+ #
265
+ # event = StateMachine::Event.new(machine, :park)
266
+ # event.transition all - :idling => :parked, :idling => same
267
+ # event # => #<StateMachine::Event name=:park transitions=[all - :idling => :parked, :idling => same]>
268
+ def inspect
269
+ transitions = branches.map do |branch|
270
+ branch.state_requirements.map do |state_requirement|
271
+ "#{state_requirement[:from].description} => #{state_requirement[:to].description}"
272
+ end * ', '
273
+ end
274
+
275
+ "#<#{self.class} name=#{name.inspect} transitions=[#{transitions * ', '}]>"
276
+ end
277
+
278
+ protected
279
+ # Add the various instance methods that can transition the object using
280
+ # the current event
281
+ def add_actions
282
+ # Checks whether the event can be fired on the current object
283
+ machine.define_helper(:instance, "can_#{qualified_name}?") do |machine, object, *args|
284
+ machine.event(name).can_fire?(object, *args)
285
+ end
286
+
287
+ # Gets the next transition that would be performed if the event were
288
+ # fired now
289
+ machine.define_helper(:instance, "#{qualified_name}_transition") do |machine, object, *args|
290
+ machine.event(name).transition_for(object, *args)
291
+ end
292
+
293
+ # Fires the event
294
+ machine.define_helper(:instance, qualified_name) do |machine, object, *args|
295
+ machine.event(name).fire(object, *args)
296
+ end
297
+
298
+ # Fires the event, raising an exception if it fails
299
+ machine.define_helper(:instance, "#{qualified_name}!") do |machine, object, *args|
300
+ object.send(qualified_name, *args) || raise(StateMachine::InvalidTransition.new(object, machine, name))
301
+ end
302
+ end
303
+ end
304
+ end