aasm 3.0.16 → 3.0.17
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/.gitignore +1 -0
- data/.travis.yml +2 -1
- data/API +34 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +1 -1
- data/HOWTO +12 -0
- data/README.md +57 -4
- data/aasm.gemspec +2 -0
- data/lib/aasm.rb +5 -4
- data/lib/aasm/aasm.rb +50 -75
- data/lib/aasm/base.rb +22 -18
- data/lib/aasm/event.rb +130 -0
- data/lib/aasm/instance_base.rb +87 -0
- data/lib/aasm/localizer.rb +54 -0
- data/lib/aasm/persistence.rb +22 -14
- data/lib/aasm/persistence/active_record_persistence.rb +38 -69
- data/lib/aasm/persistence/base.rb +42 -2
- data/lib/aasm/persistence/mongoid_persistence.rb +33 -64
- data/lib/aasm/state.rb +78 -0
- data/lib/aasm/state_machine.rb +2 -2
- data/lib/aasm/transition.rb +49 -0
- data/lib/aasm/version.rb +1 -1
- data/spec/models/active_record/api.rb +75 -0
- data/spec/models/auth_machine.rb +1 -1
- data/spec/models/bar.rb +15 -0
- data/spec/models/foo.rb +34 -0
- data/spec/models/mongoid/simple_mongoid.rb +10 -0
- data/spec/models/mongoid/{mongoid_models.rb → simple_new_dsl_mongoid.rb} +1 -12
- data/spec/models/persistence.rb +2 -1
- data/spec/models/this_name_better_not_be_in_use.rb +11 -0
- data/spec/schema.rb +1 -1
- data/spec/spec_helper.rb +8 -1
- data/spec/unit/api_spec.rb +72 -0
- data/spec/unit/callbacks_spec.rb +2 -2
- data/spec/unit/event_spec.rb +269 -0
- data/spec/unit/inspection_spec.rb +43 -5
- data/spec/unit/{supporting_classes/localizer_spec.rb → localizer_spec.rb} +2 -2
- data/spec/unit/memory_leak_spec.rb +12 -12
- data/spec/unit/persistence/active_record_persistence_spec.rb +0 -40
- data/spec/unit/persistence/mongoid_persistance_spec.rb +3 -2
- data/spec/unit/simple_example_spec.rb +6 -0
- data/spec/unit/{supporting_classes/state_spec.rb → state_spec.rb} +2 -2
- data/spec/unit/{supporting_classes/state_transition_spec.rb → transition_spec.rb} +18 -18
- metadata +127 -38
- data/lib/aasm/persistence/read_state.rb +0 -40
- data/lib/aasm/supporting_classes/event.rb +0 -146
- data/lib/aasm/supporting_classes/localizer.rb +0 -56
- data/lib/aasm/supporting_classes/state.rb +0 -80
- data/lib/aasm/supporting_classes/state_transition.rb +0 -51
- data/spec/spec_helpers/models_spec_helper.rb +0 -64
- data/spec/unit/supporting_classes/event_spec.rb +0 -203
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/API
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
Overwrite method to read the current state. Used to provide another storage mechanism,
|
3
|
+
different from the standard Rails read_attribute method.
|
4
|
+
|
5
|
+
class MyClass
|
6
|
+
include AASM
|
7
|
+
|
8
|
+
def aasm_read_state
|
9
|
+
# retrieve the current state manually
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
Overwrite method to write the current state (and actually persist it). Used to provide
|
15
|
+
another storage mechanism, different from the standard Rails write_attribute method.
|
16
|
+
|
17
|
+
class MyClass
|
18
|
+
include AASM
|
19
|
+
|
20
|
+
def aasm_write_state
|
21
|
+
# store and persist the current state manually
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
Overwrite method to write the current state (without persisting it).
|
27
|
+
|
28
|
+
class MyClass
|
29
|
+
include AASM
|
30
|
+
|
31
|
+
def aasm_write_state_without_persistence
|
32
|
+
# store the current state manually
|
33
|
+
end
|
34
|
+
end
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## Unreleased
|
4
|
+
|
5
|
+
## 3.0.17
|
6
|
+
|
7
|
+
* supporting instance level inspection for states (including permissible state, see issue #54)
|
8
|
+
* added autocreation of constants for each state ([@jherdman](https://github.com/jherdman))
|
9
|
+
|
3
10
|
## 3.0.16
|
4
11
|
|
5
12
|
* added autocreation of state scopes for Mongoid (thanks to [@jonnyshields](https://github.com/johnnyshields))
|
data/Gemfile
CHANGED
data/HOWTO
ADDED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# AASM - Ruby state machines [](http://travis-ci.org/aasm/aasm) [](http://travis-ci.org/aasm/aasm) [](https://codeclimate.com/github/aasm/aasm) [](https://coveralls.io/r/aasm/aasm)
|
2
2
|
|
3
3
|
This package contains AASM, a library for adding finite state machines to Ruby classes.
|
4
4
|
|
@@ -81,14 +81,24 @@ class Job
|
|
81
81
|
state :running
|
82
82
|
|
83
83
|
event :run, :after => :notify_somebody do
|
84
|
-
transitions :from => :sleeping, :to => :running
|
84
|
+
transitions :from => :sleeping, :to => :running, :on_transition => Proc.new {|obj, *args| obj.set_process(*args) }
|
85
85
|
end
|
86
86
|
|
87
87
|
event :sleep do
|
88
|
+
after do
|
89
|
+
...
|
90
|
+
end
|
91
|
+
error do |e|
|
92
|
+
...
|
93
|
+
end
|
88
94
|
transitions :from => :running, :to => :sleeping
|
89
95
|
end
|
90
96
|
end
|
91
97
|
|
98
|
+
def set_process(name)
|
99
|
+
...
|
100
|
+
end
|
101
|
+
|
92
102
|
def do_something
|
93
103
|
...
|
94
104
|
end
|
@@ -116,6 +126,18 @@ Here you can see a list of all possible callbacks, together with their order of
|
|
116
126
|
event:after
|
117
127
|
```
|
118
128
|
|
129
|
+
Also, you can pass parameters to events:
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
job = Job.new
|
133
|
+
job.run(:running, :defragmentation)
|
134
|
+
```
|
135
|
+
|
136
|
+
In this case the `set_process` would be called with `:defagmentation` argument.
|
137
|
+
|
138
|
+
In case of an error during the event processing the error is rescued and passed to `:error`
|
139
|
+
callback, which can handle it or re-raise it for further propagation.
|
140
|
+
|
119
141
|
### Guards
|
120
142
|
|
121
143
|
Let's assume you want to allow particular transitions only if a defined condition is
|
@@ -277,12 +299,36 @@ class AddJobState < ActiveRecord::Migration
|
|
277
299
|
end
|
278
300
|
|
279
301
|
def self.down
|
280
|
-
remove_column :
|
302
|
+
remove_column :jobs, :aasm_state
|
281
303
|
end
|
282
304
|
end
|
283
305
|
```
|
284
306
|
|
285
|
-
|
307
|
+
### <a id="inspection">Inspection
|
308
|
+
|
309
|
+
AASM supports a couple of methods to find out which states or events are provided or permissible.
|
310
|
+
|
311
|
+
Given the `Job` class from above:
|
312
|
+
|
313
|
+
```ruby
|
314
|
+
job = Job.new
|
315
|
+
|
316
|
+
job.states
|
317
|
+
=> [:sleeping, :running, :cleaning]
|
318
|
+
|
319
|
+
job.states(:permissible => true)
|
320
|
+
=> [:running]
|
321
|
+
job.run
|
322
|
+
job.states(:permissible => true)
|
323
|
+
=> [:cleaning, :sleeping]
|
324
|
+
|
325
|
+
job.events
|
326
|
+
=> [:run, :clean, :sleep]
|
327
|
+
```
|
328
|
+
|
329
|
+
|
330
|
+
|
331
|
+
## <a id="installation">Installation ##
|
286
332
|
|
287
333
|
### Manually from RubyGems.org ###
|
288
334
|
|
@@ -308,6 +354,13 @@ gem 'aasm'
|
|
308
354
|
|
309
355
|
Look at the [CHANGELOG](https://github.com/aasm/aasm/blob/master/CHANGELOG.md) for details.
|
310
356
|
|
357
|
+
## Questions? ##
|
358
|
+
|
359
|
+
Feel free to
|
360
|
+
|
361
|
+
* [create an issue on GitHub](https://github.com/aasm/aasm/issues)
|
362
|
+
* [ask a question on StackOverflow](http://stackoverflow.com) (tag with `aasm`)
|
363
|
+
* send us a tweet [@aasm](http://twitter.com/aasm)
|
311
364
|
|
312
365
|
## Authors ##
|
313
366
|
|
data/aasm.gemspec
CHANGED
@@ -23,7 +23,9 @@ Gem::Specification.new do |s|
|
|
23
23
|
s.add_development_dependency 'sqlite3'
|
24
24
|
s.add_development_dependency 'minitest'
|
25
25
|
# s.add_development_dependency 'debugger'
|
26
|
+
# s.add_development_dependency 'pry'
|
26
27
|
s.add_development_dependency 'ruby-debug-completion'
|
28
|
+
s.add_development_dependency 'coveralls'
|
27
29
|
|
28
30
|
s.files = `git ls-files`.split("\n")
|
29
31
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
data/lib/aasm.rb
CHANGED
@@ -4,10 +4,11 @@ require 'ostruct'
|
|
4
4
|
require File.join(File.dirname(__FILE__), 'aasm', 'version')
|
5
5
|
require File.join(File.dirname(__FILE__), 'aasm', 'errors')
|
6
6
|
require File.join(File.dirname(__FILE__), 'aasm', 'base')
|
7
|
-
require File.join(File.dirname(__FILE__), 'aasm', '
|
8
|
-
require File.join(File.dirname(__FILE__), 'aasm', '
|
9
|
-
require File.join(File.dirname(__FILE__), 'aasm', '
|
10
|
-
require File.join(File.dirname(__FILE__), 'aasm', '
|
7
|
+
require File.join(File.dirname(__FILE__), 'aasm', 'instance_base')
|
8
|
+
require File.join(File.dirname(__FILE__), 'aasm', 'transition')
|
9
|
+
require File.join(File.dirname(__FILE__), 'aasm', 'event')
|
10
|
+
require File.join(File.dirname(__FILE__), 'aasm', 'state')
|
11
|
+
require File.join(File.dirname(__FILE__), 'aasm', 'localizer')
|
11
12
|
require File.join(File.dirname(__FILE__), 'aasm', 'state_machine')
|
12
13
|
require File.join(File.dirname(__FILE__), 'aasm', 'persistence')
|
13
14
|
require File.join(File.dirname(__FILE__), 'aasm', 'aasm')
|
data/lib/aasm/aasm.rb
CHANGED
@@ -3,18 +3,19 @@ module AASM
|
|
3
3
|
def self.included(base) #:nodoc:
|
4
4
|
base.extend AASM::ClassMethods
|
5
5
|
AASM::StateMachine[base] ||= AASM::StateMachine.new('')
|
6
|
-
AASM::Persistence.
|
6
|
+
AASM::Persistence.load_persistence(base)
|
7
7
|
super
|
8
8
|
end
|
9
9
|
|
10
10
|
module ClassMethods
|
11
11
|
|
12
12
|
# make sure inheritance (aka subclassing) works with AASM
|
13
|
-
def inherited(
|
14
|
-
AASM::StateMachine[
|
13
|
+
def inherited(base)
|
14
|
+
AASM::StateMachine[base] = AASM::StateMachine[self].clone
|
15
15
|
super
|
16
16
|
end
|
17
17
|
|
18
|
+
# this is the entry point for all state and event definitions
|
18
19
|
def aasm(options={}, &block)
|
19
20
|
@aasm ||= AASM::Base.new(self, options)
|
20
21
|
@aasm.instance_eval(&block) if block # new DSL
|
@@ -31,6 +32,7 @@ module AASM
|
|
31
32
|
end
|
32
33
|
end
|
33
34
|
|
35
|
+
# is this better?: aasm.states.name.from_states
|
34
36
|
def aasm_from_states_for_state(state, options={})
|
35
37
|
if options[:transition]
|
36
38
|
aasm.events[options[:transition]].transitions_to_state(state).flatten.map(&:from).flatten
|
@@ -71,100 +73,73 @@ module AASM
|
|
71
73
|
|
72
74
|
# aasm.event(:event_name).human?
|
73
75
|
def aasm_human_event_name(event) # event_name?
|
74
|
-
AASM::
|
76
|
+
AASM::Localizer.new.human_event_name(self, event)
|
75
77
|
end
|
76
78
|
end # ClassMethods
|
77
79
|
|
78
|
-
|
79
|
-
|
80
|
-
@aasm_current_state ||=
|
81
|
-
aasm_persistable? ? aasm_read_state : aasm_enter_initial_state
|
82
|
-
end
|
83
|
-
|
84
|
-
# private?
|
85
|
-
def aasm_enter_initial_state
|
86
|
-
state_name = aasm_determine_state_name(self.class.aasm_initial_state)
|
87
|
-
state = aasm_state_object_for_state(state_name)
|
88
|
-
|
89
|
-
state.fire_callbacks(:before_enter, self)
|
90
|
-
state.fire_callbacks(:enter, self)
|
91
|
-
self.aasm_current_state = state_name
|
92
|
-
state.fire_callbacks(:after_enter, self)
|
93
|
-
|
94
|
-
state_name
|
80
|
+
def aasm
|
81
|
+
@aasm ||= AASM::InstanceBase.new(self)
|
95
82
|
end
|
96
83
|
|
97
|
-
#
|
98
|
-
def
|
99
|
-
|
84
|
+
# may be overwritten by persistence mixins
|
85
|
+
def aasm_read_state
|
86
|
+
aasm.enter_initial_state
|
100
87
|
end
|
101
88
|
|
102
|
-
#
|
103
|
-
|
104
|
-
|
105
|
-
aasm_events_for_current_state.select{ |e| self.send(("may_" + e.to_s + "?").to_sym) }
|
89
|
+
# may be overwritten by persistence mixins
|
90
|
+
def aasm_write_state(new_state)
|
91
|
+
true
|
106
92
|
end
|
107
93
|
|
108
|
-
|
109
|
-
|
110
|
-
|
94
|
+
# may be overwritten by persistence mixins
|
95
|
+
def aasm_write_state_without_persistence(new_state)
|
96
|
+
true
|
111
97
|
end
|
112
98
|
|
113
|
-
|
114
|
-
|
99
|
+
# deprecated
|
100
|
+
def aasm_current_state
|
101
|
+
# warn "#aasm_current_state is deprecated and will be removed in version 3.2.0; please use #aasm.state instead!"
|
102
|
+
aasm.current_state
|
115
103
|
end
|
116
104
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
105
|
+
# deprecated
|
106
|
+
def aasm_enter_initial_state
|
107
|
+
# warn "#aasm_enter_initial_state is deprecated and will be removed in version 3.2.0; please use #aasm.enter_initial_state instead!"
|
108
|
+
aasm.enter_initial_state
|
121
109
|
end
|
122
110
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
end
|
128
|
-
self.aasm_current_state = state if save_success
|
129
|
-
|
130
|
-
save_success
|
111
|
+
# deprecated
|
112
|
+
def aasm_events_for_current_state
|
113
|
+
# warn "#aasm_events_for_current_state is deprecated and will be removed in version 3.2.0; please use #aasm.events instead!"
|
114
|
+
aasm.events(aasm.current_state)
|
131
115
|
end
|
132
116
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
@aasm_current_state = state
|
117
|
+
# deprecated
|
118
|
+
def aasm_permissible_events_for_current_state
|
119
|
+
# warn "#aasm_permissible_events_for_current_state is deprecated and will be removed in version 3.2.0; please use #aasm.permissible_events instead!"
|
120
|
+
aasm.permissible_events
|
138
121
|
end
|
139
122
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
when Proc
|
145
|
-
state.call(self)
|
146
|
-
else
|
147
|
-
raise NotImplementedError, "Unrecognized state-type given. Expected Symbol, String, or Proc."
|
148
|
-
end
|
123
|
+
# deprecated
|
124
|
+
def aasm_events_for_state(state_name)
|
125
|
+
# warn "#aasm_events_for_state(state_name) is deprecated and will be removed in version 3.2.0; please use #aasm.events(state_name) instead!"
|
126
|
+
aasm.events(state_name)
|
149
127
|
end
|
150
128
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
129
|
+
# deprecated
|
130
|
+
def aasm_human_state
|
131
|
+
# warn "#aasm_human_state is deprecated and will be removed in version 3.2.0; please use #aasm.human_state instead!"
|
132
|
+
aasm.human_state
|
155
133
|
end
|
156
134
|
|
157
|
-
|
158
|
-
event = self.class.aasm_events[name]
|
159
|
-
event.may_fire?(self, *args)
|
160
|
-
end
|
135
|
+
private
|
161
136
|
|
162
137
|
def aasm_fire_event(name, options, *args)
|
163
138
|
persist = options[:persist]
|
164
139
|
|
165
140
|
event = self.class.aasm_events[name]
|
166
141
|
begin
|
167
|
-
old_state =
|
142
|
+
old_state = aasm.state_object_for_name(aasm.current_state)
|
168
143
|
|
169
144
|
|
170
145
|
old_state.fire_callbacks(:exit, self)
|
@@ -173,7 +148,7 @@ private
|
|
173
148
|
event.fire_callbacks(:before, self)
|
174
149
|
|
175
150
|
if new_state_name = event.fire(self, *args)
|
176
|
-
new_state =
|
151
|
+
new_state = aasm.state_object_for_name(new_state_name)
|
177
152
|
|
178
153
|
# new before_ callbacks
|
179
154
|
old_state.fire_callbacks(:before_exit, self)
|
@@ -183,10 +158,10 @@ private
|
|
183
158
|
|
184
159
|
persist_successful = true
|
185
160
|
if persist
|
186
|
-
persist_successful =
|
187
|
-
event.
|
161
|
+
persist_successful = aasm.set_current_state_with_persistence(new_state_name)
|
162
|
+
event.fire_callbacks(:success, self) if persist_successful
|
188
163
|
else
|
189
|
-
|
164
|
+
aasm.current_state = new_state_name
|
190
165
|
end
|
191
166
|
|
192
167
|
if persist_successful
|
@@ -194,7 +169,7 @@ private
|
|
194
169
|
new_state.fire_callbacks(:after_enter, self)
|
195
170
|
event.fire_callbacks(:after, self)
|
196
171
|
|
197
|
-
self.aasm_event_fired(name, old_state.name,
|
172
|
+
self.aasm_event_fired(name, old_state.name, aasm.current_state) if self.respond_to?(:aasm_event_fired)
|
198
173
|
else
|
199
174
|
self.aasm_event_failed(name, old_state.name) if self.respond_to?(:aasm_event_failed)
|
200
175
|
end
|
@@ -207,13 +182,13 @@ private
|
|
207
182
|
end
|
208
183
|
|
209
184
|
if AASM::StateMachine[self.class].config.whiny_transitions
|
210
|
-
raise AASM::InvalidTransition, "Event '#{event.name}' cannot transition from '#{
|
185
|
+
raise AASM::InvalidTransition, "Event '#{event.name}' cannot transition from '#{aasm.current_state}'"
|
211
186
|
else
|
212
187
|
false
|
213
188
|
end
|
214
189
|
end
|
215
190
|
rescue StandardError => e
|
216
|
-
event.
|
191
|
+
event.fire_callbacks(:error, self, e) || raise(e)
|
217
192
|
end
|
218
193
|
end
|
219
194
|
end
|
data/lib/aasm/base.rb
CHANGED
@@ -3,50 +3,54 @@ module AASM
|
|
3
3
|
|
4
4
|
def initialize(clazz, options={}, &block)
|
5
5
|
@clazz = clazz
|
6
|
-
|
7
|
-
|
6
|
+
@state_machine = AASM::StateMachine[@clazz]
|
7
|
+
@state_machine.config.column = options[:column].to_sym if options[:column]
|
8
8
|
|
9
9
|
if options.key?(:whiny_transitions)
|
10
|
-
|
11
|
-
elsif
|
12
|
-
|
10
|
+
@state_machine.config.whiny_transitions = options[:whiny_transitions]
|
11
|
+
elsif @state_machine.config.whiny_transitions.nil?
|
12
|
+
@state_machine.config.whiny_transitions = true # this is the default, so let's cry
|
13
13
|
end
|
14
14
|
|
15
15
|
if options.key?(:skip_validation_on_save)
|
16
|
-
|
17
|
-
elsif
|
18
|
-
|
16
|
+
@state_machine.config.skip_validation_on_save = options[:skip_validation_on_save]
|
17
|
+
elsif @state_machine.config.skip_validation_on_save.nil?
|
18
|
+
@state_machine.config.skip_validation_on_save = false # this is the default, so don't store any new state if the model is invalid
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
22
|
def initial_state
|
23
|
-
|
23
|
+
@state_machine.initial_state
|
24
24
|
end
|
25
25
|
|
26
|
+
# define a state
|
26
27
|
def state(name, options={})
|
27
28
|
# @clazz.aasm_state(name, options)
|
28
|
-
|
29
|
-
|
30
|
-
sm.initial_state = name if options[:initial] || !sm.initial_state
|
29
|
+
@state_machine.add_state(name, @clazz, options)
|
30
|
+
@state_machine.initial_state = name if options[:initial] || !@state_machine.initial_state
|
31
31
|
|
32
32
|
@clazz.send(:define_method, "#{name.to_s}?") do
|
33
33
|
aasm_current_state == name
|
34
34
|
end
|
35
|
+
|
36
|
+
unless @clazz.const_defined?("STATE_#{name.to_s.upcase}")
|
37
|
+
@clazz.const_set("STATE_#{name.to_s.upcase}", name)
|
38
|
+
end
|
35
39
|
end
|
36
40
|
|
41
|
+
# define an event
|
37
42
|
def event(name, options={}, &block)
|
38
43
|
# @clazz.aasm_event(name, options, &block)
|
39
|
-
sm = AASM::StateMachine[@clazz]
|
40
44
|
|
41
|
-
unless
|
42
|
-
|
45
|
+
unless @state_machine.events.has_key?(name)
|
46
|
+
@state_machine.events[name] = AASM::Event.new(name, options, &block)
|
43
47
|
end
|
44
48
|
|
45
49
|
# an addition over standard aasm so that, before firing an event, you can ask
|
46
50
|
# may_event? and get back a boolean that tells you whether the guard method
|
47
51
|
# on the transition will let this happen.
|
48
52
|
@clazz.send(:define_method, "may_#{name.to_s}?") do |*args|
|
49
|
-
|
53
|
+
aasm.may_fire_event?(name, *args)
|
50
54
|
end
|
51
55
|
|
52
56
|
@clazz.send(:define_method, "#{name.to_s}!") do |*args|
|
@@ -59,11 +63,11 @@ module AASM
|
|
59
63
|
end
|
60
64
|
|
61
65
|
def states
|
62
|
-
|
66
|
+
@state_machine.states
|
63
67
|
end
|
64
68
|
|
65
69
|
def events
|
66
|
-
|
70
|
+
@state_machine.events
|
67
71
|
end
|
68
72
|
|
69
73
|
def states_for_select
|