state_machine 0.9.4 → 0.10.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.
- data/CHANGELOG.rdoc +20 -0
- data/LICENSE +1 -1
- data/README.rdoc +74 -4
- data/Rakefile +3 -3
- data/lib/state_machine.rb +51 -24
- data/lib/state_machine/{guard.rb → branch.rb} +34 -40
- data/lib/state_machine/callback.rb +13 -18
- data/lib/state_machine/error.rb +13 -0
- data/lib/state_machine/eval_helpers.rb +3 -0
- data/lib/state_machine/event.rb +67 -30
- data/lib/state_machine/event_collection.rb +20 -3
- data/lib/state_machine/extensions.rb +3 -3
- data/lib/state_machine/integrations.rb +7 -0
- data/lib/state_machine/integrations/active_model.rb +149 -59
- data/lib/state_machine/integrations/active_model/versions.rb +30 -0
- data/lib/state_machine/integrations/active_record.rb +74 -148
- data/lib/state_machine/integrations/active_record/locale.rb +0 -7
- data/lib/state_machine/integrations/active_record/versions.rb +149 -0
- data/lib/state_machine/integrations/base.rb +64 -0
- data/lib/state_machine/integrations/data_mapper.rb +50 -39
- data/lib/state_machine/integrations/data_mapper/observer.rb +47 -12
- data/lib/state_machine/integrations/data_mapper/versions.rb +62 -0
- data/lib/state_machine/integrations/mongo_mapper.rb +37 -64
- data/lib/state_machine/integrations/mongo_mapper/locale.rb +4 -0
- data/lib/state_machine/integrations/mongo_mapper/versions.rb +102 -0
- data/lib/state_machine/integrations/mongoid.rb +297 -0
- data/lib/state_machine/integrations/mongoid/locale.rb +4 -0
- data/lib/state_machine/integrations/mongoid/versions.rb +18 -0
- data/lib/state_machine/integrations/sequel.rb +99 -55
- data/lib/state_machine/integrations/sequel/versions.rb +40 -0
- data/lib/state_machine/machine.rb +273 -136
- data/lib/state_machine/machine_collection.rb +21 -13
- data/lib/state_machine/node_collection.rb +6 -1
- data/lib/state_machine/path.rb +120 -0
- data/lib/state_machine/path_collection.rb +90 -0
- data/lib/state_machine/state.rb +28 -9
- data/lib/state_machine/state_collection.rb +1 -1
- data/lib/state_machine/transition.rb +65 -6
- data/lib/state_machine/transition_collection.rb +1 -1
- data/test/files/en.yml +8 -0
- data/test/functional/state_machine_test.rb +15 -2
- data/test/unit/branch_test.rb +890 -0
- data/test/unit/callback_test.rb +9 -36
- data/test/unit/error_test.rb +43 -0
- data/test/unit/event_collection_test.rb +67 -33
- data/test/unit/event_test.rb +165 -38
- data/test/unit/integrations/active_model_test.rb +103 -3
- data/test/unit/integrations/active_record_test.rb +90 -43
- data/test/unit/integrations/base_test.rb +87 -0
- data/test/unit/integrations/data_mapper_test.rb +105 -44
- data/test/unit/integrations/mongo_mapper_test.rb +261 -64
- data/test/unit/integrations/mongoid_test.rb +1529 -0
- data/test/unit/integrations/sequel_test.rb +33 -49
- data/test/unit/integrations_test.rb +4 -0
- data/test/unit/invalid_event_test.rb +15 -2
- data/test/unit/invalid_parallel_transition_test.rb +18 -0
- data/test/unit/invalid_transition_test.rb +72 -2
- data/test/unit/machine_collection_test.rb +55 -61
- data/test/unit/machine_test.rb +388 -26
- data/test/unit/node_collection_test.rb +14 -4
- data/test/unit/path_collection_test.rb +266 -0
- data/test/unit/path_test.rb +485 -0
- data/test/unit/state_collection_test.rb +30 -0
- data/test/unit/state_test.rb +82 -35
- data/test/unit/transition_collection_test.rb +48 -44
- data/test/unit/transition_test.rb +198 -41
- metadata +111 -74
- data/test/unit/guard_test.rb +0 -909
@@ -0,0 +1,64 @@
|
|
1
|
+
module StateMachine
|
2
|
+
module Integrations
|
3
|
+
# Provides a set of base helpers for managing individual integrations
|
4
|
+
module Base
|
5
|
+
# Never matches
|
6
|
+
def self.matches?(klass)
|
7
|
+
false
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.included(base) #:nodoc:
|
11
|
+
base.class_eval do
|
12
|
+
extend ClassMethods
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
# The default options to use for state machines using this integration
|
18
|
+
attr_reader :defaults
|
19
|
+
|
20
|
+
# Tracks the various version overrides for an integration
|
21
|
+
def versions
|
22
|
+
@versions ||= []
|
23
|
+
end
|
24
|
+
|
25
|
+
# Creates a new version override for an integration. When this
|
26
|
+
# integration is activated, each version that is marked as active will
|
27
|
+
# also extend the integration.
|
28
|
+
#
|
29
|
+
# == Example
|
30
|
+
#
|
31
|
+
# module StateMachine
|
32
|
+
# module Integrations
|
33
|
+
# module ORMLibrary
|
34
|
+
# version '0.2.x - 0.3.x' do
|
35
|
+
# def self.active?
|
36
|
+
# ::ORMLibrary::VERSION >= '0.2.0' && ::ORMLibrary::VERSION < '0.4.0'
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# def invalidate(object, attribute, message, values = [])
|
40
|
+
# # Override here...
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# In the above example, a version override is defined for the ORMLibrary
|
48
|
+
# integration when the version is between 0.2.x and 0.3.x.
|
49
|
+
def version(name, &block)
|
50
|
+
versions << mod = Module.new(&block)
|
51
|
+
mod
|
52
|
+
end
|
53
|
+
|
54
|
+
# Extends the given object with any version overrides that are currently
|
55
|
+
# active
|
56
|
+
def extended(base)
|
57
|
+
versions.each do |version|
|
58
|
+
base.extend(version) if version.active?
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -241,6 +241,10 @@ module StateMachine
|
|
241
241
|
# support for DataMapper observers. See StateMachine::Integrations::DataMapper::Observer
|
242
242
|
# for more information.
|
243
243
|
module DataMapper
|
244
|
+
include Base
|
245
|
+
|
246
|
+
require 'state_machine/integrations/data_mapper/versions'
|
247
|
+
|
244
248
|
# The default options to use for state machines using this integration
|
245
249
|
class << self; attr_reader :defaults; end
|
246
250
|
@defaults = {:action => :save, :use_transactions => false}
|
@@ -255,28 +259,17 @@ module StateMachine
|
|
255
259
|
# Loads additional files specific to DataMapper
|
256
260
|
def self.extended(base) #:nodoc:
|
257
261
|
require 'dm-core/version' unless ::DataMapper.const_defined?('VERSION')
|
258
|
-
|
262
|
+
super
|
259
263
|
end
|
260
264
|
|
261
265
|
# Forces the change in state to be recognized regardless of whether the
|
262
266
|
# state value actually changed
|
263
|
-
def write(object, attribute, value)
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
object.original_values[self.attribute] = "#{value}-ignored" if object.original_values[self.attribute] == value
|
270
|
-
elsif ::DataMapper::VERSION =~ /^0\.10\./
|
271
|
-
property = owner_class.properties[self.attribute]
|
272
|
-
object.original_attributes[property] = "#{value}-ignored" unless object.original_attributes.include?(property)
|
273
|
-
else
|
274
|
-
object.persisted_state = ::DataMapper::Resource::State::Dirty.new(object) if object.persisted_state.is_a?(::DataMapper::Resource::State::Clean)
|
275
|
-
property = owner_class.properties[self.attribute]
|
276
|
-
object.persisted_state.original_attributes[property] = value unless object.persisted_state.original_attributes.include?(property)
|
277
|
-
end
|
278
|
-
else
|
279
|
-
result = super
|
267
|
+
def write(object, attribute, value, *args)
|
268
|
+
result = super
|
269
|
+
|
270
|
+
if attribute == :state || attribute == :event && value
|
271
|
+
value = read(object, :state) if attribute == :event
|
272
|
+
mark_dirty(object, value)
|
280
273
|
end
|
281
274
|
|
282
275
|
result
|
@@ -293,6 +286,16 @@ module StateMachine
|
|
293
286
|
end
|
294
287
|
|
295
288
|
protected
|
289
|
+
# Initializes class-level extensions and defaults for this machine
|
290
|
+
def after_initialize
|
291
|
+
load_observer_extensions
|
292
|
+
end
|
293
|
+
|
294
|
+
# Loads extensions to DataMapper's Observers
|
295
|
+
def load_observer_extensions
|
296
|
+
require 'state_machine/integrations/data_mapper/observer' if ::DataMapper.const_defined?('Observer')
|
297
|
+
end
|
298
|
+
|
296
299
|
# Is validation support currently loaded?
|
297
300
|
def supports_validations?
|
298
301
|
@supports_validations ||= ::DataMapper.const_defined?('Validate')
|
@@ -300,21 +303,24 @@ module StateMachine
|
|
300
303
|
|
301
304
|
# Pluralizes the name using the built-in inflector
|
302
305
|
def pluralize(word)
|
303
|
-
|
306
|
+
::DataMapper::Inflector.pluralize(word.to_s)
|
307
|
+
end
|
308
|
+
|
309
|
+
# Only allows state initialization on new records that aren't being
|
310
|
+
# created with a set of attributes that includes this machine's
|
311
|
+
# attribute.
|
312
|
+
def initialize_state?(object, options)
|
313
|
+
ignore = (options[:attributes] || {}).keys
|
314
|
+
!ignore.map {|attribute| attribute.to_sym}.include?(attribute)
|
304
315
|
end
|
305
316
|
|
306
317
|
# Defines an initialization hook into the owner class for setting the
|
307
318
|
# initial state of the machine *before* any attributes are set on the
|
308
319
|
# object
|
309
320
|
def define_state_initializer
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
initialize_state_machines(:dynamic => false, :ignore => ignore)
|
314
|
-
super
|
315
|
-
initialize_state_machines(:dynamic => true, :ignore => ignore)
|
316
|
-
end
|
317
|
-
end_eval
|
321
|
+
define_helper(:instance, :initialize) do |machine, object, _super, *args|
|
322
|
+
object.class.state_machines.initialize_states(object, :attributes => args.first) { _super.call }
|
323
|
+
end
|
318
324
|
end
|
319
325
|
|
320
326
|
# Skips defining reader/writer methods since this is done automatically
|
@@ -332,23 +338,20 @@ module StateMachine
|
|
332
338
|
|
333
339
|
# Adds hooks into validation for automatically firing events
|
334
340
|
def define_action_helpers
|
335
|
-
|
336
|
-
# enabled because of the way dm-validations integrates
|
337
|
-
return if ::DataMapper::VERSION =~ /^0\.9\.[4-6]/ && supports_validations?
|
341
|
+
super
|
338
342
|
|
339
|
-
if action == :save
|
340
|
-
|
341
|
-
|
342
|
-
define_method(:valid?) do |*args|
|
343
|
-
self.class.state_machines.transitions(self, :save, :after => false).perform { super(*args) }
|
344
|
-
end
|
345
|
-
end
|
343
|
+
if action == :save && supports_validations?
|
344
|
+
define_helper(:instance, :valid?) do |machine, object, _super, *|
|
345
|
+
object.class.state_machines.transitions(object, :save, :after => false).perform { _super.call }
|
346
346
|
end
|
347
|
-
else
|
348
|
-
super
|
349
347
|
end
|
350
348
|
end
|
351
349
|
|
350
|
+
# Uses internal save hooks if using the :save action
|
351
|
+
def action_hook
|
352
|
+
action == :save ? :save_self : super
|
353
|
+
end
|
354
|
+
|
352
355
|
# Creates a scope for finding records *with* a particular state or
|
353
356
|
# states for the attribute
|
354
357
|
def create_with_scope(name)
|
@@ -374,6 +377,14 @@ module StateMachine
|
|
374
377
|
options[:bind_to_object] = true
|
375
378
|
super
|
376
379
|
end
|
380
|
+
|
381
|
+
# Marks the object's state as dirty so that the record will be saved
|
382
|
+
# even if no actual modifications have been made to the data
|
383
|
+
def mark_dirty(object, value)
|
384
|
+
object.persisted_state = ::DataMapper::Resource::State::Dirty.new(object) if object.persisted_state.is_a?(::DataMapper::Resource::State::Clean)
|
385
|
+
property = owner_class.properties[self.attribute]
|
386
|
+
object.persisted_state.original_attributes[property] = value unless object.persisted_state.original_attributes.include?(property)
|
387
|
+
end
|
377
388
|
end
|
378
389
|
end
|
379
390
|
end
|
@@ -1,15 +1,15 @@
|
|
1
1
|
module StateMachine
|
2
2
|
module Integrations #:nodoc:
|
3
3
|
module DataMapper
|
4
|
-
# Adds support for creating before/after transition
|
5
|
-
# DataMapper observer. These callbacks behave very
|
6
|
-
#
|
7
|
-
#
|
8
|
-
# * Each callback can define a set of transition
|
9
|
-
#
|
4
|
+
# Adds support for creating before/after/around/failure transition
|
5
|
+
# callbacks within a DataMapper observer. These callbacks behave very
|
6
|
+
# similar to hooks during save/update/destroy/etc., but with the following
|
7
|
+
# modifications:
|
8
|
+
# * Each callback can define a set of transition requirements that must be
|
9
|
+
# met in order for the callback to get invoked.
|
10
10
|
# * An additional transition parameter is available that provides
|
11
|
-
#
|
12
|
-
#
|
11
|
+
# contextual information about the event (see StateMachine::Transition
|
12
|
+
# for more information)
|
13
13
|
#
|
14
14
|
# To define a single observer for multiple state machines:
|
15
15
|
#
|
@@ -91,7 +91,7 @@ module StateMachine
|
|
91
91
|
# Vehicle instance being transition). This means that +self+ refers
|
92
92
|
# to the vehicle record within each callback block.
|
93
93
|
def before_transition(*args, &block)
|
94
|
-
add_transition_callback(:
|
94
|
+
add_transition_callback(:before_transition, *args, &block)
|
95
95
|
end
|
96
96
|
|
97
97
|
# Creates a callback that will be invoked *after* a transition is
|
@@ -101,7 +101,7 @@ module StateMachine
|
|
101
101
|
# See +before_transition+ for a description of the possible configurations
|
102
102
|
# for defining callbacks.
|
103
103
|
def after_transition(*args, &block)
|
104
|
-
add_transition_callback(:
|
104
|
+
add_transition_callback(:after_transition, *args, &block)
|
105
105
|
end
|
106
106
|
|
107
107
|
# Creates a callback that will be invoked *around* a transition so long
|
@@ -137,7 +137,42 @@ module StateMachine
|
|
137
137
|
# See +before_transition+ for a description of the possible configurations
|
138
138
|
# for defining callbacks.
|
139
139
|
def around_transition(*args, &block)
|
140
|
-
add_transition_callback(:
|
140
|
+
add_transition_callback(:around_transition, *args, &block)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Creates a callback that will be invoked *after* a transition failures to
|
144
|
+
# be performed so long as the given requirements match the transition.
|
145
|
+
#
|
146
|
+
# == Example
|
147
|
+
#
|
148
|
+
# class Vehicle
|
149
|
+
# include DataMapper::Resource
|
150
|
+
#
|
151
|
+
# property :id, Serial
|
152
|
+
# property :state, :String
|
153
|
+
#
|
154
|
+
# state_machine :initial => :parked do
|
155
|
+
# event :ignite do
|
156
|
+
# transition :parked => :idling
|
157
|
+
# end
|
158
|
+
# end
|
159
|
+
# end
|
160
|
+
#
|
161
|
+
# class VehicleObserver
|
162
|
+
# after_transition_failure do |transition|
|
163
|
+
# # log failure
|
164
|
+
# end
|
165
|
+
#
|
166
|
+
# after_transition_failure :on => :ignite do
|
167
|
+
# # log failure
|
168
|
+
# end
|
169
|
+
# end
|
170
|
+
#
|
171
|
+
# See +before_transition+ for a description of the possible configurations
|
172
|
+
# for defining callbacks. *Note* however that you cannot define the state
|
173
|
+
# requirements in these callbacks. You may only define event requirements.
|
174
|
+
def after_transition_failure(*args, &block)
|
175
|
+
add_transition_callback(:after_failure, *args, &block)
|
141
176
|
end
|
142
177
|
|
143
178
|
private
|
@@ -162,7 +197,7 @@ module StateMachine
|
|
162
197
|
klass.state_machines.values
|
163
198
|
end
|
164
199
|
|
165
|
-
state_machines.each {|machine| machine.send(
|
200
|
+
state_machines.each {|machine| machine.send(type, *args, &block)}
|
166
201
|
end if observing
|
167
202
|
end
|
168
203
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module StateMachine
|
2
|
+
module Integrations #:nodoc:
|
3
|
+
module DataMapper
|
4
|
+
version '0.9.x' do
|
5
|
+
def self.active?
|
6
|
+
::DataMapper::VERSION =~ /^0\.9\./
|
7
|
+
end
|
8
|
+
|
9
|
+
def action_hook
|
10
|
+
action
|
11
|
+
end
|
12
|
+
|
13
|
+
def mark_dirty(object, value)
|
14
|
+
object.original_values[self.attribute] = "#{value}-ignored" if object.original_values[self.attribute] == value
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
version '0.9.x - 0.10.x' do
|
19
|
+
def self.active?
|
20
|
+
::DataMapper::VERSION =~ /^0\.\d\./ || ::DataMapper::VERSION =~ /^0\.10\./
|
21
|
+
end
|
22
|
+
|
23
|
+
def pluralize(word)
|
24
|
+
::Extlib::Inflection.pluralize(word.to_s)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
version '1.0.0' do
|
29
|
+
def self.active?
|
30
|
+
::DataMapper::VERSION == '1.0.0'
|
31
|
+
end
|
32
|
+
|
33
|
+
def pluralize(word)
|
34
|
+
(defined?(::ActiveSupport::Inflector) ? ::ActiveSupport::Inflector : ::Extlib::Inflection).pluralize(word.to_s)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
version '0.9.4 - 0.9.6' do
|
39
|
+
def self.active?
|
40
|
+
::DataMapper::VERSION =~ /^0\.9\.[4-6]/
|
41
|
+
end
|
42
|
+
|
43
|
+
# 0.9.4 - 0.9.6 fails to run after callbacks when validations are
|
44
|
+
# enabled because of the way dm-validations integrates
|
45
|
+
def define_action_helpers?
|
46
|
+
super if action != :save || !supports_validations?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
version '0.10.x' do
|
51
|
+
def self.active?
|
52
|
+
::DataMapper::VERSION =~ /^0\.10\./
|
53
|
+
end
|
54
|
+
|
55
|
+
def mark_dirty(object, value)
|
56
|
+
property = owner_class.properties[self.attribute]
|
57
|
+
object.original_attributes[property] = "#{value}-ignored" unless object.original_attributes.include?(property)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'state_machine/integrations/active_model'
|
2
|
+
|
1
3
|
module StateMachine
|
2
4
|
module Integrations #:nodoc:
|
3
5
|
# Adds support for integrating state machines with MongoMapper models.
|
@@ -184,8 +186,11 @@ module StateMachine
|
|
184
186
|
# Note, also, that the transition can be accessed by simply defining
|
185
187
|
# additional arguments in the callback block.
|
186
188
|
module MongoMapper
|
189
|
+
include Base
|
187
190
|
include ActiveModel
|
188
191
|
|
192
|
+
require 'state_machine/integrations/mongo_mapper/versions'
|
193
|
+
|
189
194
|
# The default options to use for state machines using this integration
|
190
195
|
@defaults = {:action => :save}
|
191
196
|
|
@@ -196,20 +201,10 @@ module StateMachine
|
|
196
201
|
defined?(::MongoMapper::Document) && klass <= ::MongoMapper::Document
|
197
202
|
end
|
198
203
|
|
199
|
-
# Adds a validation error to the given object (no i18n support)
|
200
|
-
def invalidate(object, attribute, message, values = [])
|
201
|
-
object.errors.add(self.attribute(attribute), generate_message(message, values))
|
202
|
-
end
|
203
|
-
|
204
204
|
protected
|
205
|
-
#
|
206
|
-
def
|
207
|
-
|
208
|
-
end
|
209
|
-
|
210
|
-
# Always adds validation support
|
211
|
-
def supports_validations?
|
212
|
-
true
|
205
|
+
# The name of this integration
|
206
|
+
def integration
|
207
|
+
:mongo_mapper
|
213
208
|
end
|
214
209
|
|
215
210
|
# Only runs validations on the action if using <tt>:save</tt>
|
@@ -217,71 +212,46 @@ module StateMachine
|
|
217
212
|
action == :save
|
218
213
|
end
|
219
214
|
|
220
|
-
#
|
221
|
-
|
215
|
+
# MongoMapper uses its own implementation of mass-assignment security
|
216
|
+
# instead of ActiveModel's, but still has a similar enough API that it
|
217
|
+
# can get enabled
|
218
|
+
def supports_mass_assignment_security?
|
222
219
|
true
|
223
220
|
end
|
224
221
|
|
225
|
-
#
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
# Don't allow translations
|
230
|
-
def translate(klass, key, value)
|
231
|
-
value.to_s.humanize.downcase
|
222
|
+
# Filters attributes that cannot be assigned through the initialization
|
223
|
+
# of the object
|
224
|
+
def filter_attributes(object, attributes)
|
225
|
+
object.send(:filter_protected_attrs, attributes)
|
232
226
|
end
|
233
227
|
|
234
228
|
# Defines an initialization hook into the owner class for setting the
|
235
229
|
# initial state of the machine *before* any attributes are set on the
|
236
230
|
# object
|
237
231
|
def define_state_initializer
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
if !from_database && (!attrs || !attrs.stringify_keys.key?('_id'))
|
243
|
-
filtered = respond_to?(:filter_protected_attrs) ? filter_protected_attrs(attrs) : attrs
|
244
|
-
ignore = filtered ? filtered.keys : []
|
245
|
-
|
246
|
-
initialize_state_machines(:dynamic => false, :ignore => ignore)
|
247
|
-
super
|
248
|
-
initialize_state_machines(:dynamic => true, :ignore => ignore)
|
249
|
-
else
|
250
|
-
super
|
251
|
-
end
|
252
|
-
end
|
253
|
-
end_eval
|
232
|
+
define_helper(:instance, :initialize) do |machine, object, _super, *args|
|
233
|
+
object.class.state_machines.initialize_states(object, :attributes => args.first) { _super.call }
|
234
|
+
end
|
254
235
|
end
|
255
236
|
|
256
237
|
# Skips defining reader/writer methods since this is done automatically
|
257
238
|
def define_state_accessor
|
258
239
|
owner_class.key(attribute, String) unless owner_class.keys.include?(attribute)
|
259
|
-
|
260
|
-
name = self.name
|
261
|
-
owner_class.validates_each(attribute, :logic => lambda {|*|
|
262
|
-
machine = self.class.state_machine(name)
|
263
|
-
machine.invalidate(self, :state, :invalid) unless machine.states.match(self)
|
264
|
-
})
|
240
|
+
super
|
265
241
|
end
|
266
242
|
|
267
|
-
#
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
# Still use class_eval here instance of define_instance_method since
|
274
|
-
# we need to be able to call +super+
|
275
|
-
@instance_helper_module.class_eval do
|
276
|
-
define_method("#{name}?") do |*args|
|
277
|
-
args.empty? ? super(*args) : self.class.state_machine(name).states.matches?(self, *args)
|
278
|
-
end
|
243
|
+
# Uses around callbacks to run state events if using the :save hook
|
244
|
+
def define_action_hook
|
245
|
+
if action_hook == :save
|
246
|
+
owner_class.set_callback(:save, :around, self, :prepend => true)
|
247
|
+
else
|
248
|
+
super
|
279
249
|
end
|
280
250
|
end
|
281
251
|
|
282
|
-
#
|
283
|
-
def
|
284
|
-
|
252
|
+
# Runs state events around the machine's :save action
|
253
|
+
def around_save(object)
|
254
|
+
object.class.state_machines.transitions(object, action).perform { yield }
|
285
255
|
end
|
286
256
|
|
287
257
|
# Creates a scope for finding records *with* a particular state or
|
@@ -298,11 +268,14 @@ module StateMachine
|
|
298
268
|
|
299
269
|
# Defines a new scope with the given name
|
300
270
|
def define_scope(name, scope)
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
271
|
+
lambda {|model, values| model.query.merge(model.query(scope.call(values)))}
|
272
|
+
end
|
273
|
+
|
274
|
+
# ActiveModel's use of method_missing / respond_to for attribute methods
|
275
|
+
# breaks both ancestor lookups and defined?(super). Need to special-case
|
276
|
+
# the existence of query attribute methods.
|
277
|
+
def owner_class_ancestor_has_method?(method)
|
278
|
+
method == "#{name}?" || super
|
306
279
|
end
|
307
280
|
end
|
308
281
|
end
|