aasm 4.5.1 → 5.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/LICENSE +1 -1
- data/README.md +809 -129
- data/lib/aasm/aasm.rb +74 -37
- data/lib/aasm/base.rb +188 -41
- data/lib/aasm/configuration.rb +27 -2
- data/lib/aasm/core/event.rb +75 -47
- data/lib/aasm/core/invoker.rb +129 -0
- data/lib/aasm/core/invokers/base_invoker.rb +75 -0
- data/lib/aasm/core/invokers/class_invoker.rb +52 -0
- data/lib/aasm/core/invokers/literal_invoker.rb +49 -0
- data/lib/aasm/core/invokers/proc_invoker.rb +59 -0
- data/lib/aasm/core/state.rb +22 -13
- data/lib/aasm/core/transition.rb +30 -23
- data/lib/aasm/dsl_helper.rb +24 -22
- data/lib/aasm/errors.rb +8 -5
- data/lib/aasm/instance_base.rb +63 -15
- data/lib/aasm/localizer.rb +13 -3
- data/lib/aasm/minitest/allow_event.rb +13 -0
- data/lib/aasm/minitest/allow_transition_to.rb +13 -0
- data/lib/aasm/minitest/have_state.rb +13 -0
- data/lib/aasm/minitest/transition_from.rb +21 -0
- data/lib/aasm/minitest.rb +5 -0
- data/lib/aasm/minitest_spec.rb +15 -0
- data/lib/aasm/persistence/active_record_persistence.rb +87 -79
- data/lib/aasm/persistence/base.rb +30 -30
- data/lib/aasm/persistence/core_data_query_persistence.rb +94 -0
- data/lib/aasm/persistence/dynamoid_persistence.rb +92 -0
- data/lib/aasm/persistence/mongoid_persistence.rb +49 -35
- data/lib/aasm/persistence/no_brainer_persistence.rb +105 -0
- data/lib/aasm/persistence/orm.rb +154 -0
- data/lib/aasm/persistence/plain_persistence.rb +2 -1
- data/lib/aasm/persistence/redis_persistence.rb +112 -0
- data/lib/aasm/persistence/sequel_persistence.rb +37 -67
- data/lib/aasm/persistence.rb +20 -5
- data/lib/aasm/rspec/allow_event.rb +5 -1
- data/lib/aasm/rspec/allow_transition_to.rb +5 -1
- data/lib/aasm/rspec/transition_from.rb +8 -4
- data/lib/aasm/state_machine.rb +6 -12
- data/lib/aasm/state_machine_store.rb +76 -0
- data/lib/aasm/version.rb +1 -1
- data/lib/aasm.rb +8 -2
- data/lib/generators/aasm/aasm_generator.rb +16 -0
- data/lib/generators/aasm/orm_helpers.rb +41 -0
- data/lib/generators/active_record/aasm_generator.rb +40 -0
- data/lib/generators/active_record/templates/migration.rb +8 -0
- data/lib/generators/active_record/templates/migration_existing.rb +5 -0
- data/lib/generators/mongoid/aasm_generator.rb +28 -0
- data/lib/generators/nobrainer/aasm_generator.rb +28 -0
- data/lib/motion-aasm.rb +37 -0
- metadata +104 -259
- data/.document +0 -6
- data/.gitignore +0 -19
- data/.travis.yml +0 -37
- data/API +0 -34
- data/CHANGELOG.md +0 -272
- data/CODE_OF_CONDUCT.md +0 -13
- data/Gemfile +0 -15
- data/HOWTO +0 -12
- data/PLANNED_CHANGES.md +0 -11
- data/README_FROM_VERSION_3_TO_4.md +0 -240
- data/Rakefile +0 -26
- data/aasm.gemspec +0 -31
- data/callbacks.txt +0 -51
- data/gemfiles/rails_3.2.gemfile +0 -14
- data/gemfiles/rails_4.0.gemfile +0 -12
- data/gemfiles/rails_4.0_mongo_mapper.gemfile +0 -14
- data/gemfiles/rails_4.1.gemfile +0 -12
- data/gemfiles/rails_4.1_mongo_mapper.gemfile +0 -14
- data/gemfiles/rails_4.2.gemfile +0 -12
- data/gemfiles/rails_4.2_mongo_mapper.gemfile +0 -14
- data/gemfiles/rails_4.2_mongoid_5.gemfile +0 -12
- data/lib/aasm/persistence/mongo_mapper_persistence.rb +0 -157
- data/spec/database.rb +0 -63
- data/spec/database.yml +0 -3
- data/spec/en.yml +0 -9
- data/spec/en_deprecated_style.yml +0 -10
- data/spec/models/active_record/basic_active_record_two_state_machines_example.rb +0 -25
- data/spec/models/active_record/complex_active_record_example.rb +0 -33
- data/spec/models/active_record/derivate_new_dsl.rb +0 -7
- data/spec/models/active_record/false_state.rb +0 -35
- data/spec/models/active_record/gate.rb +0 -39
- data/spec/models/active_record/localizer_test_model.rb +0 -34
- data/spec/models/active_record/no_direct_assignment.rb +0 -21
- data/spec/models/active_record/no_scope.rb +0 -21
- data/spec/models/active_record/persisted_state.rb +0 -12
- data/spec/models/active_record/provided_and_persisted_state.rb +0 -24
- data/spec/models/active_record/reader.rb +0 -7
- data/spec/models/active_record/readme_job.rb +0 -21
- data/spec/models/active_record/simple_new_dsl.rb +0 -17
- data/spec/models/active_record/thief.rb +0 -29
- data/spec/models/active_record/transient.rb +0 -6
- data/spec/models/active_record/with_enum.rb +0 -39
- data/spec/models/active_record/with_false_enum.rb +0 -31
- data/spec/models/active_record/with_true_enum.rb +0 -39
- data/spec/models/active_record/writer.rb +0 -6
- data/spec/models/basic_two_state_machines_example.rb +0 -25
- data/spec/models/callbacks/basic.rb +0 -78
- data/spec/models/callbacks/basic_multiple.rb +0 -75
- data/spec/models/callbacks/guard_within_block.rb +0 -66
- data/spec/models/callbacks/guard_within_block_multiple.rb +0 -66
- data/spec/models/callbacks/multiple_transitions_transition_guard.rb +0 -65
- data/spec/models/callbacks/multiple_transitions_transition_guard_multiple.rb +0 -65
- data/spec/models/callbacks/private_method.rb +0 -44
- data/spec/models/callbacks/private_method_multiple.rb +0 -44
- data/spec/models/callbacks/with_args.rb +0 -61
- data/spec/models/callbacks/with_args_multiple.rb +0 -61
- data/spec/models/callbacks/with_state_arg.rb +0 -26
- data/spec/models/callbacks/with_state_arg_multiple.rb +0 -26
- data/spec/models/complex_example.rb +0 -222
- data/spec/models/conversation.rb +0 -93
- data/spec/models/default_state.rb +0 -12
- data/spec/models/double_definer.rb +0 -21
- data/spec/models/foo.rb +0 -92
- data/spec/models/foo_callback_multiple.rb +0 -45
- data/spec/models/guardian.rb +0 -48
- data/spec/models/guardian_multiple.rb +0 -48
- data/spec/models/initial_state_proc.rb +0 -31
- data/spec/models/invalid_persistor.rb +0 -31
- data/spec/models/mongo_mapper/complex_mongo_mapper_example.rb +0 -37
- data/spec/models/mongo_mapper/no_scope_mongo_mapper.rb +0 -21
- data/spec/models/mongo_mapper/simple_mongo_mapper.rb +0 -23
- data/spec/models/mongo_mapper/simple_new_dsl_mongo_mapper.rb +0 -25
- data/spec/models/mongoid/complex_mongoid_example.rb +0 -37
- data/spec/models/mongoid/no_scope_mongoid.rb +0 -21
- data/spec/models/mongoid/simple_mongoid.rb +0 -23
- data/spec/models/mongoid/simple_new_dsl_mongoid.rb +0 -25
- data/spec/models/no_initial_state.rb +0 -25
- data/spec/models/not_auto_loaded/process.rb +0 -21
- data/spec/models/parametrised_event.rb +0 -29
- data/spec/models/parametrised_event_multiple.rb +0 -29
- data/spec/models/process_with_new_dsl.rb +0 -31
- data/spec/models/provided_state.rb +0 -24
- data/spec/models/sequel/complex_sequel_example.rb +0 -45
- data/spec/models/sequel/sequel_multiple.rb +0 -25
- data/spec/models/sequel/sequel_simple.rb +0 -25
- data/spec/models/silencer.rb +0 -27
- data/spec/models/simple_example.rb +0 -15
- data/spec/models/simple_multiple_example.rb +0 -30
- data/spec/models/state_machine_with_failed_event.rb +0 -12
- data/spec/models/sub_class.rb +0 -7
- data/spec/models/sub_class_with_more_states.rb +0 -18
- data/spec/models/sub_classing.rb +0 -3
- data/spec/models/super_class.rb +0 -46
- data/spec/models/this_name_better_not_be_in_use.rb +0 -11
- data/spec/models/transactor.rb +0 -53
- data/spec/models/valid_state_name.rb +0 -23
- data/spec/models/validator.rb +0 -79
- data/spec/models/worker.rb +0 -2
- data/spec/spec_helper.rb +0 -25
- data/spec/unit/api_spec.rb +0 -77
- data/spec/unit/basic_two_state_machines_example_spec.rb +0 -10
- data/spec/unit/callback_multiple_spec.rb +0 -295
- data/spec/unit/callbacks_spec.rb +0 -296
- data/spec/unit/complex_example_spec.rb +0 -84
- data/spec/unit/complex_multiple_example_spec.rb +0 -99
- data/spec/unit/edge_cases_spec.rb +0 -16
- data/spec/unit/event_multiple_spec.rb +0 -73
- data/spec/unit/event_naming_spec.rb +0 -11
- data/spec/unit/event_spec.rb +0 -322
- data/spec/unit/guard_multiple_spec.rb +0 -60
- data/spec/unit/guard_spec.rb +0 -60
- data/spec/unit/initial_state_multiple_spec.rb +0 -15
- data/spec/unit/initial_state_spec.rb +0 -12
- data/spec/unit/inspection_multiple_spec.rb +0 -201
- data/spec/unit/inspection_spec.rb +0 -111
- data/spec/unit/localizer_spec.rb +0 -76
- data/spec/unit/memory_leak_spec.rb +0 -38
- data/spec/unit/new_dsl_spec.rb +0 -12
- data/spec/unit/persistence/active_record_persistence_multiple_spec.rb +0 -573
- data/spec/unit/persistence/active_record_persistence_spec.rb +0 -552
- data/spec/unit/persistence/mongo_mapper_persistence_multiple_spec.rb +0 -146
- data/spec/unit/persistence/mongo_mapper_persistence_spec.rb +0 -93
- data/spec/unit/persistence/mongoid_persistence_multiple_spec.rb +0 -127
- data/spec/unit/persistence/mongoid_persistence_spec.rb +0 -79
- data/spec/unit/persistence/sequel_persistence_multiple_spec.rb +0 -153
- data/spec/unit/persistence/sequel_persistence_spec.rb +0 -100
- data/spec/unit/readme_spec.rb +0 -42
- data/spec/unit/reloading_spec.rb +0 -15
- data/spec/unit/rspec_matcher_spec.rb +0 -79
- data/spec/unit/simple_example_spec.rb +0 -42
- data/spec/unit/simple_multiple_example_spec.rb +0 -63
- data/spec/unit/state_spec.rb +0 -89
- data/spec/unit/subclassing_multiple_spec.rb +0 -39
- data/spec/unit/subclassing_spec.rb +0 -31
- data/spec/unit/transition_spec.rb +0 -291
data/README.md
CHANGED
@@ -2,16 +2,62 @@
|
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/aasm.svg)](http://badge.fury.io/rb/aasm)
|
4
4
|
[![Build Status](https://travis-ci.org/aasm/aasm.svg?branch=master)](https://travis-ci.org/aasm/aasm)
|
5
|
-
[![Dependency Status](https://gemnasium.com/aasm/aasm.svg)](https://gemnasium.com/aasm/aasm)
|
6
5
|
[![Code Climate](https://codeclimate.com/github/aasm/aasm/badges/gpa.svg)](https://codeclimate.com/github/aasm/aasm)
|
6
|
+
[![codecov](https://codecov.io/gh/aasm/aasm/branch/master/graph/badge.svg)](https://codecov.io/gh/aasm/aasm)
|
7
|
+
|
8
|
+
## Index
|
9
|
+
- [Upgrade from version 3 to 4](#upgrade-from-version-3-to-4)
|
10
|
+
- [Usage](#usage)
|
11
|
+
- [Callbacks](#callbacks)
|
12
|
+
- [Lifecycle](#lifecycle)
|
13
|
+
- [The current event triggered](#the-current-event-triggered)
|
14
|
+
- [Guards](#guards)
|
15
|
+
- [Transitions](#transitions)
|
16
|
+
- [Multiple state machines per class](#multiple-state-machines-per-class)
|
17
|
+
- [Handling naming conflicts between multiple state machines](#handling-naming-conflicts-between-multiple-state-machines)
|
18
|
+
- [Binding event](#binding-event)
|
19
|
+
- [Auto-generated Status Constants](#auto-generated-status-constants)
|
20
|
+
- [Extending AASM](#extending-aasm)
|
21
|
+
- [ActiveRecord](#activerecord)
|
22
|
+
- [Bang events](#bang-events)
|
23
|
+
- [Timestamps](#timestamps)
|
24
|
+
- [ActiveRecord enums](#activerecord-enums)
|
25
|
+
- [Sequel](#sequel)
|
26
|
+
- [Dynamoid](#dynamoid)
|
27
|
+
- [Mongoid](#mongoid)
|
28
|
+
- [Nobrainer](#nobrainer)
|
29
|
+
- [Redis](#redis)
|
30
|
+
- [Automatic Scopes](#automatic-scopes)
|
31
|
+
- [Transaction support](#transaction-support)
|
32
|
+
- [Pessimistic Locking](#pessimistic-locking)
|
33
|
+
- [Column name & migration](#column-name--migration)
|
34
|
+
- [Log State Changes](#log-state-changes)
|
35
|
+
- [Inspection](#inspection)
|
36
|
+
- [Warning output](#warning-output)
|
37
|
+
- [RubyMotion support](#rubymotion-support)
|
38
|
+
- [Testing](#testing)
|
39
|
+
- [RSpec](#rspec)
|
40
|
+
- [Minitest](#minitest)
|
41
|
+
- [Assertions](#assertions)
|
42
|
+
- [Expectations](#expectations)
|
43
|
+
- [Installation](#installation)
|
44
|
+
- [Manually from RubyGems.org](#manually-from-rubygemsorg)
|
45
|
+
- [Bundler](#or-if-you-are-using-bundler)
|
46
|
+
- [Building your own gems](#building-your-own-gems)
|
47
|
+
- [Generators](#generators)
|
48
|
+
- [Test suite with Docker](#docker)
|
49
|
+
- [Latest changes](#latest-changes)
|
50
|
+
- [Questions?](#questions)
|
51
|
+
- [Maintainers](#maintainers)
|
52
|
+
- [Contributing](CONTRIBUTING.md)
|
53
|
+
- [Warranty](#warranty)
|
54
|
+
- [License](#license)
|
7
55
|
|
8
56
|
This package contains AASM, a library for adding finite state machines to Ruby classes.
|
9
57
|
|
10
58
|
AASM started as the *acts_as_state_machine* plugin but has evolved into a more generic library
|
11
|
-
that no longer targets only ActiveRecord models. It currently provides adapters for
|
12
|
-
|
13
|
-
[Mongoid](http://mongoid.org/), and [Mongomapper](http://mongomapper.com/) but it can be used for any Ruby class, no matter what
|
14
|
-
parent class it has (if any).
|
59
|
+
that no longer targets only ActiveRecord models. It currently provides adapters for many
|
60
|
+
ORMs but it can be used for any Ruby class, no matter what parent class it has (if any).
|
15
61
|
|
16
62
|
## Upgrade from version 3 to 4
|
17
63
|
|
@@ -27,20 +73,19 @@ class Job
|
|
27
73
|
include AASM
|
28
74
|
|
29
75
|
aasm do
|
30
|
-
state :sleeping, :
|
31
|
-
state :running
|
32
|
-
state :cleaning
|
76
|
+
state :sleeping, initial: true
|
77
|
+
state :running, :cleaning
|
33
78
|
|
34
79
|
event :run do
|
35
|
-
transitions :
|
80
|
+
transitions from: :sleeping, to: :running
|
36
81
|
end
|
37
82
|
|
38
83
|
event :clean do
|
39
|
-
transitions :
|
84
|
+
transitions from: :running, to: :cleaning
|
40
85
|
end
|
41
86
|
|
42
87
|
event :sleep do
|
43
|
-
transitions :
|
88
|
+
transitions from: [:running, :cleaning], to: :sleeping
|
44
89
|
end
|
45
90
|
end
|
46
91
|
|
@@ -66,7 +111,7 @@ AASM not to be *whiny*:
|
|
66
111
|
```ruby
|
67
112
|
class Job
|
68
113
|
...
|
69
|
-
aasm :
|
114
|
+
aasm whiny_transitions: false do
|
70
115
|
...
|
71
116
|
end
|
72
117
|
end
|
@@ -87,25 +132,27 @@ the transition succeeds :
|
|
87
132
|
|
88
133
|
### Callbacks
|
89
134
|
|
90
|
-
You can define a number of callbacks for your transitions. These methods will be
|
91
|
-
called
|
135
|
+
You can define a number of callbacks for your events, transitions and states. These methods, Procs or classes will be
|
136
|
+
called when certain criteria are met, like entering a particular state:
|
92
137
|
|
93
138
|
```ruby
|
94
139
|
class Job
|
95
140
|
include AASM
|
96
141
|
|
97
142
|
aasm do
|
98
|
-
state :sleeping, :
|
99
|
-
state :running
|
143
|
+
state :sleeping, initial: true, before_enter: :do_something
|
144
|
+
state :running, before_enter: Proc.new { do_something && notify_somebody }
|
145
|
+
state :finished
|
100
146
|
|
101
147
|
after_all_transitions :log_status_change
|
102
148
|
|
103
|
-
event :run, :
|
149
|
+
event :run, after: :notify_somebody do
|
104
150
|
before do
|
105
151
|
log('Preparing to run')
|
106
152
|
end
|
107
153
|
|
108
|
-
transitions :
|
154
|
+
transitions from: :sleeping, to: :running, after: Proc.new {|*args| set_process(*args) }
|
155
|
+
transitions from: :running, to: :finished, after: LogRunTime
|
109
156
|
end
|
110
157
|
|
111
158
|
event :sleep do
|
@@ -115,7 +162,7 @@ class Job
|
|
115
162
|
error do |e|
|
116
163
|
...
|
117
164
|
end
|
118
|
-
transitions :
|
165
|
+
transitions from: :running, to: :sleeping
|
119
166
|
end
|
120
167
|
end
|
121
168
|
|
@@ -131,54 +178,74 @@ class Job
|
|
131
178
|
...
|
132
179
|
end
|
133
180
|
|
134
|
-
def notify_somebody
|
181
|
+
def notify_somebody
|
135
182
|
...
|
136
183
|
end
|
137
184
|
|
138
185
|
end
|
186
|
+
|
187
|
+
class LogRunTime
|
188
|
+
def call
|
189
|
+
log "Job was running for X seconds"
|
190
|
+
end
|
191
|
+
end
|
139
192
|
```
|
140
193
|
|
141
194
|
In this case `do_something` is called before actually entering the state `sleeping`,
|
142
195
|
while `notify_somebody` is called after the transition `run` (from `sleeping` to `running`)
|
143
196
|
is finished.
|
144
197
|
|
145
|
-
|
198
|
+
AASM will also initialize `LogRunTime` and run the `call` method for you after the transition from `running` to `finished` in the example above. You can pass arguments to the class by defining an initialize method on it, like this:
|
199
|
+
|
200
|
+
Note that Procs are executed in the context of a record, it means that you don't need to expect the record as an argument, just call the methods you need.
|
146
201
|
|
147
202
|
```ruby
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
...update state...
|
159
|
-
event success # if persist successful
|
160
|
-
old_state after_exit
|
161
|
-
new_state after_enter
|
162
|
-
event after
|
163
|
-
rescue
|
164
|
-
event error
|
203
|
+
class LogRunTime
|
204
|
+
# optional args parameter can be omitted, but if you define initialize
|
205
|
+
# you must accept the model instance as the first parameter to it.
|
206
|
+
def initialize(job, args = {})
|
207
|
+
@job = job
|
208
|
+
end
|
209
|
+
|
210
|
+
def call
|
211
|
+
log "Job was running for #{@job.run_time} seconds"
|
212
|
+
end
|
165
213
|
end
|
166
214
|
```
|
167
215
|
|
168
|
-
|
216
|
+
#### Parameters
|
217
|
+
You can pass parameters to events:
|
169
218
|
|
170
219
|
```ruby
|
171
220
|
job = Job.new
|
172
|
-
job.run(:
|
221
|
+
job.run(:defragmentation)
|
173
222
|
```
|
174
223
|
|
175
|
-
In this case
|
224
|
+
All guards and after callbacks will receive these parameters. In this case `set_process` would be called with
|
225
|
+
`:defragmentation` argument.
|
176
226
|
|
177
|
-
|
227
|
+
If the first argument to the event is a state (e.g. `:running` or `:finished`), the first argument is consumed and
|
228
|
+
the state machine will attempt to transition to that state. Add comma separated parameter for guards and callbacks
|
178
229
|
|
230
|
+
```ruby
|
231
|
+
job = Job.new
|
232
|
+
job.run(:running, :defragmentation)
|
233
|
+
```
|
234
|
+
In this case `set_process` won't be called, job will transition to running state and callback will receive
|
235
|
+
`:defragmentation` as parameter
|
236
|
+
|
237
|
+
#### Error Handling
|
179
238
|
In case of an error during the event processing the error is rescued and passed to `:error`
|
180
239
|
callback, which can handle it or re-raise it for further propagation.
|
181
240
|
|
241
|
+
Also, you can define a method that will be called if any event fails:
|
242
|
+
|
243
|
+
```ruby
|
244
|
+
def aasm_event_failed(event_name, old_state_name)
|
245
|
+
# use custom exception/messages, report metrics, etc
|
246
|
+
end
|
247
|
+
```
|
248
|
+
|
182
249
|
During the transition's `:after` callback (and reliably only then, or in the global
|
183
250
|
`after_all_transitions` callback) you can access the originating state (the from-state)
|
184
251
|
and the target state (the to state), like this:
|
@@ -189,6 +256,41 @@ and the target state (the to state), like this:
|
|
189
256
|
end
|
190
257
|
```
|
191
258
|
|
259
|
+
#### Lifecycle
|
260
|
+
|
261
|
+
Here you can see a list of all possible callbacks, together with their order of calling:
|
262
|
+
|
263
|
+
```ruby
|
264
|
+
begin
|
265
|
+
event before_all_events
|
266
|
+
event before
|
267
|
+
event guards
|
268
|
+
transition guards
|
269
|
+
old_state before_exit
|
270
|
+
old_state exit
|
271
|
+
after_all_transitions
|
272
|
+
transition after
|
273
|
+
new_state before_enter
|
274
|
+
new_state enter
|
275
|
+
...update state...
|
276
|
+
event before_success # if persist successful
|
277
|
+
transition success # if persist successful, database update not guaranteed
|
278
|
+
event success # if persist successful, database update not guaranteed
|
279
|
+
old_state after_exit
|
280
|
+
new_state after_enter
|
281
|
+
event after
|
282
|
+
event after_all_events
|
283
|
+
rescue
|
284
|
+
event error
|
285
|
+
event error_on_all_events
|
286
|
+
ensure
|
287
|
+
event ensure
|
288
|
+
event ensure_on_all_events
|
289
|
+
end
|
290
|
+
```
|
291
|
+
|
292
|
+
Use event's `after_commit` callback if it should be fired after database update.
|
293
|
+
|
192
294
|
#### The current event triggered
|
193
295
|
|
194
296
|
While running the callbacks you can easily retrieve the name of the event triggered
|
@@ -219,33 +321,41 @@ and then
|
|
219
321
|
Let's assume you want to allow particular transitions only if a defined condition is
|
220
322
|
given. For this you can set up a guard per transition, which will run before actually
|
221
323
|
running the transition. If the guard returns `false` the transition will be
|
222
|
-
denied (raising `AASM::InvalidTransition`
|
324
|
+
denied (raising `AASM::InvalidTransition`):
|
223
325
|
|
224
326
|
```ruby
|
225
327
|
class Cleaner
|
226
328
|
include AASM
|
227
329
|
|
228
330
|
aasm do
|
229
|
-
state :idle, :
|
331
|
+
state :idle, initial: true
|
230
332
|
state :cleaning
|
231
333
|
|
232
334
|
event :clean do
|
233
|
-
transitions :
|
335
|
+
transitions from: :idle, to: :cleaning, guard: :cleaning_needed?
|
234
336
|
end
|
235
337
|
|
236
338
|
event :clean_if_needed do
|
237
|
-
transitions :
|
339
|
+
transitions from: :idle, to: :cleaning do
|
238
340
|
guard do
|
239
341
|
cleaning_needed?
|
240
342
|
end
|
241
343
|
end
|
242
|
-
transitions :
|
344
|
+
transitions from: :idle, to: :idle
|
345
|
+
end
|
346
|
+
|
347
|
+
event :clean_if_dirty do
|
348
|
+
transitions from: :idle, to: :cleaning, guard: :if_dirty?
|
243
349
|
end
|
244
350
|
end
|
245
351
|
|
246
352
|
def cleaning_needed?
|
247
353
|
false
|
248
354
|
end
|
355
|
+
|
356
|
+
def if_dirty?(status)
|
357
|
+
status == :dirty
|
358
|
+
end
|
249
359
|
end
|
250
360
|
|
251
361
|
job = Cleaner.new
|
@@ -253,6 +363,9 @@ job.may_clean? # => false
|
|
253
363
|
job.clean # => raises AASM::InvalidTransition
|
254
364
|
job.may_clean_if_needed? # => true
|
255
365
|
job.clean_if_needed! # idle
|
366
|
+
|
367
|
+
job.clean_if_dirty(:clean) # => raises AASM::InvalidTransition
|
368
|
+
job.clean_if_dirty(:dirty) # => true
|
256
369
|
```
|
257
370
|
|
258
371
|
You can even provide a number of guards, which all have to succeed to proceed
|
@@ -261,16 +374,16 @@ You can even provide a number of guards, which all have to succeed to proceed
|
|
261
374
|
def walked_the_dog?; ...; end
|
262
375
|
|
263
376
|
event :sleep do
|
264
|
-
transitions :
|
377
|
+
transitions from: :running, to: :sleeping, guards: [:cleaning_needed?, :walked_the_dog?]
|
265
378
|
end
|
266
379
|
```
|
267
380
|
|
268
381
|
If you want to provide guards for all transitions within an event, you can use event guards
|
269
382
|
|
270
383
|
```ruby
|
271
|
-
event :sleep, :
|
272
|
-
transitions :
|
273
|
-
transitions :
|
384
|
+
event :sleep, guards: [:walked_the_dog?] do
|
385
|
+
transitions from: :running, to: :sleeping, guards: [:cleaning_needed?]
|
386
|
+
transitions from: :cleaning, to: :sleeping
|
274
387
|
end
|
275
388
|
```
|
276
389
|
|
@@ -278,15 +391,30 @@ If you prefer a more Ruby-like guard syntax, you can use `if` and `unless` as we
|
|
278
391
|
|
279
392
|
```ruby
|
280
393
|
event :clean do
|
281
|
-
transitions :
|
394
|
+
transitions from: :running, to: :cleaning, if: :cleaning_needed?
|
282
395
|
end
|
283
396
|
|
284
397
|
event :sleep do
|
285
|
-
transitions :
|
398
|
+
transitions from: :running, to: :sleeping, unless: :cleaning_needed?
|
286
399
|
end
|
287
400
|
end
|
288
401
|
```
|
289
402
|
|
403
|
+
You can invoke a Class instead of a method if the Class responds to `call`
|
404
|
+
|
405
|
+
```ruby
|
406
|
+
event :sleep do
|
407
|
+
transitions from: :running, to: :sleeping, guards: Dog
|
408
|
+
end
|
409
|
+
```
|
410
|
+
```ruby
|
411
|
+
class Dog
|
412
|
+
def call
|
413
|
+
cleaning_needed? && walked?
|
414
|
+
end
|
415
|
+
...
|
416
|
+
end
|
417
|
+
```
|
290
418
|
|
291
419
|
### Transitions
|
292
420
|
|
@@ -299,7 +427,7 @@ class Job
|
|
299
427
|
include AASM
|
300
428
|
|
301
429
|
aasm do
|
302
|
-
state :stage1, :
|
430
|
+
state :stage1, initial: true
|
303
431
|
state :stage2
|
304
432
|
state :stage3
|
305
433
|
state :completed
|
@@ -320,40 +448,67 @@ job.stage1_completed
|
|
320
448
|
job.aasm.current_state # stage3
|
321
449
|
```
|
322
450
|
|
451
|
+
You can define transition from any defined state by omitting `from`:
|
452
|
+
|
453
|
+
```ruby
|
454
|
+
event :abort do
|
455
|
+
transitions to: :aborted
|
456
|
+
end
|
457
|
+
```
|
458
|
+
|
459
|
+
### Display name for state
|
460
|
+
|
461
|
+
You can define display name for state using :display option
|
462
|
+
|
463
|
+
```ruby
|
464
|
+
class Job
|
465
|
+
include AASM
|
466
|
+
|
467
|
+
aasm do
|
468
|
+
state :stage1, initial: true, display: 'First Stage'
|
469
|
+
state :stage2
|
470
|
+
state :stage3
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
job = Job.new
|
475
|
+
job.aasm.human_state
|
476
|
+
|
477
|
+
```
|
323
478
|
|
324
479
|
### Multiple state machines per class
|
325
480
|
|
326
481
|
Multiple state machines per class are supported. Be aware though that _AASM_ has been
|
327
|
-
built with one state machine per class in mind. Nonetheless, here's how to do it
|
482
|
+
built with one state machine per class in mind. Nonetheless, here's how to do it (see below). Please note that you will need to specify database columns for where your pertinent states will be stored - we have specified two columns `move_state` and `work_state` in the example below. See the [Column name & migration](https://github.com/aasm/aasm#column-name--migration) section for further info.
|
328
483
|
|
329
484
|
```ruby
|
330
485
|
class SimpleMultipleExample
|
331
486
|
include AASM
|
332
|
-
aasm(:move) do
|
333
|
-
state :standing, :
|
487
|
+
aasm(:move, column: 'move_state') do
|
488
|
+
state :standing, initial: true
|
334
489
|
state :walking
|
335
490
|
state :running
|
336
491
|
|
337
492
|
event :walk do
|
338
|
-
transitions :
|
493
|
+
transitions from: :standing, to: :walking
|
339
494
|
end
|
340
495
|
event :run do
|
341
|
-
transitions :
|
496
|
+
transitions from: [:standing, :walking], to: :running
|
342
497
|
end
|
343
498
|
event :hold do
|
344
|
-
transitions :
|
499
|
+
transitions from: [:walking, :running], to: :standing
|
345
500
|
end
|
346
501
|
end
|
347
502
|
|
348
|
-
aasm(:work) do
|
349
|
-
state :sleeping, :
|
503
|
+
aasm(:work, column: 'work_state') do
|
504
|
+
state :sleeping, initial: true
|
350
505
|
state :processing
|
351
506
|
|
352
507
|
event :start do
|
353
|
-
transitions :
|
508
|
+
transitions from: :sleeping, to: :processing
|
354
509
|
end
|
355
510
|
event :stop do
|
356
|
-
transitions :
|
511
|
+
transitions from: :processing, to: :sleeping
|
357
512
|
end
|
358
513
|
end
|
359
514
|
end
|
@@ -362,32 +517,202 @@ simple = SimpleMultipleExample.new
|
|
362
517
|
|
363
518
|
simple.aasm(:move).current_state
|
364
519
|
# => :standing
|
365
|
-
simple.aasm(:work).
|
520
|
+
simple.aasm(:work).current_state
|
366
521
|
# => :sleeping
|
367
522
|
|
368
523
|
simple.start
|
369
524
|
simple.aasm(:move).current_state
|
370
525
|
# => :standing
|
371
|
-
simple.aasm(:work).
|
526
|
+
simple.aasm(:work).current_state
|
372
527
|
# => :processing
|
373
528
|
|
374
529
|
```
|
375
530
|
|
376
|
-
|
377
|
-
|
378
|
-
|
531
|
+
#### Handling naming conflicts between multiple state machines
|
532
|
+
|
533
|
+
_AASM_ doesn't prohibit to define the same event in more than one state
|
534
|
+
machine. If no namespace is provided, the latest definition "wins" and
|
535
|
+
overrides previous definitions. Nonetheless, a warning is issued:
|
536
|
+
`SimpleMultipleExample: overriding method 'run'!`.
|
537
|
+
|
538
|
+
Alternatively, you can provide a namespace for each state machine:
|
539
|
+
|
540
|
+
```ruby
|
541
|
+
class NamespacedMultipleExample
|
542
|
+
include AASM
|
543
|
+
aasm(:status) do
|
544
|
+
state :unapproved, initial: true
|
545
|
+
state :approved
|
546
|
+
|
547
|
+
event :approve do
|
548
|
+
transitions from: :unapproved, to: :approved
|
549
|
+
end
|
550
|
+
|
551
|
+
event :unapprove do
|
552
|
+
transitions from: :approved, to: :unapproved
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
aasm(:review_status, namespace: :review) do
|
557
|
+
state :unapproved, initial: true
|
558
|
+
state :approved
|
559
|
+
|
560
|
+
event :approve do
|
561
|
+
transitions from: :unapproved, to: :approved
|
562
|
+
end
|
563
|
+
|
564
|
+
event :unapprove do
|
565
|
+
transitions from: :approved, to: :unapproved
|
566
|
+
end
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
namespaced = NamespacedMultipleExample.new
|
571
|
+
|
572
|
+
namespaced.aasm(:status).current_state
|
573
|
+
# => :unapproved
|
574
|
+
namespaced.aasm(:review_status).current_state
|
575
|
+
# => :unapproved
|
576
|
+
namespaced.approve_review
|
577
|
+
namespaced.aasm(:review_status).current_state
|
578
|
+
# => :approved
|
579
|
+
```
|
379
580
|
|
380
581
|
All _AASM_ class- and instance-level `aasm` methods accept a state machine selector.
|
381
582
|
So, for example, to use inspection on a class level, you have to use
|
382
583
|
|
383
584
|
```ruby
|
384
|
-
SimpleMultipleExample.aasm(:
|
585
|
+
SimpleMultipleExample.aasm(:move).states.map(&:name)
|
385
586
|
# => [:standing, :walking, :running]
|
386
587
|
```
|
387
588
|
|
388
|
-
|
389
|
-
|
589
|
+
### Binding event
|
590
|
+
|
591
|
+
Allow an event to be bound to another
|
592
|
+
```ruby
|
593
|
+
class Example
|
594
|
+
include AASM
|
595
|
+
|
596
|
+
aasm(:work) do
|
597
|
+
state :sleeping, initial: true
|
598
|
+
state :processing
|
599
|
+
|
600
|
+
event :start do
|
601
|
+
transitions from: :sleeping, to: :processing
|
602
|
+
end
|
603
|
+
event :stop do
|
604
|
+
transitions from: :processing, to: :sleeping
|
605
|
+
end
|
606
|
+
end
|
390
607
|
|
608
|
+
aasm(:question) do
|
609
|
+
state :answered, initial: true
|
610
|
+
state :asked
|
611
|
+
|
612
|
+
event :ask, binding_event: :start do
|
613
|
+
transitions from: :answered, to: :asked
|
614
|
+
end
|
615
|
+
event :answer, binding_event: :stop do
|
616
|
+
transitions from: :asked, to: :answered
|
617
|
+
end
|
618
|
+
end
|
619
|
+
end
|
620
|
+
|
621
|
+
example = Example.new
|
622
|
+
example.aasm(:work).current_state #=> :sleeping
|
623
|
+
example.aasm(:question).current_state #=> :answered
|
624
|
+
example.ask
|
625
|
+
example.aasm(:work).current_state #=> :processing
|
626
|
+
example.aasm(:question).current_state #=> :asked
|
627
|
+
```
|
628
|
+
|
629
|
+
### Auto-generated Status Constants
|
630
|
+
|
631
|
+
AASM automatically [generates constants](https://github.com/aasm/aasm/pull/60)
|
632
|
+
for each status so you don't have to explicitly define them.
|
633
|
+
|
634
|
+
```ruby
|
635
|
+
class Foo
|
636
|
+
include AASM
|
637
|
+
|
638
|
+
aasm do
|
639
|
+
state :initialized
|
640
|
+
state :calculated
|
641
|
+
state :finalized
|
642
|
+
end
|
643
|
+
end
|
644
|
+
|
645
|
+
> Foo::STATE_INITIALIZED
|
646
|
+
#=> :initialized
|
647
|
+
> Foo::STATE_CALCULATED
|
648
|
+
#=> :calculated
|
649
|
+
```
|
650
|
+
|
651
|
+
### Extending AASM
|
652
|
+
|
653
|
+
AASM allows you to easily extend `AASM::Base` for your own application purposes.
|
654
|
+
|
655
|
+
Let's suppose we have common logic across many AASM models. We can embody this logic in a sub-class of `AASM::Base`.
|
656
|
+
|
657
|
+
```ruby
|
658
|
+
class CustomAASMBase < AASM::Base
|
659
|
+
# A custom transiton that we want available across many AASM models.
|
660
|
+
def count_transitions!
|
661
|
+
klass.class_eval do
|
662
|
+
aasm with_klass: CustomAASMBase do
|
663
|
+
after_all_transitions :increment_transition_count
|
664
|
+
end
|
665
|
+
end
|
666
|
+
end
|
667
|
+
|
668
|
+
# A custom annotation that we want available across many AASM models.
|
669
|
+
def requires_guards!
|
670
|
+
klass.class_eval do
|
671
|
+
attr_reader :authorizable_called,
|
672
|
+
:transition_count,
|
673
|
+
:fillable_called
|
674
|
+
|
675
|
+
def authorizable?
|
676
|
+
@authorizable_called = true
|
677
|
+
end
|
678
|
+
|
679
|
+
def fillable?
|
680
|
+
@fillable_called = true
|
681
|
+
end
|
682
|
+
|
683
|
+
def increment_transition_count
|
684
|
+
@transition_count ||= 0
|
685
|
+
@transition_count += 1
|
686
|
+
end
|
687
|
+
end
|
688
|
+
end
|
689
|
+
end
|
690
|
+
```
|
691
|
+
|
692
|
+
When we declare our model that has an AASM state machine, we simply declare the AASM block with a `:with_klass` key to our own class.
|
693
|
+
|
694
|
+
```ruby
|
695
|
+
class SimpleCustomExample
|
696
|
+
include AASM
|
697
|
+
|
698
|
+
# Let's build an AASM state machine with our custom class.
|
699
|
+
aasm with_klass: CustomAASMBase do
|
700
|
+
requires_guards!
|
701
|
+
count_transitions!
|
702
|
+
|
703
|
+
state :initialised, initial: true
|
704
|
+
state :filled_out
|
705
|
+
state :authorised
|
706
|
+
|
707
|
+
event :fill_out do
|
708
|
+
transitions from: :initialised, to: :filled_out, guard: :fillable?
|
709
|
+
end
|
710
|
+
event :authorise do
|
711
|
+
transitions from: :filled_out, to: :authorised, guard: :authorizable?
|
712
|
+
end
|
713
|
+
end
|
714
|
+
end
|
715
|
+
```
|
391
716
|
|
392
717
|
|
393
718
|
### ActiveRecord
|
@@ -395,60 +720,80 @@ SimpleMultipleExample.aasm(:work).states
|
|
395
720
|
AASM comes with support for ActiveRecord and allows automatic persisting of the object's
|
396
721
|
state in the database.
|
397
722
|
|
723
|
+
Add `gem 'after_commit_everywhere', '~> 1.0'` to your Gemfile.
|
724
|
+
|
398
725
|
```ruby
|
399
726
|
class Job < ActiveRecord::Base
|
400
727
|
include AASM
|
401
728
|
|
402
729
|
aasm do # default column: aasm_state
|
403
|
-
state :sleeping, :
|
730
|
+
state :sleeping, initial: true
|
404
731
|
state :running
|
405
732
|
|
406
733
|
event :run do
|
407
|
-
transitions :
|
734
|
+
transitions from: :sleeping, to: :running
|
408
735
|
end
|
409
736
|
|
410
737
|
event :sleep do
|
411
|
-
transitions :
|
738
|
+
transitions from: :running, to: :sleeping
|
412
739
|
end
|
413
740
|
end
|
414
741
|
|
415
742
|
end
|
416
743
|
```
|
417
744
|
|
745
|
+
### Bang events
|
746
|
+
|
418
747
|
You can tell AASM to auto-save the object or leave it unsaved
|
419
748
|
|
420
749
|
```ruby
|
421
750
|
job = Job.new
|
422
751
|
job.run # not saved
|
423
752
|
job.run! # saved
|
753
|
+
|
754
|
+
# or
|
755
|
+
job.aasm.fire(:run) # not saved
|
756
|
+
job.aasm.fire!(:run) # saved
|
424
757
|
```
|
425
758
|
|
426
|
-
Saving includes running all validations on the `Job` class. If
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
759
|
+
Saving includes running all validations on the `Job` class. If
|
760
|
+
`whiny_persistence` flag is set to `true`, exception is raised in case of
|
761
|
+
failure. If `whiny_persistence` flag is set to `false`, methods with a bang return
|
762
|
+
`true` if the state transition is successful or `false` if an error occurs.
|
763
|
+
|
764
|
+
If you want make sure the state gets saved without running validations (and
|
765
|
+
thereby maybe persisting an invalid object state), simply tell AASM to skip the
|
766
|
+
validations. Be aware that when skipping validations, only the state column will
|
767
|
+
be updated in the database (just like ActiveRecord `update_column` is working).
|
431
768
|
|
432
769
|
```ruby
|
433
770
|
class Job < ActiveRecord::Base
|
434
771
|
include AASM
|
435
772
|
|
436
|
-
aasm :
|
437
|
-
state :sleeping, :
|
773
|
+
aasm skip_validation_on_save: true do
|
774
|
+
state :sleeping, initial: true
|
438
775
|
state :running
|
439
776
|
|
440
777
|
event :run do
|
441
|
-
transitions :
|
778
|
+
transitions from: :sleeping, to: :running
|
442
779
|
end
|
443
780
|
|
444
781
|
event :sleep do
|
445
|
-
transitions :
|
782
|
+
transitions from: :running, to: :sleeping
|
446
783
|
end
|
447
784
|
end
|
448
785
|
|
449
786
|
end
|
450
787
|
```
|
451
788
|
|
789
|
+
Also, you can skip the validation at instance level with `some_event_name_without_validation!` method.
|
790
|
+
With this you have the flexibility of having validation for all your transitions by default and then skip it wherever required.
|
791
|
+
Please note that only state column will be updated as mentioned in the above example.
|
792
|
+
|
793
|
+
```ruby
|
794
|
+
job.run_without_validation!
|
795
|
+
```
|
796
|
+
|
452
797
|
If you want to make sure that the _AASM_ column for storing the state is not directly assigned,
|
453
798
|
configure _AASM_ to not allow direct assignment, like this:
|
454
799
|
|
@@ -456,12 +801,12 @@ configure _AASM_ to not allow direct assignment, like this:
|
|
456
801
|
class Job < ActiveRecord::Base
|
457
802
|
include AASM
|
458
803
|
|
459
|
-
aasm :
|
460
|
-
state :sleeping, :
|
804
|
+
aasm no_direct_assignment: true do
|
805
|
+
state :sleeping, initial: true
|
461
806
|
state :running
|
462
807
|
|
463
808
|
event :run do
|
464
|
-
transitions :
|
809
|
+
transitions from: :sleeping, to: :running
|
465
810
|
end
|
466
811
|
end
|
467
812
|
|
@@ -477,6 +822,37 @@ job.aasm_state = :running # => raises AASM::NoDirectAssignmentError
|
|
477
822
|
job.aasm_state # => 'sleeping'
|
478
823
|
```
|
479
824
|
|
825
|
+
### Timestamps
|
826
|
+
|
827
|
+
You can tell _AASM_ to try to write a timestamp whenever a new state is entered.
|
828
|
+
If `timestamps: true` is set, _AASM_ will look for a field named like the new state plus `_at` and try to fill it:
|
829
|
+
|
830
|
+
```ruby
|
831
|
+
class Job < ActiveRecord::Base
|
832
|
+
include AASM
|
833
|
+
|
834
|
+
aasm timestamps: true do
|
835
|
+
state :sleeping, initial: true
|
836
|
+
state :running
|
837
|
+
|
838
|
+
event :run do
|
839
|
+
transitions from: :sleeping, to: :running
|
840
|
+
end
|
841
|
+
end
|
842
|
+
end
|
843
|
+
```
|
844
|
+
|
845
|
+
resulting in this:
|
846
|
+
|
847
|
+
```ruby
|
848
|
+
job = Job.create
|
849
|
+
job.running_at # => nil
|
850
|
+
job.run!
|
851
|
+
job.running_at # => 2020-02-20 20:00:00
|
852
|
+
```
|
853
|
+
|
854
|
+
Missing timestamp fields are silently ignored, so it is not necessary to have setters (such as ActiveRecord columns) for *all* states when using this option.
|
855
|
+
|
480
856
|
#### ActiveRecord enums
|
481
857
|
|
482
858
|
You can use
|
@@ -492,8 +868,8 @@ class Job < ActiveRecord::Base
|
|
492
868
|
running: 99
|
493
869
|
}
|
494
870
|
|
495
|
-
aasm :
|
496
|
-
state :sleeping, :
|
871
|
+
aasm column: :state, enum: true do
|
872
|
+
state :sleeping, initial: true
|
497
873
|
state :running
|
498
874
|
end
|
499
875
|
end
|
@@ -513,7 +889,7 @@ to ```false```.
|
|
513
889
|
|
514
890
|
### Sequel
|
515
891
|
|
516
|
-
AASM also supports [Sequel](http://sequel.jeremyevans.net/) besides _ActiveRecord_,
|
892
|
+
AASM also supports [Sequel](http://sequel.jeremyevans.net/) besides _ActiveRecord_, and _Mongoid_.
|
517
893
|
|
518
894
|
```ruby
|
519
895
|
class Job < Sequel::Model
|
@@ -528,6 +904,11 @@ end
|
|
528
904
|
However it's not yet as feature complete as _ActiveRecord_. For example, there are
|
529
905
|
scopes defined yet. See [Automatic Scopes](#automatic-scopes).
|
530
906
|
|
907
|
+
### Dynamoid
|
908
|
+
|
909
|
+
Since version `4.8.0` _AASM_ also supports [Dynamoid](http://joshsymonds.com/Dynamoid/) as
|
910
|
+
persistence ORM.
|
911
|
+
|
531
912
|
### Mongoid
|
532
913
|
|
533
914
|
AASM also supports persistence to Mongodb if you're using Mongoid. Make sure
|
@@ -544,23 +925,40 @@ class Job
|
|
544
925
|
end
|
545
926
|
```
|
546
927
|
|
547
|
-
###
|
928
|
+
### NoBrainer
|
548
929
|
|
549
|
-
AASM also supports persistence to
|
550
|
-
|
930
|
+
AASM also supports persistence to [RethinkDB](https://www.rethinkdb.com/)
|
931
|
+
if you're using [Nobrainer](http://nobrainer.io/).
|
932
|
+
Make sure to include NoBrainer::Document before you include AASM.
|
551
933
|
|
552
934
|
```ruby
|
553
935
|
class Job
|
554
|
-
include
|
936
|
+
include NoBrainer::Document
|
555
937
|
include AASM
|
556
|
-
|
557
|
-
key :aasm_state, Symbol
|
938
|
+
field :aasm_state
|
558
939
|
aasm do
|
559
940
|
...
|
560
941
|
end
|
561
942
|
end
|
562
943
|
```
|
563
944
|
|
945
|
+
### Redis
|
946
|
+
|
947
|
+
AASM also supports persistence in Redis via
|
948
|
+
[Redis::Objects](https://github.com/nateware/redis-objects).
|
949
|
+
Make sure to include Redis::Objects before you include AASM. Note that non-bang
|
950
|
+
events will work as bang events, persisting the changes on every call.
|
951
|
+
|
952
|
+
```ruby
|
953
|
+
class User
|
954
|
+
include Redis::Objects
|
955
|
+
include AASM
|
956
|
+
|
957
|
+
aasm do
|
958
|
+
end
|
959
|
+
end
|
960
|
+
```
|
961
|
+
|
564
962
|
### Automatic Scopes
|
565
963
|
|
566
964
|
AASM will automatically create scope methods for each state in the model.
|
@@ -570,7 +968,7 @@ class Job < ActiveRecord::Base
|
|
570
968
|
include AASM
|
571
969
|
|
572
970
|
aasm do
|
573
|
-
state :sleeping, :
|
971
|
+
state :sleeping, initial: true
|
574
972
|
state :running
|
575
973
|
state :cleaning
|
576
974
|
end
|
@@ -599,8 +997,8 @@ defining the `AASM` states, like this:
|
|
599
997
|
class Job < ActiveRecord::Base
|
600
998
|
include AASM
|
601
999
|
|
602
|
-
aasm :
|
603
|
-
state :sleeping, :
|
1000
|
+
aasm create_scopes: false do
|
1001
|
+
state :sleeping, initial: true
|
604
1002
|
state :running
|
605
1003
|
state :cleaning
|
606
1004
|
end
|
@@ -614,6 +1012,17 @@ Since version *3.0.13* AASM supports ActiveRecord transactions. So whenever a tr
|
|
614
1012
|
callback or the state update fails, all changes to any database record are rolled back.
|
615
1013
|
Mongodb does not support transactions.
|
616
1014
|
|
1015
|
+
There are currently 3 transactional callbacks that can be handled on the event, and 2 transactional callbacks for all events.
|
1016
|
+
|
1017
|
+
```ruby
|
1018
|
+
event before_all_transactions
|
1019
|
+
event before_transaction
|
1020
|
+
event aasm_fire_event (within transaction)
|
1021
|
+
event after_commit (if event successful)
|
1022
|
+
event after_transaction
|
1023
|
+
event after_all_transactions
|
1024
|
+
```
|
1025
|
+
|
617
1026
|
If you want to make sure a depending action happens only after the transaction is committed,
|
618
1027
|
use the `after_commit` callback along with the auto-save (bang) methods, like this:
|
619
1028
|
|
@@ -622,11 +1031,11 @@ class Job < ActiveRecord::Base
|
|
622
1031
|
include AASM
|
623
1032
|
|
624
1033
|
aasm do
|
625
|
-
state :sleeping, :
|
1034
|
+
state :sleeping, initial: true
|
626
1035
|
state :running
|
627
1036
|
|
628
|
-
event :run, :
|
629
|
-
transitions :
|
1037
|
+
event :run, after_commit: :notify_about_running_job do
|
1038
|
+
transitions from: :sleeping, to: :running
|
630
1039
|
end
|
631
1040
|
end
|
632
1041
|
|
@@ -648,18 +1057,24 @@ job.run
|
|
648
1057
|
job.save! #notify_about_running_job is not run
|
649
1058
|
```
|
650
1059
|
|
1060
|
+
Please note that `:after_commit` AASM callbacks behaves around custom implementation
|
1061
|
+
of transaction pattern rather than a real-life DB transaction. This fact still causes
|
1062
|
+
the race conditions and redundant callback calls within nested transaction. In order
|
1063
|
+
to fix that it's highly recommended to add `gem 'after_commit_everywhere', '~> 1.0'`
|
1064
|
+
to your `Gemfile`.
|
1065
|
+
|
651
1066
|
If you want to encapsulate state changes within an own transaction, the behavior
|
652
1067
|
of this nested transaction might be confusing. Take a look at
|
653
1068
|
[ActiveRecord Nested Transactions](http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html)
|
654
1069
|
if you want to know more about this. Nevertheless, AASM by default requires a new transaction
|
655
|
-
`transaction(:
|
1070
|
+
`transaction(requires_new: true)`. You can override this behavior by changing
|
656
1071
|
the configuration
|
657
1072
|
|
658
1073
|
```ruby
|
659
1074
|
class Job < ActiveRecord::Base
|
660
1075
|
include AASM
|
661
1076
|
|
662
|
-
aasm :
|
1077
|
+
aasm requires_new_transaction: false do
|
663
1078
|
...
|
664
1079
|
end
|
665
1080
|
|
@@ -667,7 +1082,60 @@ class Job < ActiveRecord::Base
|
|
667
1082
|
end
|
668
1083
|
```
|
669
1084
|
|
670
|
-
which then leads to `transaction(:
|
1085
|
+
which then leads to `transaction(requires_new: false)`, the Rails default.
|
1086
|
+
|
1087
|
+
Additionally, if you do not want any of your ActiveRecord actions to be
|
1088
|
+
wrapped in a transaction, you can specify the `use_transactions` flag. This can
|
1089
|
+
be useful if you want want to persist things to the database that happen as a
|
1090
|
+
result of a transaction or callback, even when some error occurs. The
|
1091
|
+
`use_transactions` flag is true by default.
|
1092
|
+
|
1093
|
+
```ruby
|
1094
|
+
class Job < ActiveRecord::Base
|
1095
|
+
include AASM
|
1096
|
+
|
1097
|
+
aasm use_transactions: false do
|
1098
|
+
...
|
1099
|
+
end
|
1100
|
+
|
1101
|
+
...
|
1102
|
+
end
|
1103
|
+
```
|
1104
|
+
|
1105
|
+
### Pessimistic Locking
|
1106
|
+
|
1107
|
+
AASM supports [ActiveRecord pessimistic locking via `with_lock`](http://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html#method-i-with_lock) for database persistence layers.
|
1108
|
+
|
1109
|
+
| Option | Purpose |
|
1110
|
+
| ------ | ------- |
|
1111
|
+
| `false` (default) | No lock is obtained | |
|
1112
|
+
| `true` | Obtain a blocking pessimistic lock e.g. `FOR UPDATE` |
|
1113
|
+
| String | Obtain a lock based on the SQL string e.g. `FOR UPDATE NOWAIT` |
|
1114
|
+
|
1115
|
+
|
1116
|
+
```ruby
|
1117
|
+
class Job < ActiveRecord::Base
|
1118
|
+
include AASM
|
1119
|
+
|
1120
|
+
aasm requires_lock: true do
|
1121
|
+
...
|
1122
|
+
end
|
1123
|
+
|
1124
|
+
...
|
1125
|
+
end
|
1126
|
+
```
|
1127
|
+
|
1128
|
+
```ruby
|
1129
|
+
class Job < ActiveRecord::Base
|
1130
|
+
include AASM
|
1131
|
+
|
1132
|
+
aasm requires_lock: 'FOR UPDATE NOWAIT' do
|
1133
|
+
...
|
1134
|
+
end
|
1135
|
+
|
1136
|
+
...
|
1137
|
+
end
|
1138
|
+
```
|
671
1139
|
|
672
1140
|
|
673
1141
|
### Column name & migration
|
@@ -679,15 +1147,21 @@ this by defining your favorite column name, using `:column` like this:
|
|
679
1147
|
class Job < ActiveRecord::Base
|
680
1148
|
include AASM
|
681
1149
|
|
682
|
-
aasm :
|
1150
|
+
aasm column: :my_state do
|
683
1151
|
...
|
684
1152
|
end
|
685
1153
|
|
1154
|
+
aasm :another_state_machine, column: :second_state do
|
1155
|
+
...
|
1156
|
+
end
|
686
1157
|
end
|
687
1158
|
```
|
688
1159
|
|
689
1160
|
Whatever column name is used, make sure to add a migration to provide this column
|
690
|
-
(of type `string`)
|
1161
|
+
(of type `string`).
|
1162
|
+
Do not add default value for column at the database level. If you add default
|
1163
|
+
value in database then AASM callbacks on the initial state will not be fired upon
|
1164
|
+
instantiation of the model.
|
691
1165
|
|
692
1166
|
```ruby
|
693
1167
|
class AddJobState < ActiveRecord::Migration
|
@@ -701,41 +1175,136 @@ class AddJobState < ActiveRecord::Migration
|
|
701
1175
|
end
|
702
1176
|
```
|
703
1177
|
|
1178
|
+
### Log State Changes
|
1179
|
+
|
1180
|
+
Logging state change can be done using [paper_trail](https://github.com/paper-trail-gem/paper_trail) gem
|
1181
|
+
|
1182
|
+
Example of implementation can be found here [https://github.com/nitsujri/aasm-papertrail-example](https://github.com/nitsujri/aasm-papertrail-example)
|
1183
|
+
|
1184
|
+
|
704
1185
|
### Inspection
|
705
1186
|
|
706
|
-
AASM supports
|
1187
|
+
AASM supports query methods for states and events
|
707
1188
|
|
708
|
-
Given
|
1189
|
+
Given the following `Job` class:
|
1190
|
+
|
1191
|
+
```ruby
|
1192
|
+
class Job
|
1193
|
+
include AASM
|
1194
|
+
|
1195
|
+
aasm do
|
1196
|
+
state :sleeping, initial: true
|
1197
|
+
state :running, :cleaning
|
1198
|
+
|
1199
|
+
event :run do
|
1200
|
+
transitions from: :sleeping, to: :running
|
1201
|
+
end
|
1202
|
+
|
1203
|
+
event :clean do
|
1204
|
+
transitions from: :running, to: :cleaning, guard: :cleaning_needed?
|
1205
|
+
end
|
1206
|
+
|
1207
|
+
event :sleep do
|
1208
|
+
transitions from: [:running, :cleaning], to: :sleeping
|
1209
|
+
end
|
1210
|
+
end
|
1211
|
+
|
1212
|
+
def cleaning_needed?
|
1213
|
+
false
|
1214
|
+
end
|
1215
|
+
end
|
1216
|
+
```
|
709
1217
|
|
710
1218
|
```ruby
|
711
1219
|
# show all states
|
712
1220
|
Job.aasm.states.map(&:name)
|
713
|
-
|
1221
|
+
#=> [:sleeping, :running, :cleaning]
|
714
1222
|
|
715
1223
|
job = Job.new
|
716
1224
|
|
717
|
-
# show all permitted (
|
718
|
-
job.aasm.states(:
|
719
|
-
|
1225
|
+
# show all permitted states (from initial state)
|
1226
|
+
job.aasm.states(permitted: true).map(&:name)
|
1227
|
+
#=> [:running]
|
1228
|
+
|
1229
|
+
# List all the permitted transitions(event and state pairs) from initial state
|
1230
|
+
job.aasm.permitted_transitions
|
1231
|
+
#=> [{ :event => :run, :state => :running }]
|
1232
|
+
|
720
1233
|
job.run
|
721
|
-
job.aasm.states(:
|
722
|
-
|
1234
|
+
job.aasm.states(permitted: true).map(&:name)
|
1235
|
+
#=> [:sleeping]
|
723
1236
|
|
724
|
-
# show all
|
1237
|
+
# show all non permitted states
|
1238
|
+
job.aasm.states(permitted: false).map(&:name)
|
1239
|
+
#=> [:cleaning]
|
1240
|
+
|
1241
|
+
# show all possible (triggerable) events from the current state
|
725
1242
|
job.aasm.events.map(&:name)
|
726
|
-
|
727
|
-
|
728
|
-
|
1243
|
+
#=> [:clean, :sleep]
|
1244
|
+
|
1245
|
+
# show all permitted events
|
1246
|
+
job.aasm.events(permitted: true).map(&:name)
|
1247
|
+
#=> [:sleep]
|
1248
|
+
|
1249
|
+
# show all non permitted events
|
1250
|
+
job.aasm.events(permitted: false).map(&:name)
|
1251
|
+
#=> [:clean]
|
1252
|
+
|
1253
|
+
# show all possible events except a specific one
|
1254
|
+
job.aasm.events(reject: :sleep).map(&:name)
|
1255
|
+
#=> [:clean]
|
729
1256
|
|
730
1257
|
# list states for select
|
731
1258
|
Job.aasm.states_for_select
|
732
|
-
|
1259
|
+
#=> [["Sleeping", "sleeping"], ["Running", "running"], ["Cleaning", "cleaning"]]
|
1260
|
+
|
1261
|
+
# show permitted states with guard parameter
|
1262
|
+
job.aasm.states({permitted: true}, guard_parameter).map(&:name)
|
1263
|
+
```
|
1264
|
+
|
1265
|
+
|
1266
|
+
### Warning output
|
1267
|
+
|
1268
|
+
Warnings are by default printed to `STDERR`. If you want to log those warnings to another output,
|
1269
|
+
use
|
1270
|
+
|
1271
|
+
```ruby
|
1272
|
+
class Job
|
1273
|
+
include AASM
|
1274
|
+
|
1275
|
+
aasm logger: Rails.logger do
|
1276
|
+
...
|
1277
|
+
end
|
1278
|
+
end
|
733
1279
|
```
|
734
1280
|
|
1281
|
+
You can hide warnings by setting `AASM::Configuration.hide_warnings = true`
|
1282
|
+
|
1283
|
+
### RubyMotion support
|
1284
|
+
|
1285
|
+
Now supports [CodeDataQuery](https://github.com/infinitered/cdq.git) !
|
1286
|
+
However I'm still in the process of submitting my compatibility updates to their repository.
|
1287
|
+
In the meantime you can use [my fork](https://github.com/Infotaku/cdq.git), there may still be some minor issues but I intend to extensively use it myself, so fixes should come fast.
|
1288
|
+
|
1289
|
+
Warnings:
|
1290
|
+
- Due to RubyMotion Proc's lack of 'source_location' method, it may be harder
|
1291
|
+
to find out the origin of a "cannot transition from" error. I would recommend using
|
1292
|
+
the 'instance method symbol / string' way whenever possible when defining guardians and callbacks.
|
1293
|
+
|
735
1294
|
|
736
1295
|
### Testing
|
737
1296
|
|
738
|
-
|
1297
|
+
#### RSpec
|
1298
|
+
|
1299
|
+
AASM provides some matchers for [RSpec](http://rspec.info):
|
1300
|
+
* `transition_from`,
|
1301
|
+
* `have_state`, `allow_event`
|
1302
|
+
* and `allow_transition_to`.
|
1303
|
+
|
1304
|
+
##### Installation Instructions:
|
1305
|
+
* Add `require 'aasm/rspec'` to your `spec_helper.rb` file.
|
1306
|
+
|
1307
|
+
##### Examples Of Usage in Rspec:
|
739
1308
|
|
740
1309
|
```ruby
|
741
1310
|
# classes with only the default state machine
|
@@ -748,6 +1317,8 @@ expect(job).to allow_event :run
|
|
748
1317
|
expect(job).to_not allow_event :clean
|
749
1318
|
expect(job).to allow_transition_to(:running)
|
750
1319
|
expect(job).to_not allow_transition_to(:cleaning)
|
1320
|
+
# on_event also accept multiple arguments
|
1321
|
+
expect(job).to transition_from(:sleeping).to(:running).on_event(:run, :defragmentation)
|
751
1322
|
|
752
1323
|
# classes with multiple state machine
|
753
1324
|
multiple = SimpleMultipleExample.new
|
@@ -767,6 +1338,96 @@ expect(multiple).to allow_event(:start).on(:move)
|
|
767
1338
|
expect(multiple).to_not allow_event(:stop).on(:move)
|
768
1339
|
expect(multiple).to allow_transition_to(:processing).on(:move)
|
769
1340
|
expect(multiple).to_not allow_transition_to(:sleeping).on(:move)
|
1341
|
+
# allow_event also accepts arguments
|
1342
|
+
expect(job).to allow_event(:run).with(:defragmentation)
|
1343
|
+
|
1344
|
+
```
|
1345
|
+
|
1346
|
+
#### Minitest
|
1347
|
+
|
1348
|
+
AASM provides assertions and rspec-like expectations for [Minitest](https://github.com/seattlerb/minitest).
|
1349
|
+
|
1350
|
+
##### Assertions
|
1351
|
+
|
1352
|
+
List of supported assertions: `assert_have_state`, `refute_have_state`, `assert_transitions_from`, `refute_transitions_from`, `assert_event_allowed`, `refute_event_allowed`, `assert_transition_to_allowed`, `refute_transition_to_allowed`.
|
1353
|
+
|
1354
|
+
|
1355
|
+
##### Examples Of Usage (Minitest):
|
1356
|
+
|
1357
|
+
Add `require 'aasm/minitest'` to your `test_helper.rb` file and use them like this:
|
1358
|
+
|
1359
|
+
```ruby
|
1360
|
+
# classes with only the default state machine
|
1361
|
+
job = Job.new
|
1362
|
+
assert_transitions_from job, :sleeping, to: :running, on_event: :run
|
1363
|
+
refute_transitions_from job, :sleeping, to: :cleaning, on_event: :run
|
1364
|
+
assert_have_state job, :sleeping
|
1365
|
+
refute_have_state job, :running
|
1366
|
+
assert_event_allowed job, :run
|
1367
|
+
refute_event_allowed job, :clean
|
1368
|
+
assert_transition_to_allowed job, :running
|
1369
|
+
refute_transition_to_allowed job, :cleaning
|
1370
|
+
# on_event also accept arguments
|
1371
|
+
assert_transitions_from job, :sleeping, :defragmentation, to: :running, on_event: :run
|
1372
|
+
|
1373
|
+
# classes with multiple state machine
|
1374
|
+
multiple = SimpleMultipleExample.new
|
1375
|
+
assert_transitions_from multiple, :standing, to: :walking, on_event: :walk, on: :move
|
1376
|
+
refute_transitions_from multiple, :standing, to: :running, on_event: :walk, on: :move
|
1377
|
+
assert_have_state multiple, :standing, on: :move
|
1378
|
+
refute_have_state multiple, :walking, on: :move
|
1379
|
+
assert_event_allowed multiple, :walk, on: :move
|
1380
|
+
refute_event_allowed multiple, :hold, on: :move
|
1381
|
+
assert_transition_to_allowed multiple, :walking, on: :move
|
1382
|
+
refute_transition_to_allowed multiple, :running, on: :move
|
1383
|
+
assert_transitions_from multiple, :sleeping, to: :processing, on_event: :start, on: :work
|
1384
|
+
refute_transitions_from multiple, :sleeping, to: :sleeping, on_event: :start, on: :work
|
1385
|
+
assert_have_state multiple, :sleeping, on: :work
|
1386
|
+
refute_have_state multiple, :processing, on: :work
|
1387
|
+
assert_event_allowed multiple, :start, on: :move
|
1388
|
+
refute_event_allowed multiple, :stop, on: :move
|
1389
|
+
assert_transition_to_allowed multiple, :processing, on: :move
|
1390
|
+
refute_transition_to_allowed multiple, :sleeping, on: :move
|
1391
|
+
```
|
1392
|
+
|
1393
|
+
##### Expectations
|
1394
|
+
|
1395
|
+
List of supported expectations: `must_transition_from`, `wont_transition_from`, `must_have_state`, `wont_have_state`, `must_allow_event`, `wont_allow_event`, `must_allow_transition_to`, `wont_allow_transition_to`.
|
1396
|
+
|
1397
|
+
Add `require 'aasm/minitest_spec'` to your `test_helper.rb` file and use them like this:
|
1398
|
+
|
1399
|
+
```ruby
|
1400
|
+
# classes with only the default state machine
|
1401
|
+
job = Job.new
|
1402
|
+
job.must_transition_from :sleeping, to: :running, on_event: :run
|
1403
|
+
job.wont_transition_from :sleeping, to: :cleaning, on_event: :run
|
1404
|
+
job.must_have_state :sleeping
|
1405
|
+
job.wont_have_state :running
|
1406
|
+
job.must_allow_event :run
|
1407
|
+
job.wont_allow_event :clean
|
1408
|
+
job.must_allow_transition_to :running
|
1409
|
+
job.wont_allow_transition_to :cleaning
|
1410
|
+
# on_event also accept arguments
|
1411
|
+
job.must_transition_from :sleeping, :defragmentation, to: :running, on_event: :run
|
1412
|
+
|
1413
|
+
# classes with multiple state machine
|
1414
|
+
multiple = SimpleMultipleExample.new
|
1415
|
+
multiple.must_transition_from :standing, to: :walking, on_event: :walk, on: :move
|
1416
|
+
multiple.wont_transition_from :standing, to: :running, on_event: :walk, on: :move
|
1417
|
+
multiple.must_have_state :standing, on: :move
|
1418
|
+
multiple.wont_have_state :walking, on: :move
|
1419
|
+
multiple.must_allow_event :walk, on: :move
|
1420
|
+
multiple.wont_allow_event :hold, on: :move
|
1421
|
+
multiple.must_allow_transition_to :walking, on: :move
|
1422
|
+
multiple.wont_allow_transition_to :running, on: :move
|
1423
|
+
multiple.must_transition_from :sleeping, to: :processing, on_event: :start, on: :work
|
1424
|
+
multiple.wont_transition_from :sleeping, to: :sleeping, on_event: :start, on: :work
|
1425
|
+
multiple.must_have_state :sleeping, on: :work
|
1426
|
+
multiple.wont_have_state :processing, on: :work
|
1427
|
+
multiple.must_allow_event :start, on: :move
|
1428
|
+
multiple.wont_allow_event :stop, on: :move
|
1429
|
+
multiple.must_allow_transition_to :processing, on: :move
|
1430
|
+
multiple.wont_allow_transition_to :sleeping, on: :move
|
770
1431
|
```
|
771
1432
|
|
772
1433
|
## <a id="installation">Installation ##
|
@@ -791,6 +1452,25 @@ gem 'aasm'
|
|
791
1452
|
% sudo gem install pkg/aasm-x.y.z.gem
|
792
1453
|
```
|
793
1454
|
|
1455
|
+
### Generators
|
1456
|
+
|
1457
|
+
After installing AASM you can run generator:
|
1458
|
+
|
1459
|
+
```sh
|
1460
|
+
% rails generate aasm NAME [COLUMN_NAME]
|
1461
|
+
```
|
1462
|
+
Replace NAME with the Model name, COLUMN_NAME is optional(default is 'aasm_state').
|
1463
|
+
This will create a model (if one does not exist) and configure it with aasm block.
|
1464
|
+
For ActiveRecord orm a migration file is added to add aasm state column to table.
|
1465
|
+
|
1466
|
+
### Docker
|
1467
|
+
|
1468
|
+
Run test suite easily on docker
|
1469
|
+
```
|
1470
|
+
1. docker-compose build aasm
|
1471
|
+
2. docker-compose run --rm aasm
|
1472
|
+
```
|
1473
|
+
|
794
1474
|
## Latest changes ##
|
795
1475
|
|
796
1476
|
Take a look at the [CHANGELOG](https://github.com/aasm/aasm/blob/master/CHANGELOG.md) for details about recent changes to the current version.
|
@@ -808,16 +1488,16 @@ Feel free to
|
|
808
1488
|
* [Scott Barron](https://github.com/rubyist) (2006–2009, original author)
|
809
1489
|
* [Travis Tilley](https://github.com/ttilley) (2009–2011)
|
810
1490
|
* [Thorsten Böttger](http://github.com/alto) (since 2011)
|
1491
|
+
* [Anil Maurya](http://github.com/anilmaurya) (since 2016)
|
1492
|
+
|
1493
|
+
|
1494
|
+
|
1495
|
+
## Stargazers over time
|
811
1496
|
|
1497
|
+
[![Stargazers over time](https://starchart.cc/aasm/aasm.svg)](https://starchart.cc/aasm/aasm)
|
812
1498
|
|
813
|
-
## Contributing ##
|
814
1499
|
|
815
|
-
|
816
|
-
2. Fork it
|
817
|
-
3. Create your feature branch (git checkout -b my-new-feature)
|
818
|
-
4. Commit your changes (git commit -am 'Added some feature')
|
819
|
-
5. Push to the branch (git push origin my-new-feature)
|
820
|
-
6. Create new Pull Request
|
1500
|
+
## [Contributing](CONTRIBUTING.md)
|
821
1501
|
|
822
1502
|
## Warranty ##
|
823
1503
|
|
@@ -828,7 +1508,7 @@ purpose.
|
|
828
1508
|
|
829
1509
|
## License ##
|
830
1510
|
|
831
|
-
Copyright (c) 2006-
|
1511
|
+
Copyright (c) 2006-2017 Scott Barron
|
832
1512
|
|
833
1513
|
Permission is hereby granted, free of charge, to any person obtaining
|
834
1514
|
a copy of this software and associated documentation files (the
|