state_machine 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
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