state_machines 0.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 (79) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.idea/.name +1 -0
  4. data/.idea/.rakeTasks +7 -0
  5. data/.idea/cssxfire.xml +9 -0
  6. data/.idea/encodings.xml +5 -0
  7. data/.idea/misc.xml +5 -0
  8. data/.idea/modules.xml +12 -0
  9. data/.idea/scopes/scope_settings.xml +5 -0
  10. data/.idea/state_machine2.iml +34 -0
  11. data/.idea/vcs.xml +9 -0
  12. data/.idea/workspace.xml +1156 -0
  13. data/.rspec +3 -0
  14. data/.travis.yml +8 -0
  15. data/Gemfile +4 -0
  16. data/LICENSE.txt +23 -0
  17. data/README.md +29 -0
  18. data/Rakefile +1 -0
  19. data/lib/state_machines/assertions.rb +40 -0
  20. data/lib/state_machines/branch.rb +187 -0
  21. data/lib/state_machines/callback.rb +220 -0
  22. data/lib/state_machines/core.rb +25 -0
  23. data/lib/state_machines/core_ext/class/state_machine.rb +5 -0
  24. data/lib/state_machines/core_ext.rb +2 -0
  25. data/lib/state_machines/error.rb +13 -0
  26. data/lib/state_machines/eval_helpers.rb +87 -0
  27. data/lib/state_machines/event.rb +246 -0
  28. data/lib/state_machines/event_collection.rb +141 -0
  29. data/lib/state_machines/extensions.rb +148 -0
  30. data/lib/state_machines/helper_module.rb +17 -0
  31. data/lib/state_machines/integrations/base.rb +100 -0
  32. data/lib/state_machines/integrations.rb +113 -0
  33. data/lib/state_machines/machine.rb +2234 -0
  34. data/lib/state_machines/machine_collection.rb +84 -0
  35. data/lib/state_machines/macro_methods.rb +520 -0
  36. data/lib/state_machines/matcher.rb +123 -0
  37. data/lib/state_machines/matcher_helpers.rb +54 -0
  38. data/lib/state_machines/node_collection.rb +221 -0
  39. data/lib/state_machines/path.rb +120 -0
  40. data/lib/state_machines/path_collection.rb +90 -0
  41. data/lib/state_machines/state.rb +276 -0
  42. data/lib/state_machines/state_collection.rb +112 -0
  43. data/lib/state_machines/state_context.rb +138 -0
  44. data/lib/state_machines/transition.rb +470 -0
  45. data/lib/state_machines/transition_collection.rb +245 -0
  46. data/lib/state_machines/version.rb +3 -0
  47. data/lib/state_machines/yard.rb +8 -0
  48. data/lib/state_machines.rb +3 -0
  49. data/spec/errors/default_spec.rb +14 -0
  50. data/spec/errors/with_message_spec.rb +39 -0
  51. data/spec/helpers/helper_spec.rb +14 -0
  52. data/spec/internal/app/models/auto_shop.rb +31 -0
  53. data/spec/internal/app/models/car.rb +19 -0
  54. data/spec/internal/app/models/model_base.rb +6 -0
  55. data/spec/internal/app/models/motorcycle.rb +9 -0
  56. data/spec/internal/app/models/traffic_light.rb +47 -0
  57. data/spec/internal/app/models/vehicle.rb +123 -0
  58. data/spec/machine_spec.rb +3167 -0
  59. data/spec/matcher_helpers_spec.rb +39 -0
  60. data/spec/matcher_spec.rb +157 -0
  61. data/spec/models/auto_shop_spec.rb +41 -0
  62. data/spec/models/car_spec.rb +90 -0
  63. data/spec/models/motorcycle_spec.rb +44 -0
  64. data/spec/models/traffic_light_spec.rb +56 -0
  65. data/spec/models/vehicle_spec.rb +580 -0
  66. data/spec/node_collection_spec.rb +371 -0
  67. data/spec/path_collection_spec.rb +271 -0
  68. data/spec/path_spec.rb +488 -0
  69. data/spec/spec_helper.rb +6 -0
  70. data/spec/state_collection_spec.rb +352 -0
  71. data/spec/state_context_spec.rb +442 -0
  72. data/spec/state_machine_spec.rb +29 -0
  73. data/spec/state_spec.rb +970 -0
  74. data/spec/support/migration_helpers.rb +50 -0
  75. data/spec/support/models.rb +6 -0
  76. data/spec/transition_collection_spec.rb +2199 -0
  77. data/spec/transition_spec.rb +1558 -0
  78. data/state_machines.gemspec +23 -0
  79. metadata +194 -0
@@ -0,0 +1,84 @@
1
+ module StateMachines
2
+ # Represents a collection of state machines for a class
3
+ class MachineCollection < Hash
4
+
5
+
6
+ # Initializes the state of each machine in the given object. This can allow
7
+ # states to be initialized in two groups: static and dynamic. For example:
8
+ #
9
+ # machines.initialize_states(object) do
10
+ # # After static state initialization, before dynamic state initialization
11
+ # end
12
+ #
13
+ # If no block is provided, then all states will still be initialized.
14
+ #
15
+ # Valid configuration options:
16
+ # * <tt>:static</tt> - Whether to initialize static states. If set to
17
+ # :force, the state will be initialized regardless of its current value.
18
+ # Default is :force.
19
+ # * <tt>:dynamic</tt> - Whether to initialize dynamic states. If set to
20
+ # :force, the state will be initialized regardless of its current value.
21
+ # Default is true.
22
+ # * <tt>:to</tt> - A hash to write the initialized state to instead of
23
+ # writing to the object. Default is to write directly to the object.
24
+ def initialize_states(object, options = {})
25
+ options.assert_valid_keys( :static, :dynamic, :to)
26
+ options = {:static => true, :dynamic => true}.merge(options)
27
+
28
+ each_value do |machine|
29
+ machine.initialize_state(object, :force => options[:static] == :force, :to => options[:to]) unless machine.dynamic_initial_state?
30
+ end if options[:static]
31
+
32
+ result = yield if block_given?
33
+
34
+ each_value do |machine|
35
+ machine.initialize_state(object, :force => options[:dynamic] == :force, :to => options[:to]) if machine.dynamic_initial_state?
36
+ end if options[:dynamic]
37
+
38
+ result
39
+ end
40
+
41
+ # Runs one or more events in parallel on the given object. See
42
+ # StateMachines::InstanceMethods#fire_events for more information.
43
+ def fire_events(object, *events)
44
+ run_action = [true, false].include?(events.last) ? events.pop : true
45
+
46
+ # Generate the transitions to run for each event
47
+ transitions = events.collect do |event_name|
48
+ # Find the actual event being run
49
+ event = nil
50
+ detect {|name, machine| event = machine.events[event_name, :qualified_name]}
51
+
52
+ raise(InvalidEvent.new(object, event_name)) unless event
53
+
54
+ # Get the transition that will be performed for the event
55
+ unless transition = event.transition_for(object)
56
+ event.on_failure(object)
57
+ end
58
+
59
+ transition
60
+ end.compact
61
+
62
+ # Run the events in parallel only if valid transitions were found for
63
+ # all of them
64
+ if events.length == transitions.length
65
+ TransitionCollection.new(transitions, :actions => run_action).perform
66
+ else
67
+ false
68
+ end
69
+ end
70
+
71
+ # Builds the collection of transitions for all event attributes defined on
72
+ # the given object. This will only include events whose machine actions
73
+ # match the one specified.
74
+ #
75
+ # These should only be fired as a result of the action being run.
76
+ def transitions(object, action, options = {})
77
+ transitions = map do |name, machine|
78
+ machine.events.attribute_transition_for(object, true) if machine.action == action
79
+ end
80
+
81
+ AttributeTransitionCollection.new(transitions.compact, options)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,520 @@
1
+ # A state machine is a model of behavior composed of states, events, and
2
+ # transitions. This helper adds support for defining this type of
3
+ # functionality on any Ruby class.
4
+ module StateMachines
5
+ module MacroMethods
6
+ # Creates a new state machine with the given name. The default name, if not
7
+ # specified, is <tt>:state</tt>.
8
+ #
9
+ # Configuration options:
10
+ # * <tt>:attribute</tt> - The name of the attribute to store the state value
11
+ # in. By default, this is the same as the name of the machine.
12
+ # * <tt>:initial</tt> - The initial state of the attribute. This can be a
13
+ # static state or a lambda block which will be evaluated at runtime
14
+ # (e.g. lambda {|vehicle| vehicle.speed == 0 ? :parked : :idling}).
15
+ # Default is nil.
16
+ # * <tt>:initialize</tt> - Whether to automatically initialize the attribute
17
+ # by hooking into #initialize on the owner class. Default is true.
18
+ # * <tt>:action</tt> - The instance method to invoke when an object
19
+ # transitions. Default is nil unless otherwise specified by the
20
+ # configured integration.
21
+ # * <tt>:namespace</tt> - The name to use for namespacing all generated
22
+ # state / event instance methods (e.g. "heater" would generate
23
+ # :turn_on_heater and :turn_off_heater for the :turn_on/:turn_off events).
24
+ # Default is nil.
25
+ # * <tt>:integration</tt> - The name of the integration to use for adding
26
+ # library-specific behavior to the machine. Built-in integrations
27
+ # include :active_model, :active_record, :data_mapper, :mongo_mapper, and
28
+ # :sequel. By default, this is determined automatically.
29
+ #
30
+ # Configuration options relevant to ORM integrations:
31
+ # * <tt>:plural</tt> - The pluralized version of the name. By default, this
32
+ # will attempt to call +pluralize+ on the name. If this method is not
33
+ # available, an "s" is appended. This is used for generating scopes.
34
+ # * <tt>:messages</tt> - The error messages to use when invalidating
35
+ # objects due to failed transitions. Messages include:
36
+ # * <tt>:invalid</tt>
37
+ # * <tt>:invalid_event</tt>
38
+ # * <tt>:invalid_transition</tt>
39
+ # * <tt>:use_transactions</tt> - Whether transactions should be used when
40
+ # firing events. Default is true unless otherwise specified by the
41
+ # configured integration.
42
+ #
43
+ # This also expects a block which will be used to actually configure the
44
+ # states, events and transitions for the state machine. *Note* that this
45
+ # block will be executed within the context of the state machine. As a
46
+ # result, you will not be able to access any class methods unless you refer
47
+ # to them directly (i.e. specifying the class name).
48
+ #
49
+ # For examples on the types of state machine configurations and blocks, see
50
+ # the section below.
51
+ #
52
+ # == Examples
53
+ #
54
+ # With the default name/attribute and no configuration:
55
+ #
56
+ # class Vehicle
57
+ # state_machine do
58
+ # event :park do
59
+ # ...
60
+ # end
61
+ # end
62
+ # end
63
+ #
64
+ # The above example will define a state machine named "state" that will
65
+ # store the value in the +state+ attribute. Every vehicle will start
66
+ # without an initial state.
67
+ #
68
+ # With a custom name / attribute:
69
+ #
70
+ # class Vehicle
71
+ # state_machine :status, :attribute => :status_value do
72
+ # ...
73
+ # end
74
+ # end
75
+ #
76
+ # With a static initial state:
77
+ #
78
+ # class Vehicle
79
+ # state_machine :status, :initial => :parked do
80
+ # ...
81
+ # end
82
+ # end
83
+ #
84
+ # With a dynamic initial state:
85
+ #
86
+ # class Vehicle
87
+ # state_machine :status, :initial => lambda {|vehicle| vehicle.speed == 0 ? :parked : :idling} do
88
+ # ...
89
+ # end
90
+ # end
91
+ #
92
+ # == Class Methods
93
+ #
94
+ # The following class methods will be automatically generated by the
95
+ # state machine based on the *name* of the machine. Any existing methods
96
+ # will not be overwritten.
97
+ # * <tt>human_state_name(state)</tt> - Gets the humanized value for the
98
+ # given state. This may be generated by internationalization libraries if
99
+ # supported by the integration.
100
+ # * <tt>human_state_event_name(event)</tt> - Gets the humanized value for
101
+ # the given event. This may be generated by internationalization
102
+ # libraries if supported by the integration.
103
+ #
104
+ # For example,
105
+ #
106
+ # class Vehicle
107
+ # state_machine :state, :initial => :parked do
108
+ # event :ignite do
109
+ # transition :parked => :idling
110
+ # end
111
+ #
112
+ # event :shift_up do
113
+ # transition :idling => :first_gear
114
+ # end
115
+ # end
116
+ # end
117
+ #
118
+ # Vehicle.human_state_name(:parked) # => "parked"
119
+ # Vehicle.human_state_name(:first_gear) # => "first gear"
120
+ # Vehicle.human_state_event_name(:park) # => "park"
121
+ # Vehicle.human_state_event_name(:shift_up) # => "shift up"
122
+ #
123
+ # == Instance Methods
124
+ #
125
+ # The following instance methods will be automatically generated by the
126
+ # state machine based on the *name* of the machine. Any existing methods
127
+ # will not be overwritten.
128
+ # * <tt>state</tt> - Gets the current value for the attribute
129
+ # * <tt>state=(value)</tt> - Sets the current value for the attribute
130
+ # * <tt>state?(name)</tt> - Checks the given state name against the current
131
+ # state. If the name is not a known state, then an ArgumentError is raised.
132
+ # * <tt>state_name</tt> - Gets the name of the state for the current value
133
+ # * <tt>human_state_name</tt> - Gets the human-readable name of the state
134
+ # for the current value
135
+ # * <tt>state_events(requirements = {})</tt> - Gets the list of events that
136
+ # can be fired on the current object's state (uses the *unqualified* event
137
+ # names)
138
+ # * <tt>state_transitions(requirements = {})</tt> - Gets the list of
139
+ # transitions that can be made on the current object's state
140
+ # * <tt>state_paths(requirements = {})</tt> - Gets the list of sequences of
141
+ # transitions that can be run from the current object's state
142
+ # * <tt>fire_state_event(name, *args)</tt> - Fires an arbitrary event with
143
+ # the given argument list. This is essentially the same as calling the
144
+ # actual event method itself.
145
+ #
146
+ # The <tt>state_events</tt>, <tt>state_transitions</tt>, and <tt>state_paths</tt>
147
+ # helpers all take an optional set of requirements for determining what's
148
+ # available for the current object. These requirements include:
149
+ # * <tt>:from</tt> - One or more states to transition from. If none are
150
+ # specified, then this will be the object's current state.
151
+ # * <tt>:to</tt> - One or more states to transition to. If none are
152
+ # specified, then this will match any to state.
153
+ # * <tt>:on</tt> - One or more events to transition on. If none are
154
+ # specified, then this will match any event.
155
+ # * <tt>:guard</tt> - Whether to guard transitions with the if/unless
156
+ # conditionals defined for each one. Default is true.
157
+ #
158
+ # For example,
159
+ #
160
+ # class Vehicle
161
+ # state_machine :state, :initial => :parked do
162
+ # event :ignite do
163
+ # transition :parked => :idling
164
+ # end
165
+ #
166
+ # event :park do
167
+ # transition :idling => :parked
168
+ # end
169
+ # end
170
+ # end
171
+ #
172
+ # vehicle = Vehicle.new
173
+ # vehicle.state # => "parked"
174
+ # vehicle.state_name # => :parked
175
+ # vehicle.human_state_name # => "parked"
176
+ # vehicle.state?(:parked) # => true
177
+ #
178
+ # # Changing state
179
+ # vehicle.state = 'idling'
180
+ # vehicle.state # => "idling"
181
+ # vehicle.state_name # => :idling
182
+ # vehicle.state?(:parked) # => false
183
+ #
184
+ # # Getting current event / transition availability
185
+ # vehicle.state_events # => [:park]
186
+ # vehicle.park # => true
187
+ # vehicle.state_events # => [:ignite]
188
+ # vehicle.state_events(:from => :idling) # => [:park]
189
+ # vehicle.state_events(:to => :parked) # => []
190
+ #
191
+ # vehicle.state_transitions # => [#<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
192
+ # vehicle.ignite # => true
193
+ # vehicle.state_transitions # => [#<StateMachines::Transition attribute=:state event=:park from="idling" from_name=:idling to="parked" to_name=:parked>]
194
+ #
195
+ # vehicle.state_transitions(:on => :ignite) # => []
196
+ #
197
+ # # Getting current path availability
198
+ # vehicle.state_paths # => [
199
+ # # [#<StateMachines::Transition attribute=:state event=:park from="idling" from_name=:idling to="parked" to_name=:parked>,
200
+ # # #<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
201
+ # # ]
202
+ # vehicle.state_paths(:guard => false) # =>
203
+ # # [#<StateMachines::Transition attribute=:state event=:park from="idling" from_name=:idling to="parked" to_name=:parked>,
204
+ # # #<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
205
+ # # ]
206
+ #
207
+ # # Fire arbitrary events
208
+ # vehicle.fire_state_event(:park) # => true
209
+ #
210
+ # == Attribute initialization
211
+ #
212
+ # For most classes, the initial values for state machine attributes are
213
+ # automatically assigned when a new object is created. However, this
214
+ # behavior will *not* work if the class defines an +initialize+ method
215
+ # without properly calling +super+.
216
+ #
217
+ # For example,
218
+ #
219
+ # class Vehicle
220
+ # state_machine :state, :initial => :parked do
221
+ # ...
222
+ # end
223
+ # end
224
+ #
225
+ # vehicle = Vehicle.new # => #<Vehicle:0xb7c8dbf8 @state="parked">
226
+ # vehicle.state # => "parked"
227
+ #
228
+ # In the above example, no +initialize+ method is defined. As a result,
229
+ # the default behavior of initializing the state machine attributes is used.
230
+ #
231
+ # In the following example, a custom +initialize+ method is defined:
232
+ #
233
+ # class Vehicle
234
+ # state_machine :state, :initial => :parked do
235
+ # ...
236
+ # end
237
+ #
238
+ # def initialize
239
+ # end
240
+ # end
241
+ #
242
+ # vehicle = Vehicle.new # => #<Vehicle:0xb7c77678>
243
+ # vehicle.state # => nil
244
+ #
245
+ # Since the +initialize+ method is defined, the state machine attributes
246
+ # never get initialized. In order to ensure that all initialization hooks
247
+ # are called, the custom method *must* call +super+ without any arguments
248
+ # like so:
249
+ #
250
+ # class Vehicle
251
+ # state_machine :state, :initial => :parked do
252
+ # ...
253
+ # end
254
+ #
255
+ # def initialize(attributes = {})
256
+ # ...
257
+ # super()
258
+ # end
259
+ # end
260
+ #
261
+ # vehicle = Vehicle.new # => #<Vehicle:0xb7c8dbf8 @state="parked">
262
+ # vehicle.state # => "parked"
263
+ #
264
+ # Because of the way the inclusion of modules works in Ruby, calling
265
+ # <tt>super()</tt> will not only call the superclass's +initialize+, but
266
+ # also +initialize+ on all included modules. This allows the original state
267
+ # machine hook to get called properly.
268
+ #
269
+ # If you want to avoid calling the superclass's constructor, but still want
270
+ # to initialize the state machine attributes:
271
+ #
272
+ # class Vehicle
273
+ # state_machine :state, :initial => :parked do
274
+ # ...
275
+ # end
276
+ #
277
+ # def initialize(attributes = {})
278
+ # ...
279
+ # initialize_state_machines
280
+ # end
281
+ # end
282
+ #
283
+ # vehicle = Vehicle.new # => #<Vehicle:0xb7c8dbf8 @state="parked">
284
+ # vehicle.state # => "parked"
285
+ #
286
+ # You may also need to call the +initialize_state_machines+ helper manually
287
+ # in cases where you want to change how static / dynamic initial states get
288
+ # set. For example, the following example forces the initialization of
289
+ # static states regardless of their current value:
290
+ #
291
+ # class Vehicle
292
+ # state_machine :state, :initial => :parked do
293
+ # state nil, :idling
294
+ # ...
295
+ # end
296
+ #
297
+ # def initialize(attributes = {})
298
+ # @state = 'idling'
299
+ # initialize_state_machines(:static => :force) do
300
+ # ...
301
+ # end
302
+ # end
303
+ # end
304
+ #
305
+ # vehicle = Vehicle.new # => #<Vehicle:0xb7c8dbf8 @state="parked">
306
+ # vehicle.state # => "parked"
307
+ #
308
+ # The above example is also noteworthy because it demonstrates how to avoid
309
+ # initialization issues when +nil+ is a valid state. Without passing in
310
+ # <tt>:static => :force</tt>, state_machine would never have initialized
311
+ # the state because +nil+ (the default attribute value) would have been
312
+ # interpreted as a valid current state. As a result, state_machine would
313
+ # have simply skipped initialization.
314
+ #
315
+ # == States
316
+ #
317
+ # All of the valid states for the machine are automatically tracked based
318
+ # on the events, transitions, and callbacks defined for the machine. If
319
+ # there are additional states that are never referenced, these should be
320
+ # explicitly added using the StateMachines::Machine#state or
321
+ # StateMachines::Machine#other_states helpers.
322
+ #
323
+ # When a new state is defined, a predicate method for that state is
324
+ # generated on the class. For example,
325
+ #
326
+ # class Vehicle
327
+ # state_machine :initial => :parked do
328
+ # event :ignite do
329
+ # transition all => :idling
330
+ # end
331
+ # end
332
+ # end
333
+ #
334
+ # ...will generate the following instance methods (assuming they're not
335
+ # already defined in the class):
336
+ # * <tt>parked?</tt>
337
+ # * <tt>idling?</tt>
338
+ #
339
+ # Each predicate method will return true if it matches the object's
340
+ # current state. Otherwise, it will return false.
341
+ #
342
+ # == Attribute access
343
+ #
344
+ # The actual value for a state is stored in the attribute configured for the
345
+ # state machine. In most cases, this is the same as the name of the state
346
+ # machine. For example:
347
+ #
348
+ # class Vehicle
349
+ # attr_accessor :state
350
+ #
351
+ # state_machine :state, :initial => :parked do
352
+ # ...
353
+ # state :parked, :value => 0
354
+ # start :idling, :value => 1
355
+ # end
356
+ # end
357
+ #
358
+ # vehicle = Vehicle.new # => #<Vehicle:0xb712da60 @state=0>
359
+ # vehicle.state # => 0
360
+ # vehicle.parked? # => true
361
+ # vehicle.state = 1
362
+ # vehicle.idling? # => true
363
+ #
364
+ # The most important thing to note from the example above is what it means
365
+ # to read from and write to the state machine's attribute. In particular,
366
+ # state_machine treats the attribute (+state+ in this case) like a basic
367
+ # attr_accessor that's been defined on the class. There are no special
368
+ # behaviors added, such as allowing the attribute to be written to based on
369
+ # the name of a state in the machine. This is the case for a few reasons:
370
+ # * Setting the attribute directly is an edge case that is meant to only be
371
+ # used when you want to skip state_machine altogether. This means that
372
+ # state_machine shouldn't have any effect on the attribute accessor
373
+ # methods. If you want to change the state, you should be using one of
374
+ # the events defined in the state machine.
375
+ # * Many ORMs provide custom behavior for the attribute reader / writer - it
376
+ # may even be defined by your own framework / method implementation just
377
+ # the example above showed. In order to avoid having to worry about the
378
+ # different ways an attribute can get written, state_machine just makes
379
+ # sure that the configured value for a state is always used when writing
380
+ # to the attribute.
381
+ #
382
+ # If you were interested in accessing the name of a state (instead of its
383
+ # actual value through the attribute), you could do the following:
384
+ #
385
+ # vehicle.state_name # => :idling
386
+ #
387
+ # == Events and Transitions
388
+ #
389
+ # Events defined on the machine are the interface to transitioning states
390
+ # for an object. Events can be fired either directly (through the method
391
+ # generated for the event) or indirectly (through attributes defined on
392
+ # the machine).
393
+ #
394
+ # For example,
395
+ #
396
+ # class Vehicle
397
+ # include DataMapper::Resource
398
+ # property :id, Serial
399
+ #
400
+ # state_machine :initial => :parked do
401
+ # event :ignite do
402
+ # transition :parked => :idling
403
+ # end
404
+ # end
405
+ #
406
+ # state_machine :alarm_state, :initial => :active do
407
+ # event :disable do
408
+ # transition all => :off
409
+ # end
410
+ # end
411
+ # end
412
+ #
413
+ # # Fire +ignite+ event directly
414
+ # vehicle = Vehicle.create # => #<Vehicle id=1 state="parked" alarm_state="active">
415
+ # vehicle.ignite # => true
416
+ # vehicle.state # => "idling"
417
+ # vehicle.alarm_state # => "active"
418
+ #
419
+ # # Fire +disable+ event automatically
420
+ # vehicle.alarm_state_event = 'disable'
421
+ # vehicle.save # => true
422
+ # vehicle.alarm_state # => "off"
423
+ #
424
+ # In the above example, the +state+ attribute is transitioned using the
425
+ # +ignite+ action that's generated from the state machine. On the other
426
+ # hand, the +alarm_state+ attribute is transitioned using the +alarm_state_event+
427
+ # attribute that automatically gets fired when the machine's action (+save+)
428
+ # is invoked.
429
+ #
430
+ # For more information about how to configure an event and its associated
431
+ # transitions, see StateMachines::Machine#event.
432
+ #
433
+ # == Defining callbacks
434
+ #
435
+ # Within the +state_machine+ block, you can also define callbacks for
436
+ # transitions. For more information about defining these callbacks,
437
+ # see StateMachines::Machine#before_transition, StateMachines::Machine#after_transition,
438
+ # and StateMachines::Machine#around_transition, and StateMachines::Machine#after_failure.
439
+ #
440
+ # == Namespaces
441
+ #
442
+ # When a namespace is configured for a state machine, the name provided
443
+ # will be used in generating the instance methods for interacting with
444
+ # states/events in the machine. This is particularly useful when a class
445
+ # has multiple state machines and it would be difficult to differentiate
446
+ # between the various states / events.
447
+ #
448
+ # For example,
449
+ #
450
+ # class Vehicle
451
+ # state_machine :heater_state, :initial => :off, :namespace => 'heater' do
452
+ # event :turn_on do
453
+ # transition all => :on
454
+ # end
455
+ #
456
+ # event :turn_off do
457
+ # transition all => :off
458
+ # end
459
+ # end
460
+ #
461
+ # state_machine :alarm_state, :initial => :active, :namespace => 'alarm' do
462
+ # event :turn_on do
463
+ # transition all => :active
464
+ # end
465
+ #
466
+ # event :turn_off do
467
+ # transition all => :off
468
+ # end
469
+ # end
470
+ # end
471
+ #
472
+ # The above class defines two state machines: +heater_state+ and +alarm_state+.
473
+ # For the +heater_state+ machine, the following methods are generated since
474
+ # it's namespaced by "heater":
475
+ # * <tt>can_turn_on_heater?</tt>
476
+ # * <tt>turn_on_heater</tt>
477
+ # * ...
478
+ # * <tt>can_turn_off_heater?</tt>
479
+ # * <tt>turn_off_heater</tt>
480
+ # * ..
481
+ # * <tt>heater_off?</tt>
482
+ # * <tt>heater_on?</tt>
483
+ #
484
+ # As shown, each method is unique to the state machine so that the states
485
+ # and events don't conflict. The same goes for the +alarm_state+ machine:
486
+ # * <tt>can_turn_on_alarm?</tt>
487
+ # * <tt>turn_on_alarm</tt>
488
+ # * ...
489
+ # * <tt>can_turn_off_alarm?</tt>
490
+ # * <tt>turn_off_alarm</tt>
491
+ # * ..
492
+ # * <tt>alarm_active?</tt>
493
+ # * <tt>alarm_off?</tt>
494
+ #
495
+ # == Scopes
496
+ #
497
+ # For integrations that support it, a group of default scope filters will
498
+ # be automatically created for assisting in finding objects that have the
499
+ # attribute set to one of a given set of states.
500
+ #
501
+ # For example,
502
+ #
503
+ # Vehicle.with_state(:parked) # => All vehicles where the state is parked
504
+ # Vehicle.with_states(:parked, :idling) # => All vehicles where the state is either parked or idling
505
+ #
506
+ # Vehicle.without_state(:parked) # => All vehicles where the state is *not* parked
507
+ # Vehicle.without_states(:parked, :idling) # => All vehicles where the state is *not* parked or idling
508
+ #
509
+ # *Note* that if class methods already exist with those names (i.e.
510
+ # :with_state, :with_states, :without_state, or :without_states), then a
511
+ # scope will not be defined for that name.
512
+ #
513
+ # See StateMachines::Machine for more information about using integrations
514
+ # and the individual integration docs for information about the actual
515
+ # scopes that are generated.
516
+ def state_machine(*args, &block)
517
+ StateMachines::Machine.find_or_create(self, *args, &block)
518
+ end
519
+ end
520
+ end