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.
- 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
|