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.
Files changed (102) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +0 -2
  3. data/.yardopts +3 -2
  4. data/Appraisals +48 -0
  5. data/{CHANGELOG.rdoc → CHANGELOG.md} +63 -46
  6. data/README.md +1029 -0
  7. data/gemfiles/active_model-3.0.0.gemfile.lock +1 -3
  8. data/gemfiles/active_model-3.0.5.gemfile.lock +1 -3
  9. data/gemfiles/active_model-3.1.1.gemfile +7 -0
  10. data/gemfiles/active_model-3.1.1.gemfile.lock +32 -0
  11. data/gemfiles/active_record-2.0.0.gemfile.lock +1 -3
  12. data/gemfiles/active_record-2.0.5.gemfile.lock +1 -3
  13. data/gemfiles/active_record-2.1.0.gemfile.lock +1 -3
  14. data/gemfiles/active_record-2.1.2.gemfile.lock +1 -3
  15. data/gemfiles/active_record-2.2.3.gemfile.lock +1 -3
  16. data/gemfiles/active_record-2.3.12.gemfile.lock +1 -3
  17. data/gemfiles/active_record-3.0.0.gemfile.lock +1 -3
  18. data/gemfiles/active_record-3.0.5.gemfile.lock +1 -3
  19. data/gemfiles/active_record-3.1.1.gemfile +8 -0
  20. data/gemfiles/active_record-3.1.1.gemfile.lock +43 -0
  21. data/gemfiles/data_mapper-0.10.2.gemfile.lock +1 -3
  22. data/gemfiles/data_mapper-0.9.11.gemfile.lock +1 -3
  23. data/gemfiles/data_mapper-0.9.4.gemfile.lock +1 -3
  24. data/gemfiles/data_mapper-0.9.7.gemfile.lock +1 -3
  25. data/gemfiles/data_mapper-1.0.0.gemfile.lock +1 -3
  26. data/gemfiles/data_mapper-1.0.1.gemfile.lock +1 -3
  27. data/gemfiles/data_mapper-1.0.2.gemfile.lock +1 -3
  28. data/gemfiles/data_mapper-1.1.0.gemfile.lock +1 -3
  29. data/gemfiles/data_mapper-1.2.0.gemfile +12 -0
  30. data/gemfiles/data_mapper-1.2.0.gemfile.lock +49 -0
  31. data/gemfiles/default.gemfile.lock +1 -3
  32. data/gemfiles/graphviz-0.9.0.gemfile +7 -0
  33. data/gemfiles/graphviz-0.9.0.gemfile.lock +24 -0
  34. data/gemfiles/graphviz-0.9.21.gemfile +7 -0
  35. data/gemfiles/graphviz-0.9.21.gemfile.lock +24 -0
  36. data/gemfiles/graphviz-1.0.0.gemfile +7 -0
  37. data/gemfiles/graphviz-1.0.0.gemfile.lock +24 -0
  38. data/gemfiles/mongo_mapper-0.10.0.gemfile +7 -0
  39. data/gemfiles/mongo_mapper-0.10.0.gemfile.lock +41 -0
  40. data/gemfiles/mongo_mapper-0.5.5.gemfile.lock +1 -3
  41. data/gemfiles/mongo_mapper-0.5.8.gemfile.lock +1 -3
  42. data/gemfiles/mongo_mapper-0.6.0.gemfile.lock +1 -3
  43. data/gemfiles/mongo_mapper-0.6.10.gemfile.lock +1 -3
  44. data/gemfiles/mongo_mapper-0.7.0.gemfile.lock +1 -3
  45. data/gemfiles/mongo_mapper-0.7.5.gemfile.lock +1 -3
  46. data/gemfiles/mongo_mapper-0.8.0.gemfile.lock +1 -3
  47. data/gemfiles/mongo_mapper-0.8.3.gemfile.lock +1 -3
  48. data/gemfiles/mongo_mapper-0.8.4.gemfile.lock +1 -3
  49. data/gemfiles/mongo_mapper-0.8.6.gemfile.lock +1 -3
  50. data/gemfiles/mongo_mapper-0.9.0.gemfile.lock +1 -3
  51. data/gemfiles/mongoid-2.0.0.gemfile.lock +1 -3
  52. data/gemfiles/mongoid-2.1.4.gemfile.lock +1 -3
  53. data/gemfiles/mongoid-2.2.4.gemfile +7 -0
  54. data/gemfiles/mongoid-2.2.4.gemfile.lock +40 -0
  55. data/gemfiles/mongoid-2.3.3.gemfile +7 -0
  56. data/gemfiles/mongoid-2.3.3.gemfile.lock +40 -0
  57. data/gemfiles/sequel-2.11.0.gemfile.lock +1 -3
  58. data/gemfiles/sequel-2.12.0.gemfile.lock +1 -3
  59. data/gemfiles/sequel-2.8.0.gemfile.lock +1 -3
  60. data/gemfiles/sequel-3.0.0.gemfile.lock +1 -3
  61. data/gemfiles/sequel-3.13.0.gemfile.lock +1 -3
  62. data/gemfiles/sequel-3.14.0.gemfile.lock +1 -3
  63. data/gemfiles/sequel-3.23.0.gemfile.lock +1 -3
  64. data/gemfiles/sequel-3.24.0.gemfile.lock +1 -3
  65. data/gemfiles/sequel-3.29.0.gemfile +8 -0
  66. data/gemfiles/sequel-3.29.0.gemfile.lock +26 -0
  67. data/lib/state_machine.rb +45 -0
  68. data/lib/state_machine/event.rb +18 -3
  69. data/lib/state_machine/event_collection.rb +1 -1
  70. data/lib/state_machine/integrations/active_model.rb +59 -16
  71. data/lib/state_machine/integrations/active_model/observer.rb +3 -15
  72. data/lib/state_machine/integrations/active_record.rb +46 -9
  73. data/lib/state_machine/integrations/data_mapper.rb +42 -2
  74. data/lib/state_machine/integrations/data_mapper/versions.rb +22 -10
  75. data/lib/state_machine/integrations/mongo_mapper.rb +55 -0
  76. data/lib/state_machine/integrations/mongo_mapper/versions.rb +3 -3
  77. data/lib/state_machine/integrations/mongoid.rb +57 -12
  78. data/lib/state_machine/integrations/mongoid/versions.rb +22 -4
  79. data/lib/state_machine/integrations/sequel.rb +45 -0
  80. data/lib/state_machine/integrations/sequel/versions.rb +3 -0
  81. data/lib/state_machine/machine.rb +148 -34
  82. data/lib/state_machine/node_collection.rb +36 -3
  83. data/lib/state_machine/state.rb +6 -3
  84. data/lib/state_machine/state_collection.rb +1 -1
  85. data/lib/state_machine/version.rb +1 -1
  86. data/lib/tasks/state_machine.rb +11 -9
  87. data/state_machine.gemspec +2 -3
  88. data/test/functional/state_machine_test.rb +54 -1
  89. data/test/unit/event_collection_test.rb +4 -0
  90. data/test/unit/event_test.rb +34 -1
  91. data/test/unit/integrations/active_model_test.rb +80 -0
  92. data/test/unit/integrations/active_record_test.rb +105 -2
  93. data/test/unit/integrations/data_mapper_test.rb +27 -25
  94. data/test/unit/integrations/mongo_mapper_test.rb +80 -25
  95. data/test/unit/integrations/mongoid_test.rb +61 -6
  96. data/test/unit/integrations/sequel_test.rb +8 -2
  97. data/test/unit/machine_test.rb +87 -9
  98. data/test/unit/node_collection_test.rb +129 -12
  99. data/test/unit/state_collection_test.rb +4 -0
  100. data/test/unit/state_test.rb +2 -2
  101. metadata +30 -24
  102. data/README.rdoc +0 -844
@@ -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
@@ -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
- machine.invalidate(object, :state, :invalid_transition, [[:event, human_name(object.class)]])
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
- state = machine.states.match!(object).name
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 following features must be specified:
259
- # * i18n scope (locale)
260
- # * Machine defaults
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 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
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
- # * <tt>activerecord.state_machines.vehicle.state.states.parked</tt>
339
- # * <tt>activerecord.state_machines.state.states.parked
340
- # * <tt>activerecord.state_machines.states.parked</tt>
341
- #
342
- # Event translations will be looked for using the following keys:
343
- # * <tt>activerecord.state_machines.vehicle.state.events.ignite</tt>
344
- # * <tt>activerecord.state_machines.state.events.ignite
345
- # * <tt>activerecord.state_machines.events.ignite</tt>
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.persisted_state = ::DataMapper::Resource::State::Dirty.new(object) if object.persisted_state.is_a?(::DataMapper::Resource::State::Clean)
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.persisted_state.original_attributes[property] = value unless object.persisted_state.original_attributes.include?(property)
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