state_machine 1.0.2 → 1.0.3
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/.gitignore +1 -0
- data/.travis.yml +0 -2
- data/.yardopts +3 -2
- data/Appraisals +48 -0
- data/{CHANGELOG.rdoc → CHANGELOG.md} +63 -46
- data/README.md +1029 -0
- data/gemfiles/active_model-3.0.0.gemfile.lock +1 -3
- data/gemfiles/active_model-3.0.5.gemfile.lock +1 -3
- data/gemfiles/active_model-3.1.1.gemfile +7 -0
- data/gemfiles/active_model-3.1.1.gemfile.lock +32 -0
- data/gemfiles/active_record-2.0.0.gemfile.lock +1 -3
- data/gemfiles/active_record-2.0.5.gemfile.lock +1 -3
- data/gemfiles/active_record-2.1.0.gemfile.lock +1 -3
- data/gemfiles/active_record-2.1.2.gemfile.lock +1 -3
- data/gemfiles/active_record-2.2.3.gemfile.lock +1 -3
- data/gemfiles/active_record-2.3.12.gemfile.lock +1 -3
- data/gemfiles/active_record-3.0.0.gemfile.lock +1 -3
- data/gemfiles/active_record-3.0.5.gemfile.lock +1 -3
- data/gemfiles/active_record-3.1.1.gemfile +8 -0
- data/gemfiles/active_record-3.1.1.gemfile.lock +43 -0
- data/gemfiles/data_mapper-0.10.2.gemfile.lock +1 -3
- data/gemfiles/data_mapper-0.9.11.gemfile.lock +1 -3
- data/gemfiles/data_mapper-0.9.4.gemfile.lock +1 -3
- data/gemfiles/data_mapper-0.9.7.gemfile.lock +1 -3
- data/gemfiles/data_mapper-1.0.0.gemfile.lock +1 -3
- data/gemfiles/data_mapper-1.0.1.gemfile.lock +1 -3
- data/gemfiles/data_mapper-1.0.2.gemfile.lock +1 -3
- data/gemfiles/data_mapper-1.1.0.gemfile.lock +1 -3
- data/gemfiles/data_mapper-1.2.0.gemfile +12 -0
- data/gemfiles/data_mapper-1.2.0.gemfile.lock +49 -0
- data/gemfiles/default.gemfile.lock +1 -3
- data/gemfiles/graphviz-0.9.0.gemfile +7 -0
- data/gemfiles/graphviz-0.9.0.gemfile.lock +24 -0
- data/gemfiles/graphviz-0.9.21.gemfile +7 -0
- data/gemfiles/graphviz-0.9.21.gemfile.lock +24 -0
- data/gemfiles/graphviz-1.0.0.gemfile +7 -0
- data/gemfiles/graphviz-1.0.0.gemfile.lock +24 -0
- data/gemfiles/mongo_mapper-0.10.0.gemfile +7 -0
- data/gemfiles/mongo_mapper-0.10.0.gemfile.lock +41 -0
- data/gemfiles/mongo_mapper-0.5.5.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.5.8.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.6.0.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.6.10.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.7.0.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.7.5.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.8.0.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.8.3.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.8.4.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.8.6.gemfile.lock +1 -3
- data/gemfiles/mongo_mapper-0.9.0.gemfile.lock +1 -3
- data/gemfiles/mongoid-2.0.0.gemfile.lock +1 -3
- data/gemfiles/mongoid-2.1.4.gemfile.lock +1 -3
- data/gemfiles/mongoid-2.2.4.gemfile +7 -0
- data/gemfiles/mongoid-2.2.4.gemfile.lock +40 -0
- data/gemfiles/mongoid-2.3.3.gemfile +7 -0
- data/gemfiles/mongoid-2.3.3.gemfile.lock +40 -0
- data/gemfiles/sequel-2.11.0.gemfile.lock +1 -3
- data/gemfiles/sequel-2.12.0.gemfile.lock +1 -3
- data/gemfiles/sequel-2.8.0.gemfile.lock +1 -3
- data/gemfiles/sequel-3.0.0.gemfile.lock +1 -3
- data/gemfiles/sequel-3.13.0.gemfile.lock +1 -3
- data/gemfiles/sequel-3.14.0.gemfile.lock +1 -3
- data/gemfiles/sequel-3.23.0.gemfile.lock +1 -3
- data/gemfiles/sequel-3.24.0.gemfile.lock +1 -3
- data/gemfiles/sequel-3.29.0.gemfile +8 -0
- data/gemfiles/sequel-3.29.0.gemfile.lock +26 -0
- data/lib/state_machine.rb +45 -0
- data/lib/state_machine/event.rb +18 -3
- data/lib/state_machine/event_collection.rb +1 -1
- data/lib/state_machine/integrations/active_model.rb +59 -16
- data/lib/state_machine/integrations/active_model/observer.rb +3 -15
- data/lib/state_machine/integrations/active_record.rb +46 -9
- data/lib/state_machine/integrations/data_mapper.rb +42 -2
- data/lib/state_machine/integrations/data_mapper/versions.rb +22 -10
- data/lib/state_machine/integrations/mongo_mapper.rb +55 -0
- data/lib/state_machine/integrations/mongo_mapper/versions.rb +3 -3
- data/lib/state_machine/integrations/mongoid.rb +57 -12
- data/lib/state_machine/integrations/mongoid/versions.rb +22 -4
- data/lib/state_machine/integrations/sequel.rb +45 -0
- data/lib/state_machine/integrations/sequel/versions.rb +3 -0
- data/lib/state_machine/machine.rb +148 -34
- data/lib/state_machine/node_collection.rb +36 -3
- data/lib/state_machine/state.rb +6 -3
- data/lib/state_machine/state_collection.rb +1 -1
- data/lib/state_machine/version.rb +1 -1
- data/lib/tasks/state_machine.rb +11 -9
- data/state_machine.gemspec +2 -3
- data/test/functional/state_machine_test.rb +54 -1
- data/test/unit/event_collection_test.rb +4 -0
- data/test/unit/event_test.rb +34 -1
- data/test/unit/integrations/active_model_test.rb +80 -0
- data/test/unit/integrations/active_record_test.rb +105 -2
- data/test/unit/integrations/data_mapper_test.rb +27 -25
- data/test/unit/integrations/mongo_mapper_test.rb +80 -25
- data/test/unit/integrations/mongoid_test.rb +61 -6
- data/test/unit/integrations/sequel_test.rb +8 -2
- data/test/unit/machine_test.rb +87 -9
- data/test/unit/node_collection_test.rb +129 -12
- data/test/unit/state_collection_test.rb +4 -0
- data/test/unit/state_test.rb +2 -2
- metadata +30 -24
- data/README.rdoc +0 -844
data/lib/state_machine.rb
CHANGED
|
@@ -306,6 +306,51 @@ module StateMachine
|
|
|
306
306
|
# Each predicate method will return true if it matches the object's
|
|
307
307
|
# current state. Otherwise, it will return false.
|
|
308
308
|
#
|
|
309
|
+
# == Attribute access
|
|
310
|
+
#
|
|
311
|
+
# The actual value for a state is stored in the attribute configured for the
|
|
312
|
+
# state machine. In most cases, this is the same as the name of the state
|
|
313
|
+
# machine. For example:
|
|
314
|
+
#
|
|
315
|
+
# class Vehicle
|
|
316
|
+
# attr_accessor :state
|
|
317
|
+
#
|
|
318
|
+
# state_machine :state, :initial => :parked do
|
|
319
|
+
# ...
|
|
320
|
+
# state :parked, :value => 0
|
|
321
|
+
# start :idling, :value => 1
|
|
322
|
+
# end
|
|
323
|
+
# end
|
|
324
|
+
#
|
|
325
|
+
# vehicle = Vehicle.new # => #<Vehicle:0xb712da60 @state=0>
|
|
326
|
+
# vehicle.state # => 0
|
|
327
|
+
# vehicle.parked? # => true
|
|
328
|
+
# vehicle.state = 1
|
|
329
|
+
# vehicle.idling? # => true
|
|
330
|
+
#
|
|
331
|
+
# The most important thing to note from the example above is what it means
|
|
332
|
+
# to read from and write to the state machine's attribute. In particular,
|
|
333
|
+
# state_machine treats the attribute (+state+ in this case) like a basic
|
|
334
|
+
# attr_accessor that's been defined on the class. There are no special
|
|
335
|
+
# behaviors added, such as allowing the attribute to be written to based on
|
|
336
|
+
# the name of a state in the machine. This is the case for a few reasons:
|
|
337
|
+
# * Setting the attribute directly is an edge case that is meant to only be
|
|
338
|
+
# used when you want to skip state_machine altogether. This means that
|
|
339
|
+
# state_machine shouldn't have any effect on the attribute accessor
|
|
340
|
+
# methods. If you want to change the state, you should be using one of
|
|
341
|
+
# the events defined in the state machine.
|
|
342
|
+
# * Many ORMs provide custom behavior for the attribute reader / writer - it
|
|
343
|
+
# may even be defined by your own framework / method implementation just
|
|
344
|
+
# the example above showed. In order to avoid having to worry about the
|
|
345
|
+
# different ways an attribute can get written, state_machine just makes
|
|
346
|
+
# sure that the configured value for a state is always used when writing
|
|
347
|
+
# to the attribute.
|
|
348
|
+
#
|
|
349
|
+
# If you were interested in accessing the name of a state (instead of its
|
|
350
|
+
# actual value through the attribute), you could do the following:
|
|
351
|
+
#
|
|
352
|
+
# vehicle.state_name # => :idling
|
|
353
|
+
#
|
|
309
354
|
# == Events and Transitions
|
|
310
355
|
#
|
|
311
356
|
# Events defined on the machine are the interface to transitioning states
|
data/lib/state_machine/event.rb
CHANGED
|
@@ -67,6 +67,11 @@ module StateMachine
|
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
+
# Converts the name of this event to a string
|
|
71
|
+
def name_to_s
|
|
72
|
+
name.to_s
|
|
73
|
+
end
|
|
74
|
+
|
|
70
75
|
# Creates a copy of this event in addition to the list of associated
|
|
71
76
|
# branches to prevent conflicts across events within a class hierarchy.
|
|
72
77
|
def initialize_copy(orig) #:nodoc:
|
|
@@ -81,6 +86,12 @@ module StateMachine
|
|
|
81
86
|
@human_name.is_a?(Proc) ? @human_name.call(self, klass) : @human_name
|
|
82
87
|
end
|
|
83
88
|
|
|
89
|
+
# Evaluates the given block within the context of this event. This simply
|
|
90
|
+
# provides a DSL-like syntax for defining transitions.
|
|
91
|
+
def context(&block)
|
|
92
|
+
instance_eval(&block)
|
|
93
|
+
end
|
|
94
|
+
|
|
84
95
|
# Creates a new transition that determines what to change the current state
|
|
85
96
|
# to when this event fires.
|
|
86
97
|
#
|
|
@@ -113,6 +124,10 @@ module StateMachine
|
|
|
113
124
|
# on the current state of the given object.
|
|
114
125
|
#
|
|
115
126
|
# If the event can't be fired, then this will return false, otherwise true.
|
|
127
|
+
#
|
|
128
|
+
# *Note* that this will not take the object context into account. Although
|
|
129
|
+
# a transition may be possible based on the state machine definition,
|
|
130
|
+
# object-specific behaviors (like validations) may prevent it from firing.
|
|
116
131
|
def can_fire?(object, requirements = {})
|
|
117
132
|
!transition_for(object, requirements).nil?
|
|
118
133
|
end
|
|
@@ -165,10 +180,10 @@ module StateMachine
|
|
|
165
180
|
# Marks the object as invalid and runs any failure callbacks associated with
|
|
166
181
|
# this event. This should get called anytime this event fails to transition.
|
|
167
182
|
def on_failure(object)
|
|
168
|
-
|
|
183
|
+
state = machine.states.match!(object)
|
|
184
|
+
machine.invalidate(object, :state, :invalid_transition, [[:event, human_name(object.class)], [:state, state.human_name(object.class)]])
|
|
169
185
|
|
|
170
|
-
|
|
171
|
-
Transition.new(object, machine, name, state, state).run_callbacks(:before => false)
|
|
186
|
+
Transition.new(object, machine, name, state.name, state.name).run_callbacks(:before => false)
|
|
172
187
|
end
|
|
173
188
|
|
|
174
189
|
# Draws a representation of this event on the given graph. This will
|
|
@@ -2,7 +2,7 @@ module StateMachine
|
|
|
2
2
|
# Represents a collection of events in a state machine
|
|
3
3
|
class EventCollection < NodeCollection
|
|
4
4
|
def initialize(machine) #:nodoc:
|
|
5
|
-
super(machine, :index => [:name, :qualified_name])
|
|
5
|
+
super(machine, :index => [:name, :name_to_s, :qualified_name])
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
# Gets the list of events that can be fired on the given object.
|
|
@@ -227,6 +227,56 @@ module StateMachine
|
|
|
227
227
|
# end
|
|
228
228
|
# end
|
|
229
229
|
#
|
|
230
|
+
# == Internationalization
|
|
231
|
+
#
|
|
232
|
+
# Any error message that is generated from performing invalid transitions
|
|
233
|
+
# can be localized. The following default translations are used:
|
|
234
|
+
#
|
|
235
|
+
# en:
|
|
236
|
+
# activemodel:
|
|
237
|
+
# errors:
|
|
238
|
+
# messages:
|
|
239
|
+
# invalid: "is invalid"
|
|
240
|
+
# # %{value} = attribute value, %{state} = Human state name
|
|
241
|
+
# invalid_event: "cannot transition when %{state}"
|
|
242
|
+
# # %{value} = attribute value, %{event} = Human event name, %{state} = Human current state name
|
|
243
|
+
# invalid_transition: "cannot transition via %{event}"
|
|
244
|
+
#
|
|
245
|
+
# You can override these for a specific model like so:
|
|
246
|
+
#
|
|
247
|
+
# en:
|
|
248
|
+
# activemodel:
|
|
249
|
+
# errors:
|
|
250
|
+
# models:
|
|
251
|
+
# user:
|
|
252
|
+
# invalid: "is not valid"
|
|
253
|
+
#
|
|
254
|
+
# In addition to the above, you can also provide translations for the
|
|
255
|
+
# various states / events in each state machine. Using the Vehicle example,
|
|
256
|
+
# state translations will be looked for using the following keys, where
|
|
257
|
+
# +model_name+ = "vehicle", +machine_name+ = "state" and +state_name+ = "parked":
|
|
258
|
+
# * <tt>activemodel.state_machines.#{model_name}.#{machine_name}.states.#{state_name}</tt>
|
|
259
|
+
# * <tt>activemodel.state_machines.#{model_name}.states.#{state_name}</tt>
|
|
260
|
+
# * <tt>activemodel.state_machines.#{machine_name}.states.#{state_name}</tt>
|
|
261
|
+
# * <tt>activemodel.state_machines.states.#{state_name}</tt>
|
|
262
|
+
#
|
|
263
|
+
# Event translations will be looked for using the following keys, where
|
|
264
|
+
# +model_name+ = "vehicle", +machine_name+ = "state" and +event_name+ = "ignite":
|
|
265
|
+
# * <tt>activemodel.state_machines.#{model_name}.#{machine_name}.events.#{event_name}</tt>
|
|
266
|
+
# * <tt>activemodel.state_machines.#{model_name}.events.#{event_name}</tt>
|
|
267
|
+
# * <tt>activemodel.state_machines.#{machine_name}.events.#{event_name}</tt>
|
|
268
|
+
# * <tt>activemodel.state_machines.events.#{event_name}</tt>
|
|
269
|
+
#
|
|
270
|
+
# An example translation configuration might look like so:
|
|
271
|
+
#
|
|
272
|
+
# es:
|
|
273
|
+
# activemodel:
|
|
274
|
+
# state_machines:
|
|
275
|
+
# states:
|
|
276
|
+
# parked: 'estacionado'
|
|
277
|
+
# events:
|
|
278
|
+
# park: 'estacionarse'
|
|
279
|
+
#
|
|
230
280
|
# == Dirty Attribute Tracking
|
|
231
281
|
#
|
|
232
282
|
# In order to hook in validation support for your model, the
|
|
@@ -255,9 +305,9 @@ module StateMachine
|
|
|
255
305
|
# == Creating new integrations
|
|
256
306
|
#
|
|
257
307
|
# If you want to integrate state_machine with an ORM that implements parts
|
|
258
|
-
# or all of the ActiveModel API, the
|
|
259
|
-
#
|
|
260
|
-
#
|
|
308
|
+
# or all of the ActiveModel API, only the machine defaults need to be
|
|
309
|
+
# specified. Otherwise, the implementation is similar to any other
|
|
310
|
+
# integration.
|
|
261
311
|
#
|
|
262
312
|
# For example,
|
|
263
313
|
#
|
|
@@ -270,19 +320,10 @@ module StateMachine
|
|
|
270
320
|
# defined?(::MyORM::Base) && klass <= ::MyORM::Base
|
|
271
321
|
# end
|
|
272
322
|
#
|
|
273
|
-
# def self.extended(base)
|
|
274
|
-
# locale = "#{File.dirname(__FILE__)}/my_orm/locale.rb"
|
|
275
|
-
# I18n.load_path << locale unless I18n.load_path.include?(locale)
|
|
276
|
-
# end
|
|
277
|
-
#
|
|
278
323
|
# protected
|
|
279
324
|
# def runs_validations_on_action?
|
|
280
325
|
# action == :persist
|
|
281
326
|
# end
|
|
282
|
-
#
|
|
283
|
-
# def i18n_scope
|
|
284
|
-
# :myorm
|
|
285
|
-
# end
|
|
286
327
|
# end
|
|
287
328
|
#
|
|
288
329
|
# If you wish to implement other features, such as attribute initialization
|
|
@@ -391,6 +432,7 @@ module StateMachine
|
|
|
391
432
|
# Translates the given key / value combo. Translation keys are looked
|
|
392
433
|
# up in the following order:
|
|
393
434
|
# * <tt>#{i18n_scope}.state_machines.#{model_name}.#{machine_name}.#{plural_key}.#{value}</tt>
|
|
435
|
+
# * <tt>#{i18n_scope}.state_machines.#{model_name}.#{plural_key}.#{value}</tt>
|
|
394
436
|
# * <tt>#{i18n_scope}.state_machines.#{machine_name}.#{plural_key}.#{value}</tt>
|
|
395
437
|
# * <tt>#{i18n_scope}.state_machines.#{plural_key}.#{value}</tt>
|
|
396
438
|
#
|
|
@@ -402,6 +444,7 @@ module StateMachine
|
|
|
402
444
|
|
|
403
445
|
# Generate all possible translation keys
|
|
404
446
|
translations = ancestors.map {|ancestor| :"#{ancestor.model_name.underscore}.#{name}.#{group}.#{value}"}
|
|
447
|
+
translations.concat(ancestors.map {|ancestor| :"#{ancestor.model_name.underscore}.#{group}.#{value}"})
|
|
405
448
|
translations.concat([:"#{name}.#{group}.#{value}", :"#{group}.#{value}", value.humanize.downcase])
|
|
406
449
|
I18n.translate(translations.shift, :default => translations, :scope => [i18n_scope(klass), :state_machines])
|
|
407
450
|
end
|
|
@@ -513,22 +556,22 @@ module StateMachine
|
|
|
513
556
|
def notify(type, object, transition)
|
|
514
557
|
name = self.name
|
|
515
558
|
event = transition.qualified_event
|
|
516
|
-
from = transition.from_name
|
|
517
|
-
to = transition.to_name
|
|
559
|
+
from = transition.from_name || 'nil'
|
|
560
|
+
to = transition.to_name || 'nil'
|
|
518
561
|
|
|
519
562
|
# Machine-specific updates
|
|
520
563
|
["#{type}_#{event}", "#{type}_transition_#{name}"].each do |event_segment|
|
|
521
564
|
["_from_#{from}", nil].each do |from_segment|
|
|
522
565
|
["_to_#{to}", nil].each do |to_segment|
|
|
523
566
|
object.class.changed if object.class.respond_to?(:changed)
|
|
524
|
-
object.class.notify_observers([event_segment, from_segment, to_segment].join, object, transition)
|
|
567
|
+
object.class.notify_observers('update_with_transition', [[event_segment, from_segment, to_segment].join, object, transition])
|
|
525
568
|
end
|
|
526
569
|
end
|
|
527
570
|
end
|
|
528
571
|
|
|
529
572
|
# Generic updates
|
|
530
573
|
object.class.changed if object.class.respond_to?(:changed)
|
|
531
|
-
object.class.notify_observers("#{type}_transition", object, transition)
|
|
574
|
+
object.class.notify_observers('update_with_transition', ["#{type}_transition", object, transition])
|
|
532
575
|
|
|
533
576
|
true
|
|
534
577
|
end
|
|
@@ -19,21 +19,9 @@ module StateMachine
|
|
|
19
19
|
# end
|
|
20
20
|
# end
|
|
21
21
|
module Observer
|
|
22
|
-
def
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
22
|
+
def update_with_transition(args)
|
|
23
|
+
observed_method, object, transition = args
|
|
24
|
+
send(observed_method, object, transition) if respond_to?(observed_method)
|
|
37
25
|
end
|
|
38
26
|
end
|
|
39
27
|
end
|
|
@@ -220,6 +220,11 @@ module StateMachine
|
|
|
220
220
|
#
|
|
221
221
|
# Vehicle.with_state(:parked).all(:order => 'id DESC')
|
|
222
222
|
#
|
|
223
|
+
# Note that states can also be referenced by the string version of their
|
|
224
|
+
# name:
|
|
225
|
+
#
|
|
226
|
+
# Vehicle.with_state('parked')
|
|
227
|
+
#
|
|
223
228
|
# == Callbacks
|
|
224
229
|
#
|
|
225
230
|
# All before/after transition callbacks defined for ActiveRecord models
|
|
@@ -251,6 +256,32 @@ module StateMachine
|
|
|
251
256
|
# Note, also, that the transition can be accessed by simply defining
|
|
252
257
|
# additional arguments in the callback block.
|
|
253
258
|
#
|
|
259
|
+
# === Failure callbacks
|
|
260
|
+
#
|
|
261
|
+
# +after_failure+ callbacks allow you to execute behaviors when a transition
|
|
262
|
+
# is allowed, but fails to save. This could be useful for something like
|
|
263
|
+
# auditing transition attempts. Since callbacks run within transactions in
|
|
264
|
+
# ActiveRecord, a save failure will cause any records that get created in
|
|
265
|
+
# your callback to roll back. You can work around this issue like so:
|
|
266
|
+
#
|
|
267
|
+
# class TransitionLog < ActiveRecord::Base
|
|
268
|
+
# establish_connection Rails.env.to_sym
|
|
269
|
+
# end
|
|
270
|
+
#
|
|
271
|
+
# class Vehicle < ActiveRecord::Base
|
|
272
|
+
# state_machine do
|
|
273
|
+
# after_failure do |vehicle, transition|
|
|
274
|
+
# TransitionLog.create(:vehicle => vehicle, :transition => transition)
|
|
275
|
+
# end
|
|
276
|
+
#
|
|
277
|
+
# ...
|
|
278
|
+
# end
|
|
279
|
+
# end
|
|
280
|
+
#
|
|
281
|
+
# The +TransitionLog+ model establishes a second connection to the database
|
|
282
|
+
# that allows new records to be saved without being affected by rollbacks
|
|
283
|
+
# in the +Vehicle+ model's transaction.
|
|
284
|
+
#
|
|
254
285
|
# == Observers
|
|
255
286
|
#
|
|
256
287
|
# In addition to support for ActiveRecord-like hooks, there is additional
|
|
@@ -317,7 +348,9 @@ module StateMachine
|
|
|
317
348
|
# errors:
|
|
318
349
|
# messages:
|
|
319
350
|
# invalid: "is invalid"
|
|
351
|
+
# # %{value} = attribute value, %{state} = Human state name
|
|
320
352
|
# invalid_event: "cannot transition when %{state}"
|
|
353
|
+
# # %{value} = attribute value, %{event} = Human event name, %{state} = Human current state name
|
|
321
354
|
# invalid_transition: "cannot transition via %{event}"
|
|
322
355
|
#
|
|
323
356
|
# Notice that the interpolation syntax is %{key} in Rails 3+. In Rails 2.x,
|
|
@@ -334,15 +367,19 @@ module StateMachine
|
|
|
334
367
|
#
|
|
335
368
|
# In addition to the above, you can also provide translations for the
|
|
336
369
|
# various states / events in each state machine. Using the Vehicle example,
|
|
337
|
-
# state translations will be looked for using the following keys
|
|
338
|
-
#
|
|
339
|
-
# * <tt>activerecord.state_machines.
|
|
340
|
-
# * <tt>activerecord.state_machines.states
|
|
341
|
-
#
|
|
342
|
-
#
|
|
343
|
-
#
|
|
344
|
-
#
|
|
345
|
-
#
|
|
370
|
+
# state translations will be looked for using the following keys, where
|
|
371
|
+
# +model_name+ = "vehicle", +machine_name+ = "state" and +state_name+ = "parked":
|
|
372
|
+
# * <tt>activerecord.state_machines.#{model_name}.#{machine_name}.states.#{state_name}</tt>
|
|
373
|
+
# * <tt>activerecord.state_machines.#{model_name}.states.#{state_name}</tt>
|
|
374
|
+
# * <tt>activerecord.state_machines.#{machine_name}.states.#{state_name}</tt>
|
|
375
|
+
# * <tt>activerecord.state_machines.states.#{state_name}</tt>
|
|
376
|
+
#
|
|
377
|
+
# Event translations will be looked for using the following keys, where
|
|
378
|
+
# +model_name+ = "vehicle", +machine_name+ = "state" and +event_name+ = "ignite":
|
|
379
|
+
# * <tt>activerecord.state_machines.#{model_name}.#{machine_name}.events.#{event_name}</tt>
|
|
380
|
+
# * <tt>activerecord.state_machines.#{model_name}.events.#{event_name}</tt>
|
|
381
|
+
# * <tt>activerecord.state_machines.#{machine_name}.events.#{event_name}</tt>
|
|
382
|
+
# * <tt>activerecord.state_machines.events.#{event_name}</tt>
|
|
346
383
|
#
|
|
347
384
|
# An example translation configuration might look like so:
|
|
348
385
|
#
|
|
@@ -214,6 +214,11 @@ module StateMachine
|
|
|
214
214
|
#
|
|
215
215
|
# Vehicle.with_state(:parked).all(:order => [:id.desc])
|
|
216
216
|
#
|
|
217
|
+
# Note that states can also be referenced by the string version of their
|
|
218
|
+
# name:
|
|
219
|
+
#
|
|
220
|
+
# Vehicle.with_state('parked')
|
|
221
|
+
#
|
|
217
222
|
# == Callbacks / Observers
|
|
218
223
|
#
|
|
219
224
|
# All before/after transition callbacks defined for DataMapper resources
|
|
@@ -254,6 +259,41 @@ module StateMachine
|
|
|
254
259
|
# In addition to support for DataMapper-like hooks, there is additional
|
|
255
260
|
# support for DataMapper observers. See StateMachine::Integrations::DataMapper::Observer
|
|
256
261
|
# for more information.
|
|
262
|
+
#
|
|
263
|
+
# === Failure callbacks
|
|
264
|
+
#
|
|
265
|
+
# +after_failure+ callbacks allow you to execute behaviors when a transition
|
|
266
|
+
# is allowed, but fails to save. This could be useful for something like
|
|
267
|
+
# auditing transition attempts. Since callbacks run within transactions in
|
|
268
|
+
# DataMapper, a save failure will cause any records that get created in
|
|
269
|
+
# your callback to roll back. *Note* that this is only a problem if the
|
|
270
|
+
# machine is configured to use transactions. If it is, you can work around
|
|
271
|
+
# this issue like so:
|
|
272
|
+
#
|
|
273
|
+
# DataMapper.setup(:default, 'mysql://localhost/app')
|
|
274
|
+
# DataMapper.setup(:logs, 'mysql://localhost/app')
|
|
275
|
+
#
|
|
276
|
+
# class TransitionLog
|
|
277
|
+
# include DataMapper::Resource
|
|
278
|
+
# end
|
|
279
|
+
#
|
|
280
|
+
# class Vehicle < ActiveRecord::Base
|
|
281
|
+
# include DataMapper::Resource
|
|
282
|
+
#
|
|
283
|
+
# state_machine :use_transactions => true do
|
|
284
|
+
# after_failure do |transition|
|
|
285
|
+
# DataMapper.repository(:logs) do
|
|
286
|
+
# TransitionLog.create(:vehicle => vehicle, :transition => transition)
|
|
287
|
+
# end
|
|
288
|
+
# end
|
|
289
|
+
#
|
|
290
|
+
# ...
|
|
291
|
+
# end
|
|
292
|
+
# end
|
|
293
|
+
#
|
|
294
|
+
# The failure callback creates +TransitionLog+ records using a second
|
|
295
|
+
# connection to the database, allowing them to be saved without being
|
|
296
|
+
# affected by rollbacks in the +Vehicle+ resource's transaction.
|
|
257
297
|
module DataMapper
|
|
258
298
|
include Base
|
|
259
299
|
|
|
@@ -398,9 +438,9 @@ module StateMachine
|
|
|
398
438
|
# Marks the object's state as dirty so that the record will be saved
|
|
399
439
|
# even if no actual modifications have been made to the data
|
|
400
440
|
def mark_dirty(object, value)
|
|
401
|
-
object.
|
|
441
|
+
object.persistence_state = ::DataMapper::Resource::PersistenceState::Dirty.new(object) if object.persistence_state.is_a?(::DataMapper::Resource::PersistenceState::Clean)
|
|
402
442
|
property = owner_class.properties[self.attribute]
|
|
403
|
-
object.
|
|
443
|
+
object.persistence_state.original_attributes[property] = value unless object.persistence_state.original_attributes.include?(property)
|
|
404
444
|
end
|
|
405
445
|
end
|
|
406
446
|
end
|
|
@@ -25,16 +25,6 @@ module StateMachine
|
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
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
28
|
version '0.9.4 - 0.9.6' do
|
|
39
29
|
def self.active?
|
|
40
30
|
::DataMapper::VERSION =~ /^0\.9\.[4-6]/
|
|
@@ -57,6 +47,28 @@ module StateMachine
|
|
|
57
47
|
object.original_attributes[property] = "#{value}-ignored" unless object.original_attributes.include?(property)
|
|
58
48
|
end
|
|
59
49
|
end
|
|
50
|
+
|
|
51
|
+
version '1.0.x - 1.1.x' do
|
|
52
|
+
def self.active?
|
|
53
|
+
::DataMapper::VERSION =~ /^1\.[01]\./
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def mark_dirty(object, value)
|
|
57
|
+
object.persisted_state = ::DataMapper::Resource::State::Dirty.new(object) if object.persisted_state.is_a?(::DataMapper::Resource::State::Clean)
|
|
58
|
+
property = owner_class.properties[self.attribute]
|
|
59
|
+
object.persisted_state.original_attributes[property] = value unless object.persisted_state.original_attributes.include?(property)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
version '1.0.0' do
|
|
64
|
+
def self.active?
|
|
65
|
+
::DataMapper::VERSION == '1.0.0'
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def pluralize(word)
|
|
69
|
+
(defined?(::ActiveSupport::Inflector) ? ::ActiveSupport::Inflector : ::Extlib::Inflection).pluralize(word.to_s)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
60
72
|
end
|
|
61
73
|
end
|
|
62
74
|
end
|