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,11 @@
1
+ {:en => {
2
+ :activemodel => {
3
+ :errors => {
4
+ :messages => {
5
+ :invalid => StateMachine::Machine.default_messages[:invalid],
6
+ :invalid_event => StateMachine::Machine.default_messages[:invalid_event] % ['%{state}'],
7
+ :invalid_transition => StateMachine::Machine.default_messages[:invalid_transition] % ['%{event}']
8
+ }
9
+ }
10
+ }
11
+ }}
@@ -0,0 +1,45 @@
1
+ module StateMachine
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 self.included(base) #:nodoc:
23
+ base.class_eval do
24
+ alias_method :update_without_multiple_args, :update
25
+ alias_method :update, :update_with_multiple_args
26
+ end
27
+ end
28
+
29
+ # Allows additional arguments other than the object to be passed to the
30
+ # observed methods
31
+ def update_with_multiple_args(observed_method, object, *args) #:nodoc:
32
+ if args.any?
33
+ send(observed_method, object, *args) if respond_to?(observed_method)
34
+ else
35
+ update_without_multiple_args(observed_method, object)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ ActiveModel::Observer.class_eval do
44
+ include StateMachine::Integrations::ActiveModel::Observer
45
+ end if defined?(ActiveModel::Observer)
@@ -0,0 +1,31 @@
1
+ module StateMachine
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
@@ -0,0 +1,424 @@
1
+ require 'state_machine/integrations/active_model'
2
+
3
+ module StateMachine
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 StateMachine::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
+ # If using the +save+ action for the machine, this option will be ignored as
140
+ # the transaction will be created by ActiveRecord within +save+.
141
+ #
142
+ # == Validation errors
143
+ #
144
+ # If an event fails to successfully fire because there are no matching
145
+ # transitions for the current record, a validation error is added to the
146
+ # record's state attribute to help in determining why it failed and for
147
+ # reporting via the UI.
148
+ #
149
+ # For example,
150
+ #
151
+ # vehicle = Vehicle.create(:state => 'idling') # => #<Vehicle id: 1, name: nil, state: "idling">
152
+ # vehicle.ignite # => false
153
+ # vehicle.errors.full_messages # => ["State cannot transition via \"ignite\""]
154
+ #
155
+ # If an event fails to fire because of a validation error on the record and
156
+ # *not* because a matching transition was not available, no error messages
157
+ # will be added to the state attribute.
158
+ #
159
+ # == Scopes
160
+ #
161
+ # To assist in filtering models with specific states, a series of named
162
+ # scopes are defined on the model for finding records with or without a
163
+ # particular set of states.
164
+ #
165
+ # These named scopes are essentially the functional equivalent of the
166
+ # following definitions:
167
+ #
168
+ # class Vehicle < ActiveRecord::Base
169
+ # named_scope :with_states, lambda {|*states| {:conditions => {:state => states}}}
170
+ # # with_states also aliased to with_state
171
+ #
172
+ # named_scope :without_states, lambda {|*states| {:conditions => ['state NOT IN (?)', states]}}
173
+ # # without_states also aliased to without_state
174
+ # end
175
+ #
176
+ # *Note*, however, that the states are converted to their stored values
177
+ # before being passed into the query.
178
+ #
179
+ # Because of the way named scopes work in ActiveRecord, they can be
180
+ # chained like so:
181
+ #
182
+ # Vehicle.with_state(:parked).all(:order => 'id DESC')
183
+ #
184
+ # == Callbacks
185
+ #
186
+ # All before/after transition callbacks defined for ActiveRecord models
187
+ # behave in the same way that other ActiveRecord callbacks behave. The
188
+ # object involved in the transition is passed in as an argument.
189
+ #
190
+ # For example,
191
+ #
192
+ # class Vehicle < ActiveRecord::Base
193
+ # state_machine :initial => :parked do
194
+ # before_transition any => :idling do |vehicle|
195
+ # vehicle.put_on_seatbelt
196
+ # end
197
+ #
198
+ # before_transition do |vehicle, transition|
199
+ # # log message
200
+ # end
201
+ #
202
+ # event :ignite do
203
+ # transition :parked => :idling
204
+ # end
205
+ # end
206
+ #
207
+ # def put_on_seatbelt
208
+ # ...
209
+ # end
210
+ # end
211
+ #
212
+ # Note, also, that the transition can be accessed by simply defining
213
+ # additional arguments in the callback block.
214
+ #
215
+ # == Observers
216
+ #
217
+ # In addition to support for ActiveRecord-like hooks, there is additional
218
+ # support for ActiveRecord observers. Because of the way ActiveRecord
219
+ # observers are designed, there is less flexibility around the specific
220
+ # transitions that can be hooked in. However, a large number of hooks
221
+ # *are* supported. For example, if a transition for a record's +state+
222
+ # attribute changes the state from +parked+ to +idling+ via the +ignite+
223
+ # event, the following observer methods are supported:
224
+ # * before/after/after_failure_to-_ignite_from_parked_to_idling
225
+ # * before/after/after_failure_to-_ignite_from_parked
226
+ # * before/after/after_failure_to-_ignite_to_idling
227
+ # * before/after/after_failure_to-_ignite
228
+ # * before/after/after_failure_to-_transition_state_from_parked_to_idling
229
+ # * before/after/after_failure_to-_transition_state_from_parked
230
+ # * before/after/after_failure_to-_transition_state_to_idling
231
+ # * before/after/after_failure_to-_transition_state
232
+ # * before/after/after_failure_to-_transition
233
+ #
234
+ # The following class shows an example of some of these hooks:
235
+ #
236
+ # class VehicleObserver < ActiveRecord::Observer
237
+ # def before_save(vehicle)
238
+ # # log message
239
+ # end
240
+ #
241
+ # # Callback for :ignite event *before* the transition is performed
242
+ # def before_ignite(vehicle, transition)
243
+ # # log message
244
+ # end
245
+ #
246
+ # # Callback for :ignite event *after* the transition has been performed
247
+ # def after_ignite(vehicle, transition)
248
+ # # put on seatbelt
249
+ # end
250
+ #
251
+ # # Generic transition callback *before* the transition is performed
252
+ # def after_transition(vehicle, transition)
253
+ # Audit.log(vehicle, transition)
254
+ # end
255
+ # end
256
+ #
257
+ # More flexible transition callbacks can be defined directly within the
258
+ # model as described in StateMachine::Machine#before_transition
259
+ # and StateMachine::Machine#after_transition.
260
+ #
261
+ # To define a single observer for multiple state machines:
262
+ #
263
+ # class StateMachineObserver < ActiveRecord::Observer
264
+ # observe Vehicle, Switch, Project
265
+ #
266
+ # def after_transition(record, transition)
267
+ # Audit.log(record, transition)
268
+ # end
269
+ # end
270
+ #
271
+ # == Internationalization
272
+ #
273
+ # In Rails 2.2+, any error message that is generated from performing invalid
274
+ # transitions can be localized. The following default translations are used:
275
+ #
276
+ # en:
277
+ # activerecord:
278
+ # errors:
279
+ # messages:
280
+ # invalid: "is invalid"
281
+ # invalid_event: "cannot transition when %{state}"
282
+ # invalid_transition: "cannot transition via %{event}"
283
+ #
284
+ # Notice that the interpolation syntax is %{key} in Rails 3+. In Rails 2.x,
285
+ # the appropriate syntax is {{key}}.
286
+ #
287
+ # You can override these for a specific model like so:
288
+ #
289
+ # en:
290
+ # activerecord:
291
+ # errors:
292
+ # models:
293
+ # user:
294
+ # invalid: "is not valid"
295
+ #
296
+ # In addition to the above, you can also provide translations for the
297
+ # various states / events in each state machine. Using the Vehicle example,
298
+ # state translations will be looked for using the following keys:
299
+ # * <tt>activerecord.state_machines.vehicle.state.states.parked</tt>
300
+ # * <tt>activerecord.state_machines.state.states.parked
301
+ # * <tt>activerecord.state_machines.states.parked</tt>
302
+ #
303
+ # Event translations will be looked for using the following keys:
304
+ # * <tt>activerecord.state_machines.vehicle.state.events.ignite</tt>
305
+ # * <tt>activerecord.state_machines.state.events.ignite
306
+ # * <tt>activerecord.state_machines.events.ignite</tt>
307
+ #
308
+ # An example translation configuration might look like so:
309
+ #
310
+ # es:
311
+ # activerecord:
312
+ # state_machines:
313
+ # states:
314
+ # parked: 'estacionado'
315
+ # events:
316
+ # park: 'estacionarse'
317
+ module ActiveRecord
318
+ include Base
319
+ include ActiveModel
320
+
321
+ require 'state_machine/integrations/active_record/versions'
322
+
323
+ # The default options to use for state machines using this integration
324
+ @defaults = {:action => :save}
325
+
326
+ # Whether this integration is available. Only true if ActiveRecord::Base
327
+ # is defined.
328
+ def self.available?
329
+ defined?(::ActiveRecord::Base)
330
+ end
331
+
332
+ # Should this integration be used for state machines in the given class?
333
+ # Classes that inherit from ActiveRecord::Base will automatically use
334
+ # the ActiveRecord integration.
335
+ def self.matches?(klass)
336
+ klass <= ::ActiveRecord::Base
337
+ end
338
+
339
+ def self.extended(base) #:nodoc:
340
+ require 'active_record/version'
341
+ super
342
+ end
343
+
344
+ protected
345
+ # Only runs validations on the action if using <tt>:save</tt>
346
+ def runs_validations_on_action?
347
+ action == :save
348
+ end
349
+
350
+ # Defines an initialization hook into the owner class for setting the
351
+ # initial state of the machine *before* any attributes are set on the
352
+ # object
353
+ def define_state_initializer
354
+ define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
355
+ # Initializes dynamic states
356
+ def initialize(*)
357
+ super do |*args|
358
+ self.class.state_machines.initialize_states(self, :static => false)
359
+ yield(*args) if block_given?
360
+ end
361
+ end
362
+
363
+ # Initializes static states
364
+ def attributes_from_column_definition(*)
365
+ result = super
366
+ self.class.state_machines.initialize_states(self, :dynamic => false, :to => result)
367
+ result
368
+ end
369
+ end_eval
370
+ end
371
+
372
+ # Uses around callbacks to run state events if using the :save hook
373
+ def define_action_hook
374
+ if action_hook == :save
375
+ owner_class.set_callback(:save, :around, self, :prepend => true)
376
+ else
377
+ super
378
+ end
379
+ end
380
+
381
+ # Runs state events around the machine's :save action
382
+ def around_save(object)
383
+ object.class.state_machines.transitions(object, action).perform { yield }
384
+ end
385
+
386
+ # Creates a scope for finding records *with* a particular state or
387
+ # states for the attribute
388
+ def create_with_scope(name)
389
+ create_scope(name, lambda {|values| ["#{attribute_column} IN (?)", values]})
390
+ end
391
+
392
+ # Creates a scope for finding records *without* a particular state or
393
+ # states for the attribute
394
+ def create_without_scope(name)
395
+ create_scope(name, lambda {|values| ["#{attribute_column} NOT IN (?)", values]})
396
+ end
397
+
398
+ # Generates the fully-qualifed column name for this machine's attribute
399
+ def attribute_column
400
+ connection = owner_class.connection
401
+ "#{connection.quote_table_name(owner_class.table_name)}.#{connection.quote_column_name(attribute)}"
402
+ end
403
+
404
+ # Runs a new database transaction, rolling back any changes by raising
405
+ # an ActiveRecord::Rollback exception if the yielded block fails
406
+ # (i.e. returns false).
407
+ def transaction(object)
408
+ object.class.transaction {raise ::ActiveRecord::Rollback unless yield}
409
+ end
410
+
411
+ # Defines a new named scope with the given name
412
+ def create_scope(name, scope)
413
+ lambda {|model, values| model.where(scope.call(values))}
414
+ end
415
+
416
+ # ActiveModel's use of method_missing / respond_to for attribute methods
417
+ # breaks both ancestor lookups and defined?(super). Need to special-case
418
+ # the existence of query attribute methods.
419
+ def owner_class_ancestor_has_method?(scope, method)
420
+ scope == :instance && method == "#{name}?" || super
421
+ end
422
+ end
423
+ end
424
+ end