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.
- data/.travis.yml +23 -7
- data/Appraisals +31 -25
- data/CHANGELOG.md +12 -0
- data/README.md +3 -1
- data/gemfiles/active_model-3.0.0.gemfile.lock +7 -5
- data/gemfiles/active_model-3.0.5.gemfile.lock +7 -5
- data/gemfiles/active_model-3.1.1.gemfile.lock +6 -4
- data/gemfiles/active_record-2.0.0.gemfile +1 -1
- data/gemfiles/active_record-2.0.0.gemfile.lock +7 -5
- data/gemfiles/active_record-2.0.5.gemfile +1 -1
- data/gemfiles/active_record-2.0.5.gemfile.lock +7 -5
- data/gemfiles/active_record-2.1.0.gemfile +1 -1
- data/gemfiles/active_record-2.1.0.gemfile.lock +7 -5
- data/gemfiles/active_record-2.1.2.gemfile +1 -1
- data/gemfiles/active_record-2.1.2.gemfile.lock +7 -5
- data/gemfiles/active_record-2.2.3.gemfile +1 -1
- data/gemfiles/active_record-2.2.3.gemfile.lock +7 -5
- data/gemfiles/active_record-2.3.12.gemfile +1 -1
- data/gemfiles/active_record-2.3.12.gemfile.lock +7 -5
- data/gemfiles/active_record-3.0.0.gemfile +1 -1
- data/gemfiles/active_record-3.0.0.gemfile.lock +8 -6
- data/gemfiles/active_record-3.0.5.gemfile +1 -1
- data/gemfiles/active_record-3.0.5.gemfile.lock +8 -6
- data/gemfiles/active_record-3.1.1.gemfile +1 -1
- data/gemfiles/active_record-3.1.1.gemfile.lock +7 -5
- data/gemfiles/data_mapper-0.10.2.gemfile +3 -3
- data/gemfiles/data_mapper-0.10.2.gemfile.lock +14 -5
- data/gemfiles/data_mapper-0.9.11.gemfile +3 -3
- data/gemfiles/data_mapper-0.9.11.gemfile.lock +7 -5
- data/gemfiles/data_mapper-0.9.4.gemfile +3 -3
- data/gemfiles/data_mapper-0.9.4.gemfile.lock +13 -13
- data/gemfiles/data_mapper-0.9.7.gemfile +3 -3
- data/gemfiles/data_mapper-0.9.7.gemfile.lock +13 -13
- data/gemfiles/data_mapper-1.0.0.gemfile +3 -3
- data/gemfiles/data_mapper-1.0.0.gemfile.lock +17 -8
- data/gemfiles/data_mapper-1.0.1.gemfile +3 -3
- data/gemfiles/data_mapper-1.0.1.gemfile.lock +17 -8
- data/gemfiles/data_mapper-1.0.2.gemfile +3 -3
- data/gemfiles/data_mapper-1.0.2.gemfile.lock +17 -8
- data/gemfiles/data_mapper-1.1.0.gemfile +3 -3
- data/gemfiles/data_mapper-1.1.0.gemfile.lock +17 -8
- data/gemfiles/data_mapper-1.2.0.gemfile +3 -3
- data/gemfiles/data_mapper-1.2.0.gemfile.lock +13 -4
- data/gemfiles/default.gemfile.lock +7 -5
- data/gemfiles/graphviz-0.9.0.gemfile.lock +6 -4
- data/gemfiles/graphviz-0.9.21.gemfile.lock +6 -4
- data/gemfiles/graphviz-1.0.0.gemfile.lock +6 -4
- data/gemfiles/mongo_mapper-0.10.0.gemfile.lock +6 -3
- data/gemfiles/mongo_mapper-0.5.5.gemfile +1 -1
- data/gemfiles/mongo_mapper-0.5.5.gemfile.lock +7 -5
- data/gemfiles/mongo_mapper-0.5.8.gemfile +1 -1
- data/gemfiles/mongo_mapper-0.5.8.gemfile.lock +7 -5
- data/gemfiles/mongo_mapper-0.6.0.gemfile +1 -1
- data/gemfiles/mongo_mapper-0.6.0.gemfile.lock +7 -5
- data/gemfiles/mongo_mapper-0.6.10.gemfile +1 -1
- data/gemfiles/mongo_mapper-0.6.10.gemfile.lock +7 -5
- data/gemfiles/mongo_mapper-0.7.0.gemfile +1 -1
- data/gemfiles/mongo_mapper-0.7.0.gemfile.lock +7 -5
- data/gemfiles/mongo_mapper-0.7.5.gemfile +1 -1
- data/gemfiles/mongo_mapper-0.7.5.gemfile.lock +7 -5
- data/gemfiles/mongo_mapper-0.8.0.gemfile +2 -2
- data/gemfiles/mongo_mapper-0.8.0.gemfile.lock +7 -5
- data/gemfiles/mongo_mapper-0.8.3.gemfile +2 -2
- data/gemfiles/mongo_mapper-0.8.3.gemfile.lock +7 -5
- data/gemfiles/mongo_mapper-0.8.4.gemfile +1 -1
- data/gemfiles/mongo_mapper-0.8.4.gemfile.lock +11 -8
- data/gemfiles/mongo_mapper-0.8.6.gemfile +1 -1
- data/gemfiles/mongo_mapper-0.8.6.gemfile.lock +11 -8
- data/gemfiles/mongo_mapper-0.9.0.gemfile.lock +14 -11
- data/gemfiles/mongoid-2.0.0.gemfile.lock +22 -17
- data/gemfiles/mongoid-2.1.4.gemfile.lock +21 -16
- data/gemfiles/mongoid-2.2.4.gemfile.lock +11 -8
- data/gemfiles/mongoid-2.3.3.gemfile.lock +11 -8
- data/gemfiles/sequel-2.11.0.gemfile.lock +7 -5
- data/gemfiles/sequel-2.12.0.gemfile.lock +7 -5
- data/gemfiles/sequel-2.8.0.gemfile.lock +7 -5
- data/gemfiles/sequel-3.0.0.gemfile.lock +7 -5
- data/gemfiles/sequel-3.13.0.gemfile.lock +7 -5
- data/gemfiles/sequel-3.14.0.gemfile.lock +7 -5
- data/gemfiles/sequel-3.23.0.gemfile.lock +7 -5
- data/gemfiles/sequel-3.24.0.gemfile.lock +7 -5
- data/gemfiles/sequel-3.29.0.gemfile.lock +5 -3
- data/lib/state_machine.rb +7 -1
- data/lib/state_machine/eval_helpers.rb +8 -9
- data/lib/state_machine/event.rb +10 -2
- data/lib/state_machine/integrations.rb +0 -1
- data/lib/state_machine/integrations/active_model.rb +46 -35
- data/lib/state_machine/integrations/active_record.rb +8 -0
- data/lib/state_machine/integrations/active_record/versions.rb +0 -20
- data/lib/state_machine/integrations/data_mapper.rb +22 -21
- data/lib/state_machine/integrations/data_mapper/versions.rb +0 -27
- data/lib/state_machine/integrations/mongo_mapper.rb +8 -0
- data/lib/state_machine/integrations/mongo_mapper/versions.rb +0 -4
- data/lib/state_machine/integrations/mongoid.rb +15 -2
- data/lib/state_machine/integrations/mongoid/versions.rb +0 -7
- data/lib/state_machine/integrations/sequel.rb +14 -0
- data/lib/state_machine/machine.rb +12 -0
- data/lib/state_machine/state.rb +4 -3
- data/lib/state_machine/state_context.rb +10 -9
- data/lib/state_machine/transition.rb +6 -1
- data/lib/state_machine/version.rb +1 -1
- data/state_machine.gemspec +1 -1
- data/test/functional/state_machine_test.rb +21 -1
- data/test/unit/event_test.rb +10 -0
- data/test/unit/integrations/active_model_test.rb +31 -29
- data/test/unit/integrations/active_record_test.rb +56 -26
- data/test/unit/integrations/data_mapper_test.rb +34 -57
- data/test/unit/integrations/mongo_mapper_test.rb +30 -24
- data/test/unit/integrations/mongoid_test.rb +114 -29
- data/test/unit/integrations/sequel_test.rb +36 -0
- data/test/unit/invalid_transition_test.rb +38 -0
- data/test/unit/machine_test.rb +38 -0
- data/test/unit/state_context_test.rb +29 -9
- data/test/unit/state_test.rb +21 -1
- data/test/unit/transition_collection_test.rb +72 -26
- data/test/unit/transition_test.rb +84 -73
- 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.
|
4
|
+
state_machine (1.0.3)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: http://www.rubygems.org/
|
8
8
|
specs:
|
9
|
-
appraisal (0.
|
9
|
+
appraisal (0.4.0)
|
10
10
|
bundler
|
11
11
|
rake
|
12
|
-
rake (0.9.2)
|
13
|
-
rcov (0.9.
|
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.
|
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.
|
4
|
+
state_machine (1.0.3)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: http://www.rubygems.org/
|
8
8
|
specs:
|
9
|
-
appraisal (0.
|
9
|
+
appraisal (0.4.0)
|
10
10
|
bundler
|
11
11
|
rake
|
12
|
-
rake (0.9.2)
|
13
|
-
rcov (0.9.
|
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.
|
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.
|
4
|
+
state_machine (1.0.3)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: http://www.rubygems.org/
|
8
8
|
specs:
|
9
|
-
appraisal (0.
|
9
|
+
appraisal (0.4.0)
|
10
10
|
bundler
|
11
11
|
rake
|
12
|
-
rake (0.9.2)
|
13
|
-
rcov (0.9.
|
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.
|
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.
|
4
|
+
state_machine (1.0.3)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: http://www.rubygems.org/
|
8
8
|
specs:
|
9
|
-
appraisal (0.
|
9
|
+
appraisal (0.4.0)
|
10
10
|
bundler
|
11
11
|
rake
|
12
|
-
rake (0.9.2)
|
13
|
-
rcov (0.9.
|
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.
|
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.
|
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.
|
23
|
+
appraisal (~> 0.4.0)
|
22
24
|
rake
|
23
25
|
rcov
|
24
26
|
sequel (= 3.29.0)
|
data/lib/state_machine.rb
CHANGED
@@ -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.
|
63
|
-
# argument for consistency across versions of Ruby
|
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
|
-
|
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
|
-
|
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
|
77
|
+
method.call(*args, &block)
|
79
78
|
when String
|
80
79
|
eval(method, object.instance_eval {binding}, &block)
|
81
80
|
else
|
data/lib/state_machine/event.rb
CHANGED
@@ -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
|
-
|
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.
|
@@ -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
|
-
#
|
283
|
-
#
|
284
|
-
#
|
285
|
-
#
|
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
|
-
#
|
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::
|
353
|
-
#
|
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(
|
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
|