state_machine 1.0.3 → 1.1.0

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