aasm 3.0.13 → 3.0.14
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/CHANGELOG.md +10 -6
- data/README.md +217 -109
- data/aasm.gemspec +1 -0
- data/lib/aasm/persistence.rb +2 -0
- data/lib/aasm/persistence/active_record_persistence.rb +2 -71
- data/lib/aasm/persistence/base.rb +46 -0
- data/lib/aasm/persistence/mongoid_persistence.rb +3 -92
- data/lib/aasm/persistence/read_state.rb +40 -0
- data/lib/aasm/supporting_classes/event.rb +6 -2
- data/lib/aasm/supporting_classes/localizer.rb +15 -13
- data/lib/aasm/supporting_classes/state.rb +8 -0
- data/lib/aasm/version.rb +1 -1
- data/spec/models/auth_machine.rb +84 -0
- data/spec/models/callback_new_dsl.rb +38 -0
- data/spec/models/callback_old_dsl.rb +36 -0
- data/spec/models/persistence.rb +71 -0
- data/spec/spec_helpers/models_spec_helper.rb +0 -85
- data/spec/unit/aasm_spec.rb +1 -1
- data/spec/unit/callbacks_new_dsl_spec.rb +33 -0
- data/spec/unit/callbacks_old_dsl_spec.rb +33 -0
- data/spec/unit/complex_example_spec.rb +75 -0
- data/spec/unit/event_spec.rb +31 -12
- data/spec/unit/inspection_spec.rb +7 -0
- data/spec/unit/persistence/active_record_persistence_spec.rb +228 -0
- metadata +41 -29
- data/spec/unit/active_record_persistence_spec.rb +0 -334
- data/spec/unit/auth_machine_spec.rb +0 -83
- data/spec/unit/before_after_callbacks_spec.rb +0 -79
- data/spec/unit/conversation_spec.rb +0 -7
data/CHANGELOG.md
CHANGED
@@ -1,24 +1,28 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 3.0.14
|
4
|
+
|
5
|
+
* supporting event inspection for to-states transitions (`Event#transitions_to_state?`)
|
6
|
+
|
3
7
|
## 3.0.13
|
4
8
|
|
5
|
-
* supporting ActiveRecord transactions when firing an event
|
9
|
+
* supporting *ActiveRecord* transactions when firing an event
|
6
10
|
|
7
11
|
## 3.0.12
|
8
12
|
|
9
|
-
* aasm_from_states_for_state now supports to filter for specific transition
|
13
|
+
* `aasm_from_states_for_state` now supports to filter for specific transition
|
10
14
|
|
11
15
|
## 3.0.11
|
12
16
|
|
13
|
-
* added class method aasm_from_states_for_state to retrieve all from states (regarding transitions) for a given state
|
17
|
+
* added class method `aasm_from_states_for_state` to retrieve all from states (regarding transitions) for a given state
|
14
18
|
|
15
19
|
## 3.0.10
|
16
20
|
|
17
|
-
* added support for transitions from all other states (thanks to Stefan 'swrobel' Wrobel)
|
21
|
+
* added support for transitions from all other states (thanks to *Stefan 'swrobel' Wrobel*)
|
18
22
|
|
19
23
|
## 3.0.9
|
20
24
|
|
21
|
-
* guard checks (e.g. may_edit
|
25
|
+
* guard checks (e.g. `may_edit?`) now support guard parameters as well
|
22
26
|
|
23
27
|
## 3.0.8
|
24
28
|
|
@@ -58,7 +62,7 @@
|
|
58
62
|
* whiny transactions: by default, raise an exception if an event transition is not possible
|
59
63
|
* you may disable whiny transactions
|
60
64
|
|
61
|
-
## 2.4.0
|
65
|
+
## 2.4.0
|
62
66
|
|
63
67
|
* supporting new DSL (which is much shorter)
|
64
68
|
|
data/README.md
CHANGED
@@ -2,170 +2,278 @@
|
|
2
2
|
|
3
3
|
This package contains AASM, a library for adding finite state machines to Ruby classes.
|
4
4
|
|
5
|
-
AASM started as the acts_as_state_machine plugin but has evolved into a more generic library
|
5
|
+
AASM started as the *acts_as_state_machine* plugin but has evolved into a more generic library
|
6
6
|
that no longer targets only ActiveRecord models. It currently provides adapters for
|
7
7
|
[ActiveRecord](http://api.rubyonrails.org/classes/ActiveRecord/Base.html) and
|
8
|
-
[Mongoid](http://mongoid.org/), but it can be used for any Ruby class, no matter
|
9
|
-
parent class.
|
8
|
+
[Mongoid](http://mongoid.org/), but it can be used for any Ruby class, no matter what
|
9
|
+
parent class it has (if any).
|
10
10
|
|
11
|
-
|
11
|
+
## Usage
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
Adding a state machine is as simple as including the AASM module and start defining
|
14
|
+
**states** and **events** together with their **transitions**:
|
15
15
|
|
16
|
-
|
16
|
+
```ruby
|
17
|
+
class Job
|
18
|
+
include AASM
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
aasm do
|
21
|
+
state :sleeping, :initial => true
|
22
|
+
state :running
|
23
|
+
state :cleaning
|
22
24
|
|
23
|
-
|
25
|
+
event :run do
|
26
|
+
transitions :from => :sleeping, :to => :running
|
27
|
+
end
|
24
28
|
|
25
|
-
|
29
|
+
event :clean do
|
30
|
+
transitions :from => :running, :to => :cleaning
|
31
|
+
end
|
26
32
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
oldstate:before_exit
|
32
|
-
newstate:before_enter
|
33
|
-
newstate:enter*
|
34
|
-
__update state__
|
35
|
-
event:success*
|
36
|
-
oldstate:after_exit
|
37
|
-
newstate:after_enter
|
38
|
-
event:after
|
39
|
-
obj:aasm_event_fired*
|
33
|
+
event :sleep do
|
34
|
+
transitions :from => [:running, :cleaning], :to => :sleeping
|
35
|
+
end
|
36
|
+
end
|
40
37
|
|
41
|
-
|
38
|
+
end
|
39
|
+
```
|
42
40
|
|
41
|
+
This provides you with a couple of public methods for instances of the class `Job`:
|
43
42
|
|
44
|
-
|
43
|
+
```ruby
|
44
|
+
job = Job.new
|
45
|
+
job.sleeping? # => true
|
46
|
+
job.may_run? # => true
|
47
|
+
job.run
|
48
|
+
job.running? # => true
|
49
|
+
job.sleeping? # => false
|
50
|
+
job.may_run? # => false
|
51
|
+
job.run # => raises AASM::InvalidTransition
|
52
|
+
```
|
45
53
|
|
46
|
-
|
54
|
+
If you don't like exceptions and prefer a simple `true` or `false` as response, tell
|
55
|
+
AASM not to be *whiny*:
|
47
56
|
|
48
|
-
```
|
49
|
-
|
57
|
+
```ruby
|
58
|
+
class Job
|
59
|
+
...
|
60
|
+
aasm :whiny_transitions => false do
|
61
|
+
...
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
job.running? # => true
|
66
|
+
job.may_run? # => false
|
67
|
+
job.run # => false
|
50
68
|
```
|
51
69
|
|
52
|
-
###
|
70
|
+
### Callbacks
|
71
|
+
|
72
|
+
You can define a number of callbacks for your transitions. These methods will be
|
73
|
+
called, when certain criteria are met, like entering a particular state:
|
53
74
|
|
54
75
|
```ruby
|
55
|
-
|
56
|
-
|
57
|
-
```
|
76
|
+
class Job
|
77
|
+
include AASM
|
58
78
|
|
59
|
-
|
79
|
+
aasm do
|
80
|
+
state :sleeping, :initial => true, :before_enter => :do_something
|
81
|
+
state :running
|
60
82
|
|
61
|
-
|
62
|
-
|
63
|
-
|
83
|
+
event :run, :after => :notify_somebody do
|
84
|
+
transitions :from => :sleeping, :to => :running
|
85
|
+
end
|
86
|
+
|
87
|
+
event :sleep do
|
88
|
+
transitions :from => :running, :to => :sleeping
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def do_something
|
93
|
+
...
|
94
|
+
end
|
95
|
+
|
96
|
+
def notify_somebody
|
97
|
+
...
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
64
101
|
```
|
65
102
|
|
66
|
-
|
103
|
+
In this case `do_something` is called before actually entering the state `sleeping`,
|
104
|
+
while `notify_somebody` is called after the transition `run` (from `sleeping` to `running`)
|
105
|
+
is finished.
|
106
|
+
|
107
|
+
Here you can see a list of all possible callbacks, together with their order of calling:
|
67
108
|
|
68
|
-
|
109
|
+
```ruby
|
110
|
+
event:before
|
111
|
+
previous_state:before_exit
|
112
|
+
new_state:before_enter
|
113
|
+
...update state...
|
114
|
+
previous_state:after_exit
|
115
|
+
new_state:after_enter
|
116
|
+
event:after
|
117
|
+
```
|
69
118
|
|
70
|
-
|
119
|
+
### Guards
|
120
|
+
|
121
|
+
Let's assume you want to allow particular transitions only if a defined condition is
|
122
|
+
given. For this you can set up a guard per transition, which will run before actually
|
123
|
+
running the transition. If the guard returns `false` the transition will be
|
124
|
+
denied (raising `AASM::InvalidTransition` or returning `false` itself):
|
71
125
|
|
72
126
|
```ruby
|
73
|
-
class
|
127
|
+
class Job
|
74
128
|
include AASM
|
75
129
|
|
76
|
-
aasm
|
77
|
-
state :
|
78
|
-
state :
|
79
|
-
state :
|
130
|
+
aasm do
|
131
|
+
state :sleeping, :initial => true
|
132
|
+
state :running
|
133
|
+
state :cleaning
|
80
134
|
|
81
|
-
event :
|
82
|
-
transitions :
|
135
|
+
event :run do
|
136
|
+
transitions :from => :sleeping, :to => :running
|
83
137
|
end
|
84
138
|
|
85
|
-
event :
|
86
|
-
transitions :
|
139
|
+
event :clean do
|
140
|
+
transitions :from => :running, :to => :cleaning
|
87
141
|
end
|
142
|
+
|
143
|
+
event :sleep do
|
144
|
+
transitions :from => :running, :to => :sleeping, :guard => :cleaning_needed?
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def cleaning_needed?
|
149
|
+
false
|
88
150
|
end
|
89
151
|
|
90
152
|
end
|
153
|
+
|
154
|
+
job = Job.new
|
155
|
+
job.run
|
156
|
+
job.may_sleep? # => false
|
157
|
+
job.sleep # => raises AASM::InvalidTransition
|
91
158
|
```
|
92
159
|
|
93
|
-
### A Slightly More Complex Example ###
|
94
160
|
|
95
|
-
|
161
|
+
### ActiveRecord
|
162
|
+
|
163
|
+
AASM comes with support for ActiveRecord and allows automatical persisting of the object's
|
164
|
+
state in the database.
|
96
165
|
|
97
166
|
```ruby
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
event :get_married do
|
112
|
-
transitions :to => :married, :guard => :willing_to_give_up_manhood?
|
113
|
-
end
|
167
|
+
class Job < ActiveRecord::Base
|
168
|
+
include AASM
|
169
|
+
|
170
|
+
aasm do # default column: aasm_state
|
171
|
+
state :sleeping, :initial => true
|
172
|
+
state :running
|
173
|
+
|
174
|
+
event :run do
|
175
|
+
transitions :from => :sleeping, :to => :running
|
176
|
+
end
|
177
|
+
|
178
|
+
event :sleep do
|
179
|
+
transitions :from => :running, :to => :sleeping
|
114
180
|
end
|
115
|
-
aasm_initial_state Proc.new { |relationship| relationship.strictly_for_fun? ? :intimate : :dating }
|
116
|
-
|
117
|
-
def strictly_for_fun?; end
|
118
|
-
def drunk?; end
|
119
|
-
def willing_to_give_up_manhood?; end
|
120
|
-
def make_happy; end
|
121
|
-
def make_depressed; end
|
122
|
-
def make_very_happy; end
|
123
|
-
def never_speak_again; end
|
124
|
-
def give_up_intimacy; end
|
125
|
-
def buy_exotic_car_and_wear_a_combover; end
|
126
181
|
end
|
182
|
+
|
183
|
+
end
|
184
|
+
```
|
185
|
+
|
186
|
+
You can tell AASM to auto-save the object or leave it unsaved
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
job = Job.new
|
190
|
+
job.run # not saved
|
191
|
+
job.run! # saved
|
127
192
|
```
|
128
193
|
|
129
|
-
|
194
|
+
Saving includes running all validations on the `Job` class. If you want make sure
|
195
|
+
the state gets saved without running validations (and thereby maybe persisting an
|
196
|
+
invalid object state), simply tell AASM to skip the validations:
|
197
|
+
|
130
198
|
```ruby
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
199
|
+
class Job < ActiveRecord::Base
|
200
|
+
include AASM
|
201
|
+
|
202
|
+
aasm :skip_validation_on_save => true do
|
203
|
+
state :sleeping, :initial => true
|
204
|
+
state :running
|
205
|
+
|
206
|
+
event :run do
|
207
|
+
transitions :from => :sleeping, :to => :running
|
208
|
+
end
|
209
|
+
|
210
|
+
event :sleep do
|
211
|
+
transitions :from => :running, :to => :sleeping
|
143
212
|
end
|
144
213
|
end
|
214
|
+
|
215
|
+
end
|
145
216
|
```
|
146
217
|
|
147
|
-
###
|
218
|
+
### Transaction support
|
219
|
+
|
220
|
+
Since version *3.0.13* AASM supports ActiveRecord transactions. So whenever a transition
|
221
|
+
callback or the state update fails, all changes to any database record are rolled back.
|
222
|
+
|
223
|
+
### Column name & migration
|
224
|
+
|
225
|
+
As a default AASM uses the column `aasm_state` to store the states. You can override
|
226
|
+
this by defining your favorite column name, using `:column` like this:
|
227
|
+
|
148
228
|
```ruby
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
event :run do
|
155
|
-
transitions :to => :running, :from => :sleeping
|
156
|
-
end
|
157
|
-
event :sleep do
|
158
|
-
transitions :to => :sleeping, :from => :running
|
159
|
-
end
|
160
|
-
end
|
161
|
-
validates_presence_of :name
|
229
|
+
class Job < ActiveRecord::Base
|
230
|
+
include AASM
|
231
|
+
|
232
|
+
aasm :column => 'my_state' do
|
233
|
+
...
|
162
234
|
end
|
235
|
+
|
236
|
+
end
|
163
237
|
```
|
164
|
-
This model can change AASM states which are stored into the database, even if the model itself is invalid!
|
165
238
|
|
239
|
+
Whatever column name is used, make sure to add a migration to provide this column
|
240
|
+
(of type `string`):
|
166
241
|
|
242
|
+
```ruby
|
243
|
+
class AddJobState < ActiveRecord::Migration
|
244
|
+
def self.up
|
245
|
+
add_column :jobs, :aasm_state, :string
|
246
|
+
end
|
247
|
+
|
248
|
+
def self.down
|
249
|
+
remove_column :job, :aasm_state
|
250
|
+
end
|
251
|
+
end
|
252
|
+
```
|
253
|
+
|
254
|
+
## Installation ##
|
255
|
+
|
256
|
+
### Manually from RubyGems.org ###
|
257
|
+
|
258
|
+
```sh
|
259
|
+
% gem install aasm
|
260
|
+
```
|
261
|
+
|
262
|
+
### Or if you are using Bundler ###
|
263
|
+
|
264
|
+
```ruby
|
265
|
+
# Gemfile
|
266
|
+
gem 'aasm'
|
267
|
+
```
|
268
|
+
|
269
|
+
### Building your own gems ###
|
270
|
+
|
271
|
+
```sh
|
272
|
+
% rake build
|
273
|
+
% sudo gem install pkg/aasm-x.y.z.gem
|
274
|
+
```
|
167
275
|
|
168
|
-
##
|
276
|
+
## Latest changes ##
|
169
277
|
|
170
278
|
Look at the [CHANGELOG](https://github.com/aasm/aasm/blob/master/CHANGELOG.md) for details.
|
171
279
|
|
data/aasm.gemspec
CHANGED
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
|
|
21
21
|
s.add_development_dependency 'shoulda'
|
22
22
|
s.add_development_dependency 'sqlite3'
|
23
23
|
s.add_development_dependency 'minitest'
|
24
|
+
# s.add_development_dependency 'debugger'
|
24
25
|
s.add_development_dependency 'ruby-debug-completion'
|
25
26
|
|
26
27
|
s.files = `git ls-files`.split("\n")
|
data/lib/aasm/persistence.rb
CHANGED
@@ -6,6 +6,8 @@ module AASM
|
|
6
6
|
# Use a fancier auto-loading thingy, perhaps. When there are more persistence engines.
|
7
7
|
hierarchy = base.ancestors.map {|klass| klass.to_s}
|
8
8
|
|
9
|
+
require File.join(File.dirname(__FILE__), 'persistence', 'base')
|
10
|
+
require File.join(File.dirname(__FILE__), 'persistence', 'read_state')
|
9
11
|
if hierarchy.include?("ActiveRecord::Base")
|
10
12
|
require File.join(File.dirname(__FILE__), 'persistence', 'active_record_persistence')
|
11
13
|
base.send(:include, AASM::Persistence::ActiveRecordPersistence)
|
@@ -32,9 +32,10 @@ module AASM
|
|
32
32
|
# end
|
33
33
|
#
|
34
34
|
def self.included(base)
|
35
|
+
base.extend AASM::Persistence::Base::ClassMethods
|
35
36
|
base.extend AASM::Persistence::ActiveRecordPersistence::ClassMethods
|
36
37
|
base.send(:include, AASM::Persistence::ActiveRecordPersistence::InstanceMethods)
|
37
|
-
base.send(:include, AASM::Persistence::
|
38
|
+
base.send(:include, AASM::Persistence::ReadState) unless base.method_defined?(:aasm_read_state)
|
38
39
|
base.send(:include, AASM::Persistence::ActiveRecordPersistence::WriteState) unless base.method_defined?(:aasm_write_state)
|
39
40
|
base.send(:include, AASM::Persistence::ActiveRecordPersistence::WriteStateWithoutPersistence) unless base.method_defined?(:aasm_write_state_without_persistence)
|
40
41
|
|
@@ -46,41 +47,6 @@ module AASM
|
|
46
47
|
end
|
47
48
|
|
48
49
|
module ClassMethods
|
49
|
-
# Maps to the aasm_column in the database. Defaults to "aasm_state". You can write:
|
50
|
-
#
|
51
|
-
# create_table :foos do |t|
|
52
|
-
# t.string :name
|
53
|
-
# t.string :aasm_state
|
54
|
-
# end
|
55
|
-
#
|
56
|
-
# class Foo < ActiveRecord::Base
|
57
|
-
# include AASM
|
58
|
-
# end
|
59
|
-
#
|
60
|
-
# OR:
|
61
|
-
#
|
62
|
-
# create_table :foos do |t|
|
63
|
-
# t.string :name
|
64
|
-
# t.string :status
|
65
|
-
# end
|
66
|
-
#
|
67
|
-
# class Foo < ActiveRecord::Base
|
68
|
-
# include AASM
|
69
|
-
# aasm_column :status
|
70
|
-
# end
|
71
|
-
#
|
72
|
-
# This method is both a getter and a setter
|
73
|
-
def aasm_column(column_name=nil)
|
74
|
-
if column_name
|
75
|
-
AASM::StateMachine[self].config.column = column_name.to_sym
|
76
|
-
# @aasm_column = column_name.to_sym
|
77
|
-
else
|
78
|
-
AASM::StateMachine[self].config.column ||= :aasm_state
|
79
|
-
# @aasm_column ||= :aasm_state
|
80
|
-
end
|
81
|
-
# @aasm_column
|
82
|
-
AASM::StateMachine[self].config.column
|
83
|
-
end
|
84
50
|
|
85
51
|
def find_in_state(number, state, *args)
|
86
52
|
with_state_scope state do
|
@@ -203,41 +169,6 @@ module AASM
|
|
203
169
|
end
|
204
170
|
end
|
205
171
|
|
206
|
-
module ReadState
|
207
|
-
|
208
|
-
# Returns the value of the aasm_column - called from <tt>aasm_current_state</tt>
|
209
|
-
#
|
210
|
-
# If it's a new record, and the aasm state column is blank it returns the initial state:
|
211
|
-
#
|
212
|
-
# class Foo < ActiveRecord::Base
|
213
|
-
# include AASM
|
214
|
-
# aasm_column :status
|
215
|
-
# aasm_state :opened
|
216
|
-
# aasm_state :closed
|
217
|
-
# end
|
218
|
-
#
|
219
|
-
# foo = Foo.new
|
220
|
-
# foo.current_state # => :opened
|
221
|
-
# foo.close
|
222
|
-
# foo.current_state # => :closed
|
223
|
-
#
|
224
|
-
# foo = Foo.find(1)
|
225
|
-
# foo.current_state # => :opened
|
226
|
-
# foo.aasm_state = nil
|
227
|
-
# foo.current_state # => nil
|
228
|
-
#
|
229
|
-
# NOTE: intended to be called from an event
|
230
|
-
#
|
231
|
-
# This allows for nil aasm states - be sure to add validation to your model
|
232
|
-
def aasm_read_state
|
233
|
-
if new_record?
|
234
|
-
send(self.class.aasm_column).blank? ? aasm_determine_state_name(self.class.aasm_initial_state) : send(self.class.aasm_column).to_sym
|
235
|
-
else
|
236
|
-
send(self.class.aasm_column).nil? ? nil : send(self.class.aasm_column).to_sym
|
237
|
-
end
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
172
|
end
|
242
173
|
end
|
243
174
|
end
|