aasm 3.0.26 → 3.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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/CHANGELOG.md +9 -1
- data/README.md +56 -4
- data/aasm.gemspec +1 -1
- data/lib/aasm/base.rb +21 -15
- data/lib/aasm/event.rb +15 -4
- data/lib/aasm/instance_base.rb +2 -0
- data/lib/aasm/persistence/active_record_persistence.rb +17 -1
- data/lib/aasm/transition.rb +13 -6
- data/lib/aasm/version.rb +1 -1
- data/spec/models/guardian.rb +48 -0
- data/spec/unit/api_spec.rb +12 -12
- data/spec/unit/callbacks_spec.rb +25 -25
- data/spec/unit/complex_example_spec.rb +15 -15
- data/spec/unit/event_spec.rb +44 -44
- data/spec/unit/guard_spec.rb +60 -0
- data/spec/unit/initial_state_spec.rb +3 -3
- data/spec/unit/inspection_spec.rb +36 -36
- data/spec/unit/localizer_spec.rb +12 -12
- data/spec/unit/new_dsl_spec.rb +2 -2
- data/spec/unit/persistence/active_record_persistence_spec.rb +107 -67
- data/spec/unit/persistence/mongoid_persistance_spec.rb +24 -24
- data/spec/unit/simple_example_spec.rb +20 -20
- data/spec/unit/state_spec.rb +16 -16
- data/spec/unit/subclassing_spec.rb +6 -6
- data/spec/unit/transition_spec.rb +55 -40
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 624aadcd0801dd7a8fb367c00d29ea7f5dccd6f3
|
4
|
+
data.tar.gz: 8f4d0f3978038a5a16fbe2696efbb1bb1f173603
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9359ea9f090c25f5ae789f3ef47e3ad8829ebf7074e23cb9fee9738537f9207e96a9c5e6ddbbe592c02b2d80aac18df7673aa3427735b0766b6e9a34d4c62dd9
|
7
|
+
data.tar.gz: ddbfc65e9019d92b165f75ebf170c68682e829ed969d9c743df02dabaa03bc53ca6397d5d5b3bb5a8ebe66cae53621cefd8f74bffc4890536638112a6856bf88
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -4,9 +4,17 @@
|
|
4
4
|
|
5
5
|
* deprecated old aasm_* class methods (old-style DSL), in preparation for AASM v4.0.0
|
6
6
|
|
7
|
+
## 3.1.0
|
8
|
+
|
9
|
+
* validating the current state (see [issue #95](https://github.com/aasm/aasm/issues/95), thanks to [@ivantsepp](https://github.com/ivantsepp))
|
10
|
+
* allow configuring behavior of nested transactions (see [issue #107](https://github.com/aasm/aasm/issues/107))
|
11
|
+
* support multiple guards per transition
|
12
|
+
* support event guards (see [issue #85](https://github.com/aasm/aasm/issues/85))
|
13
|
+
* support reading from- and to-state during on_transition callback (see [issue #100](https://github.com/aasm/aasm/issues/100))
|
14
|
+
|
7
15
|
## 3.0.26
|
8
16
|
|
9
|
-
* support state.human_name (aliased to state.localized_name) (see [issue #105](https://github.com/aasm/aasm/issues/105)
|
17
|
+
* support state.human_name (aliased to state.localized_name) (see [issue #105](https://github.com/aasm/aasm/issues/105))
|
10
18
|
|
11
19
|
## 3.0.25
|
12
20
|
|
data/README.md
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# AASM - Ruby state machines
|
2
2
|
|
3
|
-
|
3
|
+
<a href="http://badge.fury.io/rb/aasm"><img src="https://badge.fury.io/rb/aasm@2x.png" alt="Gem Version" height="18"></a>
|
4
|
+
[](http://travis-ci.org/aasm/aasm)
|
5
|
+
[](https://codeclimate.com/github/aasm/aasm)
|
6
|
+
[](https://coveralls.io/r/aasm/aasm)
|
4
7
|
|
5
8
|
This package contains AASM, a library for adding finite state machines to Ruby classes.
|
6
9
|
|
@@ -91,7 +94,7 @@ class Job
|
|
91
94
|
state :sleeping, :initial => true, :before_enter => :do_something
|
92
95
|
state :running
|
93
96
|
|
94
|
-
event :run, :after => Proc.new {
|
97
|
+
event :run, :after => Proc.new { do_afterwards } do
|
95
98
|
transitions :from => :sleeping, :to => :running, :on_transition => Proc.new {|obj, *args| obj.set_process(*args) }
|
96
99
|
end
|
97
100
|
|
@@ -114,7 +117,7 @@ class Job
|
|
114
117
|
...
|
115
118
|
end
|
116
119
|
|
117
|
-
def
|
120
|
+
def do_afterwards
|
118
121
|
...
|
119
122
|
end
|
120
123
|
|
@@ -122,7 +125,7 @@ end
|
|
122
125
|
```
|
123
126
|
|
124
127
|
In this case `do_something` is called before actually entering the state `sleeping`,
|
125
|
-
while `
|
128
|
+
while `do_afterwards` is called after the transition `run` (from `sleeping` to `running`)
|
126
129
|
is finished.
|
127
130
|
|
128
131
|
Here you can see a list of all possible callbacks, together with their order of calling:
|
@@ -149,6 +152,15 @@ In this case the `set_process` would be called with `:defagmentation` argument.
|
|
149
152
|
In case of an error during the event processing the error is rescued and passed to `:error`
|
150
153
|
callback, which can handle it or re-raise it for further propagation.
|
151
154
|
|
155
|
+
During the `:on_transition` callback (and reliably only then) you can access the
|
156
|
+
originating state (the from-state) and the target state (the to state), like this:
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
def set_process(name)
|
160
|
+
logger.info "from #{aasm.from_state} to #{aasm.to_state}"
|
161
|
+
end
|
162
|
+
```
|
163
|
+
|
152
164
|
### Guards
|
153
165
|
|
154
166
|
Let's assume you want to allow particular transitions only if a defined condition is
|
@@ -190,6 +202,24 @@ job.may_sleep? # => false
|
|
190
202
|
job.sleep # => raises AASM::InvalidTransition
|
191
203
|
```
|
192
204
|
|
205
|
+
You can even provide a number of guards, which all have to succeed to proceed
|
206
|
+
|
207
|
+
```ruby
|
208
|
+
def walked_the_dog?; ...; end
|
209
|
+
|
210
|
+
event :sleep do
|
211
|
+
transitions :from => :running, :to => :sleeping, :guards => [:cleaning_needed?, :walked_the_dog?]
|
212
|
+
end
|
213
|
+
```
|
214
|
+
|
215
|
+
If you want to provide guards for all transitions withing an event, you can use event guards
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
event :sleep, :guards => [:walked_the_dog?] do
|
219
|
+
transitions :from => :running, :to => :sleeping, :guards => [:cleaning_needed?]
|
220
|
+
transitions :from => :cleaning, :to => :sleeping
|
221
|
+
end
|
222
|
+
```
|
193
223
|
|
194
224
|
### ActiveRecord
|
195
225
|
|
@@ -339,6 +369,28 @@ class Job < ActiveRecord::Base
|
|
339
369
|
end
|
340
370
|
```
|
341
371
|
|
372
|
+
If you want to encapsulate state changes within an own transaction, the behavior
|
373
|
+
of this nested transaction might be confusing. Take a look at
|
374
|
+
[ActiveRecord Nested Transactions](http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html)
|
375
|
+
if you want to know more about this. Nevertheless, AASM by default requires a new transaction
|
376
|
+
`transaction(:requires_new => true)`. You can override this behavior by changing
|
377
|
+
the configuration
|
378
|
+
|
379
|
+
```ruby
|
380
|
+
class Job < ActiveRecord::Base
|
381
|
+
include AASM
|
382
|
+
|
383
|
+
aasm :requires_new_transaction => false do
|
384
|
+
...
|
385
|
+
end
|
386
|
+
|
387
|
+
...
|
388
|
+
end
|
389
|
+
```
|
390
|
+
|
391
|
+
which then leads to `transaction(:requires_new => false)`, the Rails default.
|
392
|
+
|
393
|
+
|
342
394
|
### Column name & migration
|
343
395
|
|
344
396
|
As a default AASM uses the column `aasm_state` to store the states. You can override
|
data/aasm.gemspec
CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
|
|
19
19
|
# s.add_development_dependency 'mongoid' if Gem::Version.create(RUBY_VERSION.dup) >= Gem::Version.create('1.9.3')
|
20
20
|
s.add_development_dependency 'rake'
|
21
21
|
s.add_development_dependency 'sdoc'
|
22
|
-
s.add_development_dependency 'rspec', '
|
22
|
+
s.add_development_dependency 'rspec', '>= 2.14'
|
23
23
|
s.add_development_dependency 'rr'
|
24
24
|
# s.add_development_dependency 'sqlite3'
|
25
25
|
s.add_development_dependency 'minitest'
|
data/lib/aasm/base.rb
CHANGED
@@ -5,24 +5,19 @@ module AASM
|
|
5
5
|
@clazz = clazz
|
6
6
|
@state_machine = AASM::StateMachine[@clazz]
|
7
7
|
@state_machine.config.column = options[:column].to_sym if options[:column]
|
8
|
+
@options = options
|
8
9
|
|
9
|
-
if
|
10
|
-
|
11
|
-
elsif @state_machine.config.whiny_transitions.nil?
|
12
|
-
@state_machine.config.whiny_transitions = true # this is the default, so let's cry
|
13
|
-
end
|
10
|
+
# let's cry if the transition is invalid
|
11
|
+
configure :whiny_transitions, true
|
14
12
|
|
15
|
-
|
16
|
-
|
17
|
-
elsif @state_machine.config.create_scopes.nil?
|
18
|
-
@state_machine.config.create_scopes = true # this is the default, so let's create scopes
|
19
|
-
end
|
13
|
+
# create named scopes for each state
|
14
|
+
configure :create_scopes, true
|
20
15
|
|
21
|
-
if
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
16
|
+
# don't store any new state if the model is invalid
|
17
|
+
configure :skip_validation_on_save, false
|
18
|
+
|
19
|
+
# use requires_new for nested transactions
|
20
|
+
configure :requires_new_transaction, true
|
26
21
|
end
|
27
22
|
|
28
23
|
def initial_state(new_initial_state=nil)
|
@@ -87,5 +82,16 @@ module AASM
|
|
87
82
|
end
|
88
83
|
end
|
89
84
|
|
85
|
+
private
|
86
|
+
|
87
|
+
def configure(key, default_value)
|
88
|
+
@state_machine.config.send(:new_ostruct_member, key)
|
89
|
+
if @options.key?(key)
|
90
|
+
@state_machine.config.send("#{key}=", @options[key])
|
91
|
+
elsif @state_machine.config.send(key).nil?
|
92
|
+
@state_machine.config.send("#{key}=", default_value)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
90
96
|
end
|
91
97
|
end
|
data/lib/aasm/event.rb
CHANGED
@@ -6,6 +6,7 @@ module AASM
|
|
6
6
|
def initialize(name, options = {}, &block)
|
7
7
|
@name = name
|
8
8
|
@transitions = []
|
9
|
+
@guards = Array(options[:guard] || options[:guards])
|
9
10
|
update(options, &block)
|
10
11
|
end
|
11
12
|
|
@@ -57,18 +58,28 @@ module AASM
|
|
57
58
|
## DSL interface
|
58
59
|
def transitions(definitions=nil)
|
59
60
|
if definitions # define new transitions
|
60
|
-
# Create a separate transition for each from
|
61
|
+
# Create a separate transition for each from-state to the given state
|
61
62
|
Array(definitions[:from]).each do |s|
|
62
|
-
@transitions << AASM::Transition.new(definitions.merge(
|
63
|
+
@transitions << AASM::Transition.new(attach_event_guards(definitions.merge(:from => s.to_sym)))
|
64
|
+
end
|
65
|
+
# Create a transition if :to is specified without :from (transitions from ANY state)
|
66
|
+
if @transitions.empty? && definitions[:to]
|
67
|
+
@transitions << AASM::Transition.new(attach_event_guards(definitions))
|
63
68
|
end
|
64
|
-
# Create a transition if to is specified without from (transitions from ANY state)
|
65
|
-
@transitions << AASM::Transition.new(definitions) if @transitions.empty? && definitions[:to]
|
66
69
|
end
|
67
70
|
@transitions
|
68
71
|
end
|
69
72
|
|
70
73
|
private
|
71
74
|
|
75
|
+
def attach_event_guards(definitions)
|
76
|
+
unless @guards.empty?
|
77
|
+
given_guards = Array(definitions.delete(:guard) || definitions.delete(:guards))
|
78
|
+
definitions[:guards] = given_guards + @guards
|
79
|
+
end
|
80
|
+
definitions
|
81
|
+
end
|
82
|
+
|
72
83
|
def update(options = {}, &block)
|
73
84
|
@options = options
|
74
85
|
if block then
|
data/lib/aasm/instance_base.rb
CHANGED
@@ -36,8 +36,12 @@ module AASM
|
|
36
36
|
else
|
37
37
|
base.before_validation_on_create(:aasm_ensure_initial_state)
|
38
38
|
end
|
39
|
+
|
39
40
|
# ensure initial aasm state even when validations are skipped
|
40
41
|
base.before_create(:aasm_ensure_initial_state)
|
42
|
+
|
43
|
+
# ensure state is in the list of states
|
44
|
+
base.validate :aasm_validate_states
|
41
45
|
end
|
42
46
|
|
43
47
|
module ClassMethods
|
@@ -134,7 +138,7 @@ module AASM
|
|
134
138
|
end
|
135
139
|
|
136
140
|
def aasm_fire_event(name, options, *args, &block)
|
137
|
-
success = self.class.transaction(:requires_new =>
|
141
|
+
success = self.class.transaction(:requires_new => requires_new?) do
|
138
142
|
super
|
139
143
|
end
|
140
144
|
|
@@ -145,6 +149,18 @@ module AASM
|
|
145
149
|
|
146
150
|
success
|
147
151
|
end
|
152
|
+
|
153
|
+
def requires_new?
|
154
|
+
AASM::StateMachine[self.class].config.requires_new_transaction
|
155
|
+
end
|
156
|
+
|
157
|
+
def aasm_validate_states
|
158
|
+
unless AASM::StateMachine[self.class].config.skip_validation_on_save
|
159
|
+
if aasm.current_state && !aasm.states.include?(aasm.current_state)
|
160
|
+
self.errors.add(AASM::StateMachine[self.class].config.column , "is invalid")
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
148
164
|
end # InstanceMethods
|
149
165
|
|
150
166
|
end
|
data/lib/aasm/transition.rb
CHANGED
@@ -4,20 +4,24 @@ module AASM
|
|
4
4
|
alias_method :options, :opts
|
5
5
|
|
6
6
|
def initialize(opts)
|
7
|
-
@from
|
7
|
+
@from = opts[:from]
|
8
|
+
@to = opts[:to]
|
9
|
+
@guards = Array(opts[:guard] || opts[:guards])
|
10
|
+
@on_transition = opts[:on_transition]
|
8
11
|
@opts = opts
|
9
12
|
end
|
10
13
|
|
11
14
|
# TODO: should be named allowed? or similar
|
12
15
|
def perform(obj, *args)
|
13
|
-
|
16
|
+
@guards.each do |guard|
|
17
|
+
case guard
|
14
18
|
when Symbol, String
|
15
|
-
obj.send(
|
19
|
+
return false unless obj.send(guard, *args)
|
16
20
|
when Proc
|
17
|
-
|
18
|
-
|
19
|
-
true
|
21
|
+
return false unless guard.call(obj, *args)
|
22
|
+
end
|
20
23
|
end
|
24
|
+
true
|
21
25
|
end
|
22
26
|
|
23
27
|
def execute(obj, *args)
|
@@ -37,6 +41,9 @@ module AASM
|
|
37
41
|
private
|
38
42
|
|
39
43
|
def _execute(obj, on_transition, *args)
|
44
|
+
obj.aasm.from_state = @from if obj.aasm.respond_to?(:from_state=)
|
45
|
+
obj.aasm.to_state = @to if obj.aasm.respond_to?(:to_state=)
|
46
|
+
|
40
47
|
case on_transition
|
41
48
|
when Proc
|
42
49
|
on_transition.arity == 0 ? on_transition.call : on_transition.call(obj, *args)
|
data/lib/aasm/version.rb
CHANGED
@@ -0,0 +1,48 @@
|
|
1
|
+
class Guardian
|
2
|
+
include AASM
|
3
|
+
|
4
|
+
aasm do
|
5
|
+
state :alpha, :initial => true
|
6
|
+
state :beta
|
7
|
+
|
8
|
+
event :use_one_guard_that_succeeds do
|
9
|
+
transitions :from => :alpha, :to => :beta, :guard => :succeed
|
10
|
+
end
|
11
|
+
event :use_one_guard_that_fails do
|
12
|
+
transitions :from => :alpha, :to => :beta, :guard => :fail
|
13
|
+
end
|
14
|
+
|
15
|
+
event :use_guards_that_succeed do
|
16
|
+
transitions :from => :alpha, :to => :beta, :guards => [:succeed, :another_succeed]
|
17
|
+
end
|
18
|
+
event :use_guards_where_the_first_fails do
|
19
|
+
transitions :from => :alpha, :to => :beta, :guards => [:succeed, :fail]
|
20
|
+
end
|
21
|
+
event :use_guards_where_the_second_fails do
|
22
|
+
transitions :from => :alpha, :to => :beta, :guards => [:fail, :succeed]
|
23
|
+
end
|
24
|
+
|
25
|
+
event :use_event_guards_that_succeed, :guards => [:succeed, :another_succeed] do
|
26
|
+
transitions :from => :alpha, :to => :beta
|
27
|
+
end
|
28
|
+
event :use_event_and_transition_guards_that_succeed, :guards => [:succeed, :another_succeed] do
|
29
|
+
transitions :from => :alpha, :to => :beta, :guards => [:more_succeed]
|
30
|
+
end
|
31
|
+
event :use_event_guards_where_the_first_fails, :guards => [:succeed, :fail] do
|
32
|
+
transitions :from => :alpha, :to => :beta
|
33
|
+
end
|
34
|
+
event :use_event_guards_where_the_second_fails, :guards => [:fail, :succeed] do
|
35
|
+
transitions :from => :alpha, :to => :beta, :guard => :another_succeed
|
36
|
+
end
|
37
|
+
event :use_event_and_transition_guards_where_third_fails, :guards => [:succeed, :another_succeed] do
|
38
|
+
transitions :from => :alpha, :to => :beta, :guards => [:fail]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def fail; false; end
|
43
|
+
|
44
|
+
def succeed; true; end
|
45
|
+
def another_succeed; true; end
|
46
|
+
def more_succeed; true; end
|
47
|
+
|
48
|
+
end
|
data/spec/unit/api_spec.rb
CHANGED
@@ -3,19 +3,19 @@ require 'models/active_record/api.rb'
|
|
3
3
|
|
4
4
|
describe "reading the current state" do
|
5
5
|
it "uses the AASM default" do
|
6
|
-
DefaultState.new.aasm.current_state.
|
6
|
+
expect(DefaultState.new.aasm.current_state).to eql :alpha
|
7
7
|
end
|
8
8
|
|
9
9
|
it "uses the provided method" do
|
10
|
-
ProvidedState.new.aasm.current_state.
|
10
|
+
expect(ProvidedState.new.aasm.current_state).to eql :beta
|
11
11
|
end
|
12
12
|
|
13
13
|
it "uses the persistence storage" do
|
14
|
-
PersistedState.new.aasm.current_state.
|
14
|
+
expect(PersistedState.new.aasm.current_state).to eql :alpha
|
15
15
|
end
|
16
16
|
|
17
17
|
it "uses the provided method even if persisted" do
|
18
|
-
ProvidedAndPersistedState.new.aasm.current_state.
|
18
|
+
expect(ProvidedAndPersistedState.new.aasm.current_state).to eql :gamma
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
@@ -23,25 +23,25 @@ describe "writing and persisting the current state" do
|
|
23
23
|
it "uses the AASM default" do
|
24
24
|
o = DefaultState.new
|
25
25
|
o.release!
|
26
|
-
o.persisted_store.
|
26
|
+
expect(o.persisted_store).to be_nil
|
27
27
|
end
|
28
28
|
|
29
29
|
it "uses the provided method" do
|
30
30
|
o = ProvidedState.new
|
31
31
|
o.release!
|
32
|
-
o.persisted_store.
|
32
|
+
expect(o.persisted_store).to eql :beta
|
33
33
|
end
|
34
34
|
|
35
35
|
it "uses the persistence storage" do
|
36
36
|
o = PersistedState.new
|
37
37
|
o.release!
|
38
|
-
o.persisted_store.
|
38
|
+
expect(o.persisted_store).to be_nil
|
39
39
|
end
|
40
40
|
|
41
41
|
it "uses the provided method even if persisted" do
|
42
42
|
o = ProvidedAndPersistedState.new
|
43
43
|
o.release!
|
44
|
-
o.persisted_store.
|
44
|
+
expect(o.persisted_store).to eql :beta
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
@@ -49,24 +49,24 @@ describe "writing the current state without persisting it" do
|
|
49
49
|
it "uses the AASM default" do
|
50
50
|
o = DefaultState.new
|
51
51
|
o.release
|
52
|
-
o.transient_store.
|
52
|
+
expect(o.transient_store).to be_nil
|
53
53
|
end
|
54
54
|
|
55
55
|
it "uses the provided method" do
|
56
56
|
o = ProvidedState.new
|
57
57
|
o.release
|
58
|
-
o.transient_store.
|
58
|
+
expect(o.transient_store).to eql :beta
|
59
59
|
end
|
60
60
|
|
61
61
|
it "uses the persistence storage" do
|
62
62
|
o = PersistedState.new
|
63
63
|
o.release
|
64
|
-
o.transient_store.
|
64
|
+
expect(o.transient_store).to be_nil
|
65
65
|
end
|
66
66
|
|
67
67
|
it "uses the provided method even if persisted" do
|
68
68
|
o = ProvidedAndPersistedState.new
|
69
69
|
o.release
|
70
|
-
o.transient_store.
|
70
|
+
expect(o.transient_store).to eql :beta
|
71
71
|
end
|
72
72
|
end
|