state_machine 1.0.3 → 1.1.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.
Files changed (117) hide show
  1. data/.travis.yml +23 -7
  2. data/Appraisals +31 -25
  3. data/CHANGELOG.md +12 -0
  4. data/README.md +3 -1
  5. data/gemfiles/active_model-3.0.0.gemfile.lock +7 -5
  6. data/gemfiles/active_model-3.0.5.gemfile.lock +7 -5
  7. data/gemfiles/active_model-3.1.1.gemfile.lock +6 -4
  8. data/gemfiles/active_record-2.0.0.gemfile +1 -1
  9. data/gemfiles/active_record-2.0.0.gemfile.lock +7 -5
  10. data/gemfiles/active_record-2.0.5.gemfile +1 -1
  11. data/gemfiles/active_record-2.0.5.gemfile.lock +7 -5
  12. data/gemfiles/active_record-2.1.0.gemfile +1 -1
  13. data/gemfiles/active_record-2.1.0.gemfile.lock +7 -5
  14. data/gemfiles/active_record-2.1.2.gemfile +1 -1
  15. data/gemfiles/active_record-2.1.2.gemfile.lock +7 -5
  16. data/gemfiles/active_record-2.2.3.gemfile +1 -1
  17. data/gemfiles/active_record-2.2.3.gemfile.lock +7 -5
  18. data/gemfiles/active_record-2.3.12.gemfile +1 -1
  19. data/gemfiles/active_record-2.3.12.gemfile.lock +7 -5
  20. data/gemfiles/active_record-3.0.0.gemfile +1 -1
  21. data/gemfiles/active_record-3.0.0.gemfile.lock +8 -6
  22. data/gemfiles/active_record-3.0.5.gemfile +1 -1
  23. data/gemfiles/active_record-3.0.5.gemfile.lock +8 -6
  24. data/gemfiles/active_record-3.1.1.gemfile +1 -1
  25. data/gemfiles/active_record-3.1.1.gemfile.lock +7 -5
  26. data/gemfiles/data_mapper-0.10.2.gemfile +3 -3
  27. data/gemfiles/data_mapper-0.10.2.gemfile.lock +14 -5
  28. data/gemfiles/data_mapper-0.9.11.gemfile +3 -3
  29. data/gemfiles/data_mapper-0.9.11.gemfile.lock +7 -5
  30. data/gemfiles/data_mapper-0.9.4.gemfile +3 -3
  31. data/gemfiles/data_mapper-0.9.4.gemfile.lock +13 -13
  32. data/gemfiles/data_mapper-0.9.7.gemfile +3 -3
  33. data/gemfiles/data_mapper-0.9.7.gemfile.lock +13 -13
  34. data/gemfiles/data_mapper-1.0.0.gemfile +3 -3
  35. data/gemfiles/data_mapper-1.0.0.gemfile.lock +17 -8
  36. data/gemfiles/data_mapper-1.0.1.gemfile +3 -3
  37. data/gemfiles/data_mapper-1.0.1.gemfile.lock +17 -8
  38. data/gemfiles/data_mapper-1.0.2.gemfile +3 -3
  39. data/gemfiles/data_mapper-1.0.2.gemfile.lock +17 -8
  40. data/gemfiles/data_mapper-1.1.0.gemfile +3 -3
  41. data/gemfiles/data_mapper-1.1.0.gemfile.lock +17 -8
  42. data/gemfiles/data_mapper-1.2.0.gemfile +3 -3
  43. data/gemfiles/data_mapper-1.2.0.gemfile.lock +13 -4
  44. data/gemfiles/default.gemfile.lock +7 -5
  45. data/gemfiles/graphviz-0.9.0.gemfile.lock +6 -4
  46. data/gemfiles/graphviz-0.9.21.gemfile.lock +6 -4
  47. data/gemfiles/graphviz-1.0.0.gemfile.lock +6 -4
  48. data/gemfiles/mongo_mapper-0.10.0.gemfile.lock +6 -3
  49. data/gemfiles/mongo_mapper-0.5.5.gemfile +1 -1
  50. data/gemfiles/mongo_mapper-0.5.5.gemfile.lock +7 -5
  51. data/gemfiles/mongo_mapper-0.5.8.gemfile +1 -1
  52. data/gemfiles/mongo_mapper-0.5.8.gemfile.lock +7 -5
  53. data/gemfiles/mongo_mapper-0.6.0.gemfile +1 -1
  54. data/gemfiles/mongo_mapper-0.6.0.gemfile.lock +7 -5
  55. data/gemfiles/mongo_mapper-0.6.10.gemfile +1 -1
  56. data/gemfiles/mongo_mapper-0.6.10.gemfile.lock +7 -5
  57. data/gemfiles/mongo_mapper-0.7.0.gemfile +1 -1
  58. data/gemfiles/mongo_mapper-0.7.0.gemfile.lock +7 -5
  59. data/gemfiles/mongo_mapper-0.7.5.gemfile +1 -1
  60. data/gemfiles/mongo_mapper-0.7.5.gemfile.lock +7 -5
  61. data/gemfiles/mongo_mapper-0.8.0.gemfile +2 -2
  62. data/gemfiles/mongo_mapper-0.8.0.gemfile.lock +7 -5
  63. data/gemfiles/mongo_mapper-0.8.3.gemfile +2 -2
  64. data/gemfiles/mongo_mapper-0.8.3.gemfile.lock +7 -5
  65. data/gemfiles/mongo_mapper-0.8.4.gemfile +1 -1
  66. data/gemfiles/mongo_mapper-0.8.4.gemfile.lock +11 -8
  67. data/gemfiles/mongo_mapper-0.8.6.gemfile +1 -1
  68. data/gemfiles/mongo_mapper-0.8.6.gemfile.lock +11 -8
  69. data/gemfiles/mongo_mapper-0.9.0.gemfile.lock +14 -11
  70. data/gemfiles/mongoid-2.0.0.gemfile.lock +22 -17
  71. data/gemfiles/mongoid-2.1.4.gemfile.lock +21 -16
  72. data/gemfiles/mongoid-2.2.4.gemfile.lock +11 -8
  73. data/gemfiles/mongoid-2.3.3.gemfile.lock +11 -8
  74. data/gemfiles/sequel-2.11.0.gemfile.lock +7 -5
  75. data/gemfiles/sequel-2.12.0.gemfile.lock +7 -5
  76. data/gemfiles/sequel-2.8.0.gemfile.lock +7 -5
  77. data/gemfiles/sequel-3.0.0.gemfile.lock +7 -5
  78. data/gemfiles/sequel-3.13.0.gemfile.lock +7 -5
  79. data/gemfiles/sequel-3.14.0.gemfile.lock +7 -5
  80. data/gemfiles/sequel-3.23.0.gemfile.lock +7 -5
  81. data/gemfiles/sequel-3.24.0.gemfile.lock +7 -5
  82. data/gemfiles/sequel-3.29.0.gemfile.lock +5 -3
  83. data/lib/state_machine.rb +7 -1
  84. data/lib/state_machine/eval_helpers.rb +8 -9
  85. data/lib/state_machine/event.rb +10 -2
  86. data/lib/state_machine/integrations.rb +0 -1
  87. data/lib/state_machine/integrations/active_model.rb +46 -35
  88. data/lib/state_machine/integrations/active_record.rb +8 -0
  89. data/lib/state_machine/integrations/active_record/versions.rb +0 -20
  90. data/lib/state_machine/integrations/data_mapper.rb +22 -21
  91. data/lib/state_machine/integrations/data_mapper/versions.rb +0 -27
  92. data/lib/state_machine/integrations/mongo_mapper.rb +8 -0
  93. data/lib/state_machine/integrations/mongo_mapper/versions.rb +0 -4
  94. data/lib/state_machine/integrations/mongoid.rb +15 -2
  95. data/lib/state_machine/integrations/mongoid/versions.rb +0 -7
  96. data/lib/state_machine/integrations/sequel.rb +14 -0
  97. data/lib/state_machine/machine.rb +12 -0
  98. data/lib/state_machine/state.rb +4 -3
  99. data/lib/state_machine/state_context.rb +10 -9
  100. data/lib/state_machine/transition.rb +6 -1
  101. data/lib/state_machine/version.rb +1 -1
  102. data/state_machine.gemspec +1 -1
  103. data/test/functional/state_machine_test.rb +21 -1
  104. data/test/unit/event_test.rb +10 -0
  105. data/test/unit/integrations/active_model_test.rb +31 -29
  106. data/test/unit/integrations/active_record_test.rb +56 -26
  107. data/test/unit/integrations/data_mapper_test.rb +34 -57
  108. data/test/unit/integrations/mongo_mapper_test.rb +30 -24
  109. data/test/unit/integrations/mongoid_test.rb +114 -29
  110. data/test/unit/integrations/sequel_test.rb +36 -0
  111. data/test/unit/invalid_transition_test.rb +38 -0
  112. data/test/unit/machine_test.rb +38 -0
  113. data/test/unit/state_context_test.rb +29 -9
  114. data/test/unit/state_test.rb +21 -1
  115. data/test/unit/transition_collection_test.rb +72 -26
  116. data/test/unit/transition_test.rb +84 -73
  117. metadata +8 -8
@@ -1,24 +1,26 @@
1
1
  PATH
2
2
  remote: /home/aaron/Projects/Personal/pluginaweek/state_machine
3
3
  specs:
4
- state_machine (1.0.2)
4
+ state_machine (1.0.3)
5
5
 
6
6
  GEM
7
7
  remote: http://www.rubygems.org/
8
8
  specs:
9
- appraisal (0.3.8)
9
+ appraisal (0.4.0)
10
10
  bundler
11
11
  rake
12
- rake (0.9.2)
13
- rcov (0.9.10)
12
+ rake (0.9.2.2)
13
+ rcov (0.9.11)
14
+ rcov (0.9.11-java)
14
15
  sequel (3.13.0)
15
16
  sqlite3-ruby (1.3.1)
16
17
 
17
18
  PLATFORMS
19
+ java
18
20
  ruby
19
21
 
20
22
  DEPENDENCIES
21
- appraisal (~> 0.3.8)
23
+ appraisal (~> 0.4.0)
22
24
  rake
23
25
  rcov
24
26
  sequel (= 3.13.0)
@@ -1,24 +1,26 @@
1
1
  PATH
2
2
  remote: /home/aaron/Projects/Personal/pluginaweek/state_machine
3
3
  specs:
4
- state_machine (1.0.2)
4
+ state_machine (1.0.3)
5
5
 
6
6
  GEM
7
7
  remote: http://www.rubygems.org/
8
8
  specs:
9
- appraisal (0.3.8)
9
+ appraisal (0.4.0)
10
10
  bundler
11
11
  rake
12
- rake (0.9.2)
13
- rcov (0.9.10)
12
+ rake (0.9.2.2)
13
+ rcov (0.9.11)
14
+ rcov (0.9.11-java)
14
15
  sequel (3.14.0)
15
16
  sqlite3-ruby (1.3.1)
16
17
 
17
18
  PLATFORMS
19
+ java
18
20
  ruby
19
21
 
20
22
  DEPENDENCIES
21
- appraisal (~> 0.3.8)
23
+ appraisal (~> 0.4.0)
22
24
  rake
23
25
  rcov
24
26
  sequel (= 3.14.0)
@@ -1,24 +1,26 @@
1
1
  PATH
2
2
  remote: /home/aaron/Projects/Personal/pluginaweek/state_machine
3
3
  specs:
4
- state_machine (1.0.2)
4
+ state_machine (1.0.3)
5
5
 
6
6
  GEM
7
7
  remote: http://www.rubygems.org/
8
8
  specs:
9
- appraisal (0.3.8)
9
+ appraisal (0.4.0)
10
10
  bundler
11
11
  rake
12
- rake (0.9.2)
13
- rcov (0.9.10)
12
+ rake (0.9.2.2)
13
+ rcov (0.9.11)
14
+ rcov (0.9.11-java)
14
15
  sequel (3.23.0)
15
16
  sqlite3-ruby (1.3.1)
16
17
 
17
18
  PLATFORMS
19
+ java
18
20
  ruby
19
21
 
20
22
  DEPENDENCIES
21
- appraisal (~> 0.3.8)
23
+ appraisal (~> 0.4.0)
22
24
  rake
23
25
  rcov
24
26
  sequel (= 3.23.0)
@@ -1,24 +1,26 @@
1
1
  PATH
2
2
  remote: /home/aaron/Projects/Personal/pluginaweek/state_machine
3
3
  specs:
4
- state_machine (1.0.2)
4
+ state_machine (1.0.3)
5
5
 
6
6
  GEM
7
7
  remote: http://www.rubygems.org/
8
8
  specs:
9
- appraisal (0.3.8)
9
+ appraisal (0.4.0)
10
10
  bundler
11
11
  rake
12
- rake (0.9.2)
13
- rcov (0.9.10)
12
+ rake (0.9.2.2)
13
+ rcov (0.9.11)
14
+ rcov (0.9.11-java)
14
15
  sequel (3.24.0)
15
16
  sqlite3-ruby (1.3.1)
16
17
 
17
18
  PLATFORMS
19
+ java
18
20
  ruby
19
21
 
20
22
  DEPENDENCIES
21
- appraisal (~> 0.3.8)
23
+ appraisal (~> 0.4.0)
22
24
  rake
23
25
  rcov
24
26
  sequel (= 3.24.0)
@@ -6,19 +6,21 @@ PATH
6
6
  GEM
7
7
  remote: http://www.rubygems.org/
8
8
  specs:
9
- appraisal (0.3.8)
9
+ appraisal (0.4.0)
10
10
  bundler
11
11
  rake
12
- rake (0.9.2)
12
+ rake (0.9.2.2)
13
13
  rcov (0.9.11)
14
+ rcov (0.9.11-java)
14
15
  sequel (3.29.0)
15
16
  sqlite3-ruby (1.3.1)
16
17
 
17
18
  PLATFORMS
19
+ java
18
20
  ruby
19
21
 
20
22
  DEPENDENCIES
21
- appraisal (~> 0.3.8)
23
+ appraisal (~> 0.4.0)
22
24
  rake
23
25
  rcov
24
26
  sequel (= 3.29.0)
@@ -141,6 +141,9 @@ module StateMachine
141
141
  # transitions that can be made on the current object's state
142
142
  # * <tt>state_paths(requirements = {})</tt> - Gets the list of sequences of
143
143
  # transitions that can be run from the current object's state
144
+ # * <tt>fire_state_event(name, *args)</tt> - Fires an arbitrary event with
145
+ # the given argument list. This is essentially the same as calling the
146
+ # actual event method itself.
144
147
  #
145
148
  # The <tt>state_events</tt>, <tt>state_transitions</tt>, and <tt>state_paths</tt>
146
149
  # helpers all take an optional set of requirements for determining what's
@@ -188,7 +191,7 @@ module StateMachine
188
191
  # vehicle.state_events(:to => :parked) # => []
189
192
  #
190
193
  # vehicle.state_transitions # => [#<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
191
- # vehicle.ignite
194
+ # vehicle.ignite # => true
192
195
  # vehicle.state_transitions # => [#<StateMachine::Transition attribute=:state event=:park from="idling" from_name=:idling to="parked" to_name=:parked>]
193
196
  #
194
197
  # vehicle.state_transitions(:on => :ignite) # => []
@@ -202,6 +205,9 @@ module StateMachine
202
205
  # # [#<StateMachine::Transition attribute=:state event=:park from="idling" from_name=:idling to="parked" to_name=:parked>,
203
206
  # # #<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
204
207
  # # ]
208
+ #
209
+ # # Fire arbitrary events
210
+ # vehicle.fire_state_event(:park) # => true
205
211
  #
206
212
  # == Attribute initialization
207
213
  #
@@ -57,25 +57,24 @@ module StateMachine
57
57
  when Proc, Method
58
58
  args.unshift(object)
59
59
  arity = method.arity
60
- limit = [0, 1].include?(arity) ? arity : args.length
61
60
 
62
- # Procs don't support blocks in < Ruby 1.8.6, so it's tacked on as an
63
- # argument for consistency across versions of Ruby (even though 1.9
64
- # supports yielding within blocks)
61
+ # Procs don't support blocks in < Ruby 1.9, so it's tacked on as an
62
+ # argument for consistency across versions of Ruby
65
63
  if block_given? && Proc === method && arity != 0
66
64
  if [1, 2].include?(arity)
67
65
  # Force the block to be either the only argument or the 2nd one
68
66
  # after the object (may mean additional arguments get discarded)
69
- limit = arity
70
- args.insert(limit - 1, block)
67
+ args = args[0, arity - 1] + [block]
71
68
  else
72
69
  # Tack the block to the end of the args
73
- limit += 1 unless limit < 0
74
- args.push(block)
70
+ args << block
75
71
  end
72
+ else
73
+ # These method types are only called with 0, 1, or n arguments
74
+ args = args[0, arity] if [0, 1].include?(arity)
76
75
  end
77
76
 
78
- method.call(*args[0, limit], &block)
77
+ method.call(*args, &block)
79
78
  when String
80
79
  eval(method, object.instance_eval {binding}, &block)
81
80
  else
@@ -55,8 +55,7 @@ module StateMachine
55
55
  @name = name
56
56
  @qualified_name = machine.namespace ? :"#{name}_#{machine.namespace}" : name
57
57
  @human_name = options[:human_name] || @name.to_s.tr('_', ' ')
58
- @branches = []
59
- @known_states = []
58
+ reset
60
59
 
61
60
  # Output a warning if another event has a conflicting qualified name
62
61
  if conflict = machine.owner_class.state_machines.detect {|name, other_machine| other_machine != @machine && other_machine.events[qualified_name, :qualified_name]}
@@ -186,6 +185,15 @@ module StateMachine
186
185
  Transition.new(object, machine, name, state.name, state.name).run_callbacks(:before => false)
187
186
  end
188
187
 
188
+ # Resets back to the initial state of the event, with no branches / known
189
+ # states associated. This allows you to redefine an event in situations
190
+ # where you either are re-using an existing state machine implementation
191
+ # or are subclassing machines.
192
+ def reset
193
+ @branches = []
194
+ @known_states = []
195
+ end
196
+
189
197
  # Draws a representation of this event on the given graph. This will
190
198
  # create 1 or more edges on the graph for each branch (i.e. transition)
191
199
  # configured.
@@ -43,7 +43,6 @@ module StateMachine
43
43
  # end
44
44
  #
45
45
  # class ActiveModelVehicle
46
- # include ActiveModel::Dirty
47
46
  # include ActiveModel::Observing
48
47
  # include ActiveModel::Validations
49
48
  # end
@@ -7,7 +7,6 @@ module StateMachine
7
7
  # If using ActiveModel directly within your class, then any one of the
8
8
  # following features need to be included in order for the integration to be
9
9
  # detected:
10
- # * ActiveModel::Dirty
11
10
  # * ActiveModel::Observing
12
11
  # * ActiveModel::Validations
13
12
  #
@@ -15,7 +14,6 @@ module StateMachine
15
14
  # ActiveModel class:
16
15
  #
17
16
  # class Vehicle
18
- # include ActiveModel::Dirty
19
17
  # include ActiveModel::Observing
20
18
  # include ActiveModel::Validations
21
19
  #
@@ -99,6 +97,14 @@ module StateMachine
99
97
  # vehicle.ignite # => false
100
98
  # vehicle.errors.full_messages # => ["State cannot transition via \"ignite\""]
101
99
  #
100
+ # In addition, if you're using the <tt>ignite!</tt> version of the event,
101
+ # then the failure reason (such as the current validation errors) will be
102
+ # included in the exception that gets raised when the event fails. For
103
+ # example, assuming there's a validation on a field called +name+ on the class:
104
+ #
105
+ # vehicle = Vehicle.new
106
+ # vehicle.ignite! # => StateMachine::InvalidTransition: Cannot transition state via :ignite from :parked (Reason(s): Name cannot be blank)
107
+ #
102
108
  # === Security implications
103
109
  #
104
110
  # Beware that public event attributes mean that events can be fired
@@ -279,28 +285,46 @@ module StateMachine
279
285
  #
280
286
  # == Dirty Attribute Tracking
281
287
  #
282
- # In order to hook in validation support for your model, the
283
- # ActiveModel::Validations feature must be included. If this is included
284
- # then state attributes will always be properly marked as changed whether
285
- # they were a callback or not.
288
+ # When using the ActiveModel::Dirty extension, your model will keep track of
289
+ # any changes that are made to attributes. Depending on your ORM, an object
290
+ # will only be saved when there are attributes that have changed on the
291
+ # object. When integrating with state_machine, typically the +state+ field
292
+ # will be marked as dirty after a transition occurs. In some situations,
293
+ # however, this isn't the case.
286
294
  #
287
- # For example,
295
+ # If you define loopback transitions in your state machine, the value for
296
+ # the machine's attribute (e.g. state) will not change. Unless you explicitly
297
+ # indicate so, this means that your object won't persist anything on a
298
+ # loopback. For example:
288
299
  #
289
300
  # class Vehicle
301
+ # include ActiveModel::Validations
290
302
  # include ActiveModel::Dirty
291
303
  # attr_accessor :state
292
304
  #
293
305
  # state_machine :initial => :parked do
294
306
  # event :park do
295
- # transition :parked => :parked
307
+ # transition :parked => :parked, ...
308
+ # end
309
+ # end
310
+ # end
311
+ #
312
+ # If, instead, you'd like your object to always persist regardless of
313
+ # whether the value actually changed, you can do so by using the
314
+ # <tt>#{attribute}_will_change!</tt> helpers or defining a +before_transition+
315
+ # callback that actually changes an attribute on the model. For example:
316
+ #
317
+ # class Vehicle
318
+ # ...
319
+ # state_machine :initial => :parked do
320
+ # before_transition all => same do |vehicle|
321
+ # vehicle.state_will_change!
322
+ #
323
+ # # Alternative solution, updating timestamp
324
+ # # vehicle.updated_at = Time.curent
296
325
  # end
297
326
  # end
298
327
  # end
299
- #
300
- # vehicle = Vehicle.new
301
- # vehicle.changed # => []
302
- # vehicle.park # => true
303
- # vehicle.changed # => ["state"]
304
328
  #
305
329
  # == Creating new integrations
306
330
  #
@@ -349,23 +373,10 @@ module StateMachine
349
373
  end
350
374
 
351
375
  # Should this integration be used for state machines in the given class?
352
- # Classes that include ActiveModel::Dirty, ActiveModel::Observing, or
353
- # ActiveModel::Validations will automatically use the ActiveModel
354
- # integration.
376
+ # Classes that include ActiveModel::Observing or ActiveModel::Validations
377
+ # will automatically use the ActiveModel integration.
355
378
  def self.matches?(klass)
356
- %w(Dirty Observing Validations).any? {|feature| ::ActiveModel.const_defined?(feature) && klass <= ::ActiveModel.const_get(feature)}
357
- end
358
-
359
- # Forces the change in state to be recognized regardless of whether the
360
- # state value actually changed
361
- def write(object, attribute, value, *args)
362
- result = super
363
-
364
- if (attribute == :state || attribute == :event && value) && supports_dirty_tracking?(object) && !object.send("#{self.attribute}_changed?")
365
- object.send("#{self.attribute}_will_change!")
366
- end
367
-
368
- result
379
+ %w(Observing Validations).any? {|feature| ::ActiveModel.const_defined?(feature) && klass <= ::ActiveModel.const_get(feature)}
369
380
  end
370
381
 
371
382
  # Adds a validation error to the given object
@@ -382,6 +393,12 @@ module StateMachine
382
393
  end
383
394
  end
384
395
 
396
+ # Describes the current validation errors on the given object. If none
397
+ # are specific, then the default error is interpeted as a "halt".
398
+ def errors_for(object)
399
+ object.errors.empty? ? 'Transition halted' : object.errors.full_messages * ', '
400
+ end
401
+
385
402
  # Resets any errors previously added when invalidating the given object
386
403
  def reset(object)
387
404
  object.errors.clear if supports_validations?
@@ -407,12 +424,6 @@ module StateMachine
407
424
  false
408
425
  end
409
426
 
410
- # Whether change (dirty) tracking is supported in the integration.
411
- # Only true if the ActiveModel feature is enabled on the owner class.
412
- def supports_dirty_tracking?(object)
413
- defined?(::ActiveModel::Dirty) && owner_class <= ::ActiveModel::Dirty && object.respond_to?("#{self.attribute}_changed?")
414
- end
415
-
416
427
  # Gets the terminator to use for callbacks
417
428
  def callback_terminator
418
429
  @terminator ||= lambda {|result| result == false}
@@ -195,6 +195,14 @@ module StateMachine
195
195
  # *not* because a matching transition was not available, no error messages
196
196
  # will be added to the state attribute.
197
197
  #
198
+ # In addition, if you're using the <tt>ignite!</tt> version of the event,
199
+ # then the failure reason (such as the current validation errors) will be
200
+ # included in the exception that gets raised when the event fails. For
201
+ # example, assuming there's a validation on a field called +name+ on the class:
202
+ #
203
+ # vehicle = Vehicle.new
204
+ # vehicle.ignite! # => StateMachine::InvalidTransition: Cannot transition state via :ignite from :parked (Reason(s): Name cannot be blank)
205
+ #
198
206
  # == Scopes
199
207
  #
200
208
  # To assist in filtering models with specific states, a series of named
@@ -97,26 +97,6 @@ module StateMachine
97
97
  end
98
98
  end
99
99
 
100
- version '2.0.x' do
101
- def self.active?
102
- ::ActiveRecord::VERSION::MAJOR == 2 && ::ActiveRecord::VERSION::MINOR == 0
103
- end
104
-
105
- def supports_dirty_tracking?(object)
106
- false
107
- end
108
- end
109
-
110
- version '2.1.x - 2.3.x' do
111
- def self.active?
112
- ::ActiveRecord::VERSION::MAJOR == 2 && ::ActiveRecord::VERSION::MINOR > 0
113
- end
114
-
115
- def supports_dirty_tracking?(object)
116
- object.respond_to?("#{attribute}_changed?")
117
- end
118
- end
119
-
120
100
  version '2.3.2 - 2.3.x' do
121
101
  def self.active?
122
102
  ::ActiveRecord::VERSION::MAJOR == 2 && ::ActiveRecord::VERSION::MINOR == 3 && ::ActiveRecord::VERSION::TINY >= 2
@@ -178,6 +178,14 @@ module StateMachine
178
178
  # *not* because a matching transition was not available, no error messages
179
179
  # will be added to the state attribute.
180
180
  #
181
+ # In addition, if you're using the <tt>ignite!</tt> version of the event,
182
+ # then the failure reason (such as the current validation errors) will be
183
+ # included in the exception that gets raised when the event fails. For
184
+ # example, assuming there's a validation on a field called +name+ on the class:
185
+ #
186
+ # vehicle = Vehicle.new
187
+ # vehicle.ignite! # => StateMachine::InvalidTransition: Cannot transition state via :ignite from :parked (Reason(s): Name cannot be blank)
188
+ #
181
189
  # == Scopes
182
190
  #
183
191
  # To assist in filtering models with specific states, a series of class
@@ -322,24 +330,25 @@ module StateMachine
322
330
  super
323
331
  end
324
332
 
325
- # Forces the change in state to be recognized regardless of whether the
326
- # state value actually changed
327
- def write(object, attribute, value, *args)
328
- result = super
329
-
330
- if attribute == :state || attribute == :event && value
331
- value = read(object, :state) if attribute == :event
332
- mark_dirty(object, value)
333
- end
334
-
335
- result
336
- end
337
-
338
333
  # Adds a validation error to the given object
339
334
  def invalidate(object, attribute, message, values = [])
340
335
  object.errors.add(self.attribute(attribute), generate_message(message, values)) if supports_validations?
341
336
  end
342
337
 
338
+ # Describes the current validation errors on the given object. If none
339
+ # are specific, then the default error is interpeted as a "halt".
340
+ def errors_for(object)
341
+ if object.errors.empty?
342
+ 'Transition halted'
343
+ else
344
+ errors = []
345
+ object.errors.each_pair do |field_name, field_errors|
346
+ field_errors.each {|error| errors << "#{field_name} #{error}"}
347
+ end
348
+ errors * ', '
349
+ end
350
+ end
351
+
343
352
  # Resets any errors previously added when invalidating the given object
344
353
  def reset(object)
345
354
  object.errors.clear if supports_validations?
@@ -434,14 +443,6 @@ module StateMachine
434
443
  options[:bind_to_object] = true
435
444
  super
436
445
  end
437
-
438
- # Marks the object's state as dirty so that the record will be saved
439
- # even if no actual modifications have been made to the data
440
- def mark_dirty(object, value)
441
- object.persistence_state = ::DataMapper::Resource::PersistenceState::Dirty.new(object) if object.persistence_state.is_a?(::DataMapper::Resource::PersistenceState::Clean)
442
- property = owner_class.properties[self.attribute]
443
- object.persistence_state.original_attributes[property] = value unless object.persistence_state.original_attributes.include?(property)
444
- end
445
446
  end
446
447
  end
447
448
  end