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