enum_state_machine 0.0.1 → 0.0.2

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +12 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. metadata +83 -130
  6. data/.rvmrc +0 -1
  7. data/enum_state_machine.gemspec +0 -25
  8. data/lib/enum_state_machine.rb +0 -9
  9. data/lib/enum_state_machine/assertions.rb +0 -36
  10. data/lib/enum_state_machine/branch.rb +0 -225
  11. data/lib/enum_state_machine/callback.rb +0 -232
  12. data/lib/enum_state_machine/core.rb +0 -12
  13. data/lib/enum_state_machine/core_ext.rb +0 -2
  14. data/lib/enum_state_machine/core_ext/class/state_machine.rb +0 -5
  15. data/lib/enum_state_machine/error.rb +0 -13
  16. data/lib/enum_state_machine/eval_helpers.rb +0 -87
  17. data/lib/enum_state_machine/event.rb +0 -257
  18. data/lib/enum_state_machine/event_collection.rb +0 -141
  19. data/lib/enum_state_machine/extensions.rb +0 -149
  20. data/lib/enum_state_machine/graph.rb +0 -92
  21. data/lib/enum_state_machine/helper_module.rb +0 -17
  22. data/lib/enum_state_machine/initializers.rb +0 -4
  23. data/lib/enum_state_machine/initializers/rails.rb +0 -22
  24. data/lib/enum_state_machine/integrations.rb +0 -97
  25. data/lib/enum_state_machine/integrations/active_model.rb +0 -585
  26. data/lib/enum_state_machine/integrations/active_model/locale.rb +0 -11
  27. data/lib/enum_state_machine/integrations/active_model/observer.rb +0 -33
  28. data/lib/enum_state_machine/integrations/active_model/observer_update.rb +0 -42
  29. data/lib/enum_state_machine/integrations/active_model/versions.rb +0 -31
  30. data/lib/enum_state_machine/integrations/active_record.rb +0 -548
  31. data/lib/enum_state_machine/integrations/active_record/locale.rb +0 -20
  32. data/lib/enum_state_machine/integrations/active_record/versions.rb +0 -123
  33. data/lib/enum_state_machine/integrations/base.rb +0 -100
  34. data/lib/enum_state_machine/machine.rb +0 -2292
  35. data/lib/enum_state_machine/machine_collection.rb +0 -86
  36. data/lib/enum_state_machine/macro_methods.rb +0 -518
  37. data/lib/enum_state_machine/matcher.rb +0 -123
  38. data/lib/enum_state_machine/matcher_helpers.rb +0 -54
  39. data/lib/enum_state_machine/node_collection.rb +0 -222
  40. data/lib/enum_state_machine/path.rb +0 -120
  41. data/lib/enum_state_machine/path_collection.rb +0 -90
  42. data/lib/enum_state_machine/state.rb +0 -297
  43. data/lib/enum_state_machine/state_collection.rb +0 -112
  44. data/lib/enum_state_machine/state_context.rb +0 -138
  45. data/lib/enum_state_machine/state_enum.rb +0 -23
  46. data/lib/enum_state_machine/transition.rb +0 -470
  47. data/lib/enum_state_machine/transition_collection.rb +0 -245
  48. data/lib/enum_state_machine/version.rb +0 -3
  49. data/lib/enum_state_machine/yard.rb +0 -8
  50. data/lib/enum_state_machine/yard/handlers.rb +0 -12
  51. data/lib/enum_state_machine/yard/handlers/base.rb +0 -32
  52. data/lib/enum_state_machine/yard/handlers/event.rb +0 -25
  53. data/lib/enum_state_machine/yard/handlers/machine.rb +0 -344
  54. data/lib/enum_state_machine/yard/handlers/state.rb +0 -25
  55. data/lib/enum_state_machine/yard/handlers/transition.rb +0 -47
  56. data/lib/enum_state_machine/yard/templates.rb +0 -3
  57. data/lib/enum_state_machine/yard/templates/default/class/html/setup.rb +0 -30
  58. data/lib/enum_state_machine/yard/templates/default/class/html/state_machines.erb +0 -12
  59. data/lib/tasks/enum_state_machine.rake +0 -1
  60. data/lib/tasks/enum_state_machine.rb +0 -24
  61. data/lib/yard-enum_state_machine.rb +0 -2
  62. data/test/functional/state_machine_test.rb +0 -1066
  63. data/test/unit/integrations/active_model_test.rb +0 -1245
  64. data/test/unit/integrations/active_record_test.rb +0 -2551
  65. data/test/unit/integrations/base_test.rb +0 -104
  66. data/test/unit/integrations_test.rb +0 -71
  67. data/test/unit/invalid_event_test.rb +0 -20
  68. data/test/unit/invalid_parallel_transition_test.rb +0 -18
  69. data/test/unit/invalid_transition_test.rb +0 -115
  70. data/test/unit/machine_collection_test.rb +0 -603
  71. data/test/unit/machine_test.rb +0 -3395
  72. data/test/unit/state_machine_test.rb +0 -31
@@ -1,11 +0,0 @@
1
- {:en => {
2
- :activemodel => {
3
- :errors => {
4
- :messages => {
5
- :invalid => EnumStateMachine::Machine.default_messages[:invalid],
6
- :invalid_event => EnumStateMachine::Machine.default_messages[:invalid_event] % ['%{state}'],
7
- :invalid_transition => EnumStateMachine::Machine.default_messages[:invalid_transition] % ['%{event}']
8
- }
9
- }
10
- }
11
- }}
@@ -1,33 +0,0 @@
1
- module EnumStateMachine
2
- module Integrations #:nodoc:
3
- module ActiveModel
4
- # Adds support for invoking callbacks on ActiveModel observers with more
5
- # than one argument (e.g. the record *and* the state transition). By
6
- # default, ActiveModel only supports passing the record into the
7
- # callbacks.
8
- #
9
- # For example:
10
- #
11
- # class VehicleObserver < ActiveModel::Observer
12
- # # The default behavior: only pass in the record
13
- # def after_save(vehicle)
14
- # end
15
- #
16
- # # Custom behavior: allow the transition to be passed in as well
17
- # def after_transition(vehicle, transition)
18
- # Audit.log(vehicle, transition)
19
- # end
20
- # end
21
- module Observer
22
- def update_with_transition(observer_update)
23
- method = observer_update.method
24
- send(method, *observer_update.args) if respond_to?(method)
25
- end
26
- end
27
- end
28
- end
29
- end
30
-
31
- ActiveModel::Observer.class_eval do
32
- include EnumStateMachine::Integrations::ActiveModel::Observer
33
- end if defined?(ActiveModel::Observer)
@@ -1,42 +0,0 @@
1
- module EnumStateMachine
2
- module Integrations #:nodoc:
3
- module ActiveModel
4
- # Represents the encapsulation of all of the details to be included in an
5
- # update to state machine observers. This allows multiple arguments to
6
- # get passed to an observer method (instead of just a single +object+)
7
- # while still respecting the way in which ActiveModel checks for the
8
- # object's list of observers.
9
- class ObserverUpdate
10
- # The method to invoke on the observer
11
- attr_reader :method
12
-
13
- # The object being transitioned
14
- attr_reader :object
15
-
16
- # The transition being run
17
- attr_reader :transition
18
-
19
- def initialize(method, object, transition) #:nodoc:
20
- @method, @object, @transition = method, object, transition
21
- end
22
-
23
- # The arguments to pass into the method
24
- def args
25
- [object, transition]
26
- end
27
-
28
- # The class of the object being transitioned. Normally the object
29
- # getting passed into observer methods is the actual instance of the
30
- # ActiveModel class. ActiveModel uses that instance's class to check
31
- # for enabled / disabled observers.
32
- #
33
- # Since state_machine is passing an ObserverUpdate instance into observer
34
- # methods, +class+ needs to be overridden so that ActiveModel can still
35
- # get access to the enabled / disabled observers.
36
- def class
37
- object.class
38
- end
39
- end
40
- end
41
- end
42
- end
@@ -1,31 +0,0 @@
1
- module EnumStateMachine
2
- module Integrations #:nodoc:
3
- module ActiveModel
4
- version '2.x' do
5
- def self.active?
6
- !defined?(::ActiveModel::VERSION) || ::ActiveModel::VERSION::MAJOR == 2
7
- end
8
-
9
- def define_validation_hook
10
- define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
11
- def valid?(*)
12
- self.class.state_machines.transitions(self, #{action.inspect}, :after => false).perform { super }
13
- end
14
- end_eval
15
- end
16
- end
17
-
18
- version '3.0.x' do
19
- def self.active?
20
- defined?(::ActiveModel::VERSION) && ::ActiveModel::VERSION::MAJOR == 3 && ::ActiveModel::VERSION::MINOR == 0
21
- end
22
-
23
- def define_validation_hook
24
- # +around+ callbacks don't have direct access to results until AS 3.1
25
- owner_class.set_callback(:validation, :after, 'value', :prepend => true)
26
- super
27
- end
28
- end
29
- end
30
- end
31
- end
@@ -1,548 +0,0 @@
1
- require 'enum_state_machine/integrations/active_model'
2
-
3
- module EnumStateMachine
4
- module Integrations #:nodoc:
5
- # Adds support for integrating state machines with ActiveRecord models.
6
- #
7
- # == Examples
8
- #
9
- # Below is an example of a simple state machine defined within an
10
- # ActiveRecord model:
11
- #
12
- # class Vehicle < ActiveRecord::Base
13
- # state_machine :initial => :parked do
14
- # event :ignite do
15
- # transition :parked => :idling
16
- # end
17
- # end
18
- # end
19
- #
20
- # The examples in the sections below will use the above class as a
21
- # reference.
22
- #
23
- # == Actions
24
- #
25
- # By default, the action that will be invoked when a state is transitioned
26
- # is the +save+ action. This will cause the record to save the changes
27
- # made to the state machine's attribute. *Note* that if any other changes
28
- # were made to the record prior to transition, then those changes will
29
- # be saved as well.
30
- #
31
- # For example,
32
- #
33
- # vehicle = Vehicle.create # => #<Vehicle id: 1, name: nil, state: "parked">
34
- # vehicle.name = 'Ford Explorer'
35
- # vehicle.ignite # => true
36
- # vehicle.reload # => #<Vehicle id: 1, name: "Ford Explorer", state: "idling">
37
- #
38
- # == Events
39
- #
40
- # As described in EnumStateMachine::InstanceMethods#state_machine, event
41
- # attributes are created for every machine that allow transitions to be
42
- # performed automatically when the object's action (in this case, :save)
43
- # is called.
44
- #
45
- # In ActiveRecord, these automated events are run in the following order:
46
- # * before validation - Run before callbacks and persist new states, then validate
47
- # * before save - If validation was skipped, run before callbacks and persist new states, then save
48
- # * after save - Run after callbacks
49
- #
50
- # For example,
51
- #
52
- # vehicle = Vehicle.create # => #<Vehicle id: 1, name: nil, state: "parked">
53
- # vehicle.state_event # => nil
54
- # vehicle.state_event = 'invalid'
55
- # vehicle.valid? # => false
56
- # vehicle.errors.full_messages # => ["State event is invalid"]
57
- #
58
- # vehicle.state_event = 'ignite'
59
- # vehicle.valid? # => true
60
- # vehicle.save # => true
61
- # vehicle.state # => "idling"
62
- # vehicle.state_event # => nil
63
- #
64
- # Note that this can also be done on a mass-assignment basis:
65
- #
66
- # vehicle = Vehicle.create(:state_event => 'ignite') # => #<Vehicle id: 1, name: nil, state: "idling">
67
- # vehicle.state # => "idling"
68
- #
69
- # This technique is always used for transitioning states when the +save+
70
- # action (which is the default) is configured for the machine.
71
- #
72
- # === Security implications
73
- #
74
- # Beware that public event attributes mean that events can be fired
75
- # whenever mass-assignment is being used. If you want to prevent malicious
76
- # users from tampering with events through URLs / forms, the attribute
77
- # should be protected like so:
78
- #
79
- # class Vehicle < ActiveRecord::Base
80
- # attr_protected :state_event
81
- # # attr_accessible ... # Alternative technique
82
- #
83
- # state_machine do
84
- # ...
85
- # end
86
- # end
87
- #
88
- # If you want to only have *some* events be able to fire via mass-assignment,
89
- # you can build two state machines (one public and one protected) like so:
90
- #
91
- # class Vehicle < ActiveRecord::Base
92
- # attr_protected :state_event # Prevent access to events in the first machine
93
- #
94
- # state_machine do
95
- # # Define private events here
96
- # end
97
- #
98
- # # Public machine targets the same state as the private machine
99
- # state_machine :public_state, :attribute => :state do
100
- # # Define public events here
101
- # end
102
- # end
103
- #
104
- # == Transactions
105
- #
106
- # In order to ensure that any changes made during transition callbacks
107
- # are rolled back during a failed attempt, every transition is wrapped
108
- # within a transaction.
109
- #
110
- # For example,
111
- #
112
- # class Message < ActiveRecord::Base
113
- # end
114
- #
115
- # Vehicle.state_machine do
116
- # before_transition do |vehicle, transition|
117
- # Message.create(:content => transition.inspect)
118
- # false
119
- # end
120
- # end
121
- #
122
- # vehicle = Vehicle.create # => #<Vehicle id: 1, name: nil, state: "parked">
123
- # vehicle.ignite # => false
124
- # Message.count # => 0
125
- #
126
- # *Note* that only before callbacks that halt the callback chain and
127
- # failed attempts to save the record will result in the transaction being
128
- # rolled back. If an after callback halts the chain, the previous result
129
- # still applies and the transaction is *not* rolled back.
130
- #
131
- # To turn off transactions:
132
- #
133
- # class Vehicle < ActiveRecord::Base
134
- # state_machine :initial => :parked, :use_transactions => false do
135
- # ...
136
- # end
137
- # end
138
- #
139
- # == Validations
140
- #
141
- # As mentioned in EnumStateMachine::Machine#state, you can define behaviors,
142
- # like validations, that only execute for certain states. One *important*
143
- # caveat here is that, due to a constraint in ActiveRecord's validation
144
- # framework, custom validators will not work as expected when defined to run
145
- # in multiple states. For example:
146
- #
147
- # class Vehicle < ActiveRecord::Base
148
- # state_machine do
149
- # ...
150
- # state :first_gear, :second_gear do
151
- # validate :speed_is_legal
152
- # end
153
- # end
154
- # end
155
- #
156
- # In this case, the <tt>:speed_is_legal</tt> validation will only get run
157
- # for the <tt>:second_gear</tt> state. To avoid this, you can define your
158
- # custom validation like so:
159
- #
160
- # class Vehicle < ActiveRecord::Base
161
- # state_machine do
162
- # ...
163
- # state :first_gear, :second_gear do
164
- # validate {|vehicle| vehicle.speed_is_legal}
165
- # end
166
- # end
167
- # end
168
- #
169
- # == Validation errors
170
- #
171
- # If an event fails to successfully fire because there are no matching
172
- # transitions for the current record, a validation error is added to the
173
- # record's state attribute to help in determining why it failed and for
174
- # reporting via the UI.
175
- #
176
- # For example,
177
- #
178
- # vehicle = Vehicle.create(:state => 'idling') # => #<Vehicle id: 1, name: nil, state: "idling">
179
- # vehicle.ignite # => false
180
- # vehicle.errors.full_messages # => ["State cannot transition via \"ignite\""]
181
- #
182
- # If an event fails to fire because of a validation error on the record and
183
- # *not* because a matching transition was not available, no error messages
184
- # will be added to the state attribute.
185
- #
186
- # In addition, if you're using the <tt>ignite!</tt> version of the event,
187
- # then the failure reason (such as the current validation errors) will be
188
- # included in the exception that gets raised when the event fails. For
189
- # example, assuming there's a validation on a field called +name+ on the class:
190
- #
191
- # vehicle = Vehicle.new
192
- # vehicle.ignite! # => EnumStateMachine::InvalidTransition: Cannot transition state via :ignite from :parked (Reason(s): Name cannot be blank)
193
- #
194
- # == Scopes
195
- #
196
- # To assist in filtering models with specific states, a series of named
197
- # scopes are defined on the model for finding records with or without a
198
- # particular set of states.
199
- #
200
- # These named scopes are essentially the functional equivalent of the
201
- # following definitions:
202
- #
203
- # class Vehicle < ActiveRecord::Base
204
- # named_scope :with_states, lambda {|*states| {:conditions => {:state => states}}}
205
- # # with_states also aliased to with_state
206
- #
207
- # named_scope :without_states, lambda {|*states| {:conditions => ['state NOT IN (?)', states]}}
208
- # # without_states also aliased to without_state
209
- # end
210
- #
211
- # *Note*, however, that the states are converted to their stored values
212
- # before being passed into the query.
213
- #
214
- # Because of the way named scopes work in ActiveRecord, they can be
215
- # chained like so:
216
- #
217
- # Vehicle.with_state(:parked).all(:order => 'id DESC')
218
- #
219
- # Note that states can also be referenced by the string version of their
220
- # name:
221
- #
222
- # Vehicle.with_state('parked')
223
- #
224
- # == Callbacks
225
- #
226
- # All before/after transition callbacks defined for ActiveRecord models
227
- # behave in the same way that other ActiveRecord callbacks behave. The
228
- # object involved in the transition is passed in as an argument.
229
- #
230
- # For example,
231
- #
232
- # class Vehicle < ActiveRecord::Base
233
- # state_machine :initial => :parked do
234
- # before_transition any => :idling do |vehicle|
235
- # vehicle.put_on_seatbelt
236
- # end
237
- #
238
- # before_transition do |vehicle, transition|
239
- # # log message
240
- # end
241
- #
242
- # event :ignite do
243
- # transition :parked => :idling
244
- # end
245
- # end
246
- #
247
- # def put_on_seatbelt
248
- # ...
249
- # end
250
- # end
251
- #
252
- # Note, also, that the transition can be accessed by simply defining
253
- # additional arguments in the callback block.
254
- #
255
- # === Failure callbacks
256
- #
257
- # +after_failure+ callbacks allow you to execute behaviors when a transition
258
- # is allowed, but fails to save. This could be useful for something like
259
- # auditing transition attempts. Since callbacks run within transactions in
260
- # ActiveRecord, a save failure will cause any records that get created in
261
- # your callback to roll back. You can work around this issue like so:
262
- #
263
- # class TransitionLog < ActiveRecord::Base
264
- # establish_connection Rails.env.to_sym
265
- # end
266
- #
267
- # class Vehicle < ActiveRecord::Base
268
- # state_machine do
269
- # after_failure do |vehicle, transition|
270
- # TransitionLog.create(:vehicle => vehicle, :transition => transition)
271
- # end
272
- #
273
- # ...
274
- # end
275
- # end
276
- #
277
- # The +TransitionLog+ model establishes a second connection to the database
278
- # that allows new records to be saved without being affected by rollbacks
279
- # in the +Vehicle+ model's transaction.
280
- #
281
- # === Callback Order
282
- #
283
- # Callbacks occur in the following order. Callbacks specific to state_machine
284
- # are bolded. The remaining callbacks are part of ActiveRecord.
285
- #
286
- # * (-) save
287
- # * (-) begin transaction (if enabled)
288
- # * (1) *before_transition*
289
- # * (-) valid
290
- # * (2) before_validation
291
- # * (-) validate
292
- # * (3) after_validation
293
- # * (4) before_save
294
- # * (5) before_create
295
- # * (-) create
296
- # * (6) after_create
297
- # * (7) after_save
298
- # * (8) *after_transition*
299
- # * (-) end transaction (if enabled)
300
- # * (9) after_commit
301
- #
302
- # == Observers
303
- #
304
- # In addition to support for ActiveRecord-like hooks, there is additional
305
- # support for ActiveRecord observers. Because of the way ActiveRecord
306
- # observers are designed, there is less flexibility around the specific
307
- # transitions that can be hooked in. However, a large number of hooks
308
- # *are* supported. For example, if a transition for a record's +state+
309
- # attribute changes the state from +parked+ to +idling+ via the +ignite+
310
- # event, the following observer methods are supported:
311
- # * before/after/after_failure_to-_ignite_from_parked_to_idling
312
- # * before/after/after_failure_to-_ignite_from_parked
313
- # * before/after/after_failure_to-_ignite_to_idling
314
- # * before/after/after_failure_to-_ignite
315
- # * before/after/after_failure_to-_transition_state_from_parked_to_idling
316
- # * before/after/after_failure_to-_transition_state_from_parked
317
- # * before/after/after_failure_to-_transition_state_to_idling
318
- # * before/after/after_failure_to-_transition_state
319
- # * before/after/after_failure_to-_transition
320
- #
321
- # The following class shows an example of some of these hooks:
322
- #
323
- # class VehicleObserver < ActiveRecord::Observer
324
- # def before_save(vehicle)
325
- # # log message
326
- # end
327
- #
328
- # # Callback for :ignite event *before* the transition is performed
329
- # def before_ignite(vehicle, transition)
330
- # # log message
331
- # end
332
- #
333
- # # Callback for :ignite event *after* the transition has been performed
334
- # def after_ignite(vehicle, transition)
335
- # # put on seatbelt
336
- # end
337
- #
338
- # # Generic transition callback *before* the transition is performed
339
- # def after_transition(vehicle, transition)
340
- # Audit.log(vehicle, transition)
341
- # end
342
- # end
343
- #
344
- # More flexible transition callbacks can be defined directly within the
345
- # model as described in EnumStateMachine::Machine#before_transition
346
- # and EnumStateMachine::Machine#after_transition.
347
- #
348
- # To define a single observer for multiple state machines:
349
- #
350
- # class EnumStateMachineObserver < ActiveRecord::Observer
351
- # observe Vehicle, Switch, Project
352
- #
353
- # def after_transition(record, transition)
354
- # Audit.log(record, transition)
355
- # end
356
- # end
357
- #
358
- # == Internationalization
359
- #
360
- # In Rails 2.2+, any error message that is generated from performing invalid
361
- # transitions can be localized. The following default translations are used:
362
- #
363
- # en:
364
- # activerecord:
365
- # errors:
366
- # messages:
367
- # invalid: "is invalid"
368
- # # %{value} = attribute value, %{state} = Human state name
369
- # invalid_event: "cannot transition when %{state}"
370
- # # %{value} = attribute value, %{event} = Human event name, %{state} = Human current state name
371
- # invalid_transition: "cannot transition via %{event}"
372
- #
373
- # Notice that the interpolation syntax is %{key} in Rails 3+. In Rails 2.x,
374
- # the appropriate syntax is {{key}}.
375
- #
376
- # You can override these for a specific model like so:
377
- #
378
- # en:
379
- # activerecord:
380
- # errors:
381
- # models:
382
- # user:
383
- # invalid: "is not valid"
384
- #
385
- # In addition to the above, you can also provide translations for the
386
- # various states / events in each state machine. Using the Vehicle example,
387
- # state translations will be looked for using the following keys, where
388
- # +model_name+ = "vehicle", +machine_name+ = "state" and +state_name+ = "parked":
389
- # * <tt>activerecord.state_machines.#{model_name}.#{machine_name}.states.#{state_name}</tt>
390
- # * <tt>activerecord.state_machines.#{model_name}.states.#{state_name}</tt>
391
- # * <tt>activerecord.state_machines.#{machine_name}.states.#{state_name}</tt>
392
- # * <tt>activerecord.state_machines.states.#{state_name}</tt>
393
- #
394
- # Event translations will be looked for using the following keys, where
395
- # +model_name+ = "vehicle", +machine_name+ = "state" and +event_name+ = "ignite":
396
- # * <tt>activerecord.state_machines.#{model_name}.#{machine_name}.events.#{event_name}</tt>
397
- # * <tt>activerecord.state_machines.#{model_name}.events.#{event_name}</tt>
398
- # * <tt>activerecord.state_machines.#{machine_name}.events.#{event_name}</tt>
399
- # * <tt>activerecord.state_machines.events.#{event_name}</tt>
400
- #
401
- # An example translation configuration might look like so:
402
- #
403
- # es:
404
- # activerecord:
405
- # state_machines:
406
- # states:
407
- # parked: 'estacionado'
408
- # events:
409
- # park: 'estacionarse'
410
- module ActiveRecord
411
- include Base
412
- include ActiveModel
413
-
414
- require 'enum_state_machine/integrations/active_record/versions'
415
-
416
- # The default options to use for state machines using this integration
417
- @defaults = {:action => :save}
418
-
419
- # Classes that inherit from ActiveRecord::Base will automatically use
420
- # the ActiveRecord integration.
421
- def self.matching_ancestors
422
- %w(ActiveRecord::Base)
423
- end
424
-
425
- def self.extended(base) #:nodoc:
426
- require 'active_record/version'
427
- super
428
- end
429
-
430
- protected
431
- # Only runs validations on the action if using <tt>:save</tt>
432
- def runs_validations_on_action?
433
- action == :save
434
- end
435
-
436
- # Gets the db default for the machine's attribute
437
- def owner_class_attribute_default
438
- if owner_class.connected? && owner_class.table_exists? && column = owner_class.columns_hash[attribute.to_s]
439
- column.default
440
- end
441
- end
442
-
443
- # Defines an initialization hook into the owner class for setting the
444
- # initial state of the machine *before* any attributes are set on the
445
- # object
446
- def define_state_initializer
447
- define_static_state_initializer
448
- define_dynamic_state_initializer
449
- end
450
-
451
- # Initializes static states
452
- def define_static_state_initializer
453
- # This is the only available hook where the default set of attributes
454
- # can be overridden for a new object *prior* to the processing of the
455
- # attributes passed into #initialize
456
- define_helper :class, <<-end_eval, __FILE__, __LINE__ + 1
457
- def column_defaults(*) #:nodoc:
458
- result = super
459
- # No need to pass in an object, since the overrides will be forced
460
- self.state_machines.initialize_states(nil, :static => :force, :dynamic => false, :to => result)
461
- result
462
- end
463
- end_eval
464
- end
465
-
466
- # Initializes dynamic states
467
- def define_dynamic_state_initializer
468
- define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
469
- def initialize(*)
470
- super do |*args|
471
- self.class.state_machines.initialize_states(self, :static => false)
472
- yield(*args) if block_given?
473
- end
474
- end
475
- end_eval
476
- end
477
-
478
- # Uses around callbacks to run state events if using the :save hook
479
- def define_action_hook
480
- if action_hook == :save
481
- define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
482
- def save(*)
483
- self.class.state_machine(#{name.inspect}).send(:around_save, self) { super }
484
- end
485
-
486
- def save!(*)
487
- self.class.state_machine(#{name.inspect}).send(:around_save, self) { super } || raise(ActiveRecord::RecordInvalid.new(self))
488
- end
489
-
490
- def changed_for_autosave?
491
- super || self.class.state_machines.any? {|name, machine| machine.action == :save && machine.read(self, :event)}
492
- end
493
- end_eval
494
- else
495
- super
496
- end
497
- end
498
-
499
- # Runs state events around the machine's :save action
500
- def around_save(object)
501
- transaction(object) do
502
- object.class.state_machines.transitions(object, action).perform { yield }
503
- end
504
- end
505
-
506
- # Creates a scope for finding records *with* a particular state or
507
- # states for the attribute
508
- def create_with_scope(name)
509
- create_scope(name, lambda {|values| ["#{attribute_column} IN (?)", values]})
510
- end
511
-
512
- # Creates a scope for finding records *without* a particular state or
513
- # states for the attribute
514
- def create_without_scope(name)
515
- create_scope(name, lambda {|values| ["#{attribute_column} NOT IN (?)", values]})
516
- end
517
-
518
- # Generates the fully-qualifed column name for this machine's attribute
519
- def attribute_column
520
- connection = owner_class.connection
521
- "#{connection.quote_table_name(owner_class.table_name)}.#{connection.quote_column_name(attribute)}"
522
- end
523
-
524
- # Runs a new database transaction, rolling back any changes by raising
525
- # an ActiveRecord::Rollback exception if the yielded block fails
526
- # (i.e. returns false).
527
- def transaction(object)
528
- result = nil
529
- object.class.transaction do
530
- raise ::ActiveRecord::Rollback unless result = yield
531
- end
532
- result
533
- end
534
-
535
- # Defines a new named scope with the given name
536
- def create_scope(name, scope)
537
- lambda {|model, values| model.where(scope.call(values))}
538
- end
539
-
540
- # ActiveModel's use of method_missing / respond_to for attribute methods
541
- # breaks both ancestor lookups and defined?(super). Need to special-case
542
- # the existence of query attribute methods.
543
- def owner_class_ancestor_has_method?(scope, method)
544
- scope == :instance && method == "#{attribute}?" ? owner_class : super
545
- end
546
- end
547
- end
548
- end