aasm 4.11.1 → 5.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.travis.yml +56 -23
- data/Appraisals +67 -0
- data/CHANGELOG.md +112 -0
- data/CONTRIBUTING.md +24 -0
- data/Dockerfile +44 -0
- data/Gemfile +3 -21
- data/Gemfile.lock_old +151 -0
- data/LICENSE +1 -1
- data/README.md +540 -139
- data/Rakefile +6 -1
- data/TESTING.md +25 -0
- data/aasm.gemspec +5 -0
- data/docker-compose.yml +40 -0
- data/gemfiles/norails.gemfile +10 -0
- data/gemfiles/rails_4.2.gemfile +13 -11
- data/gemfiles/rails_4.2_mongoid_5.gemfile +8 -11
- data/gemfiles/rails_4.2_nobrainer.gemfile +9 -0
- data/gemfiles/rails_5.0.gemfile +11 -18
- data/gemfiles/rails_5.0_nobrainer.gemfile +9 -0
- data/gemfiles/rails_5.1.gemfile +14 -0
- data/gemfiles/rails_5.2.gemfile +14 -0
- data/lib/aasm/aasm.rb +40 -29
- data/lib/aasm/base.rb +61 -11
- data/lib/aasm/configuration.rb +10 -0
- data/lib/aasm/core/event.rb +45 -37
- 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 +47 -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 +17 -69
- data/lib/aasm/dsl_helper.rb +24 -22
- data/lib/aasm/errors.rb +4 -6
- data/lib/aasm/instance_base.rb +22 -4
- 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 +49 -105
- data/lib/aasm/persistence/base.rb +20 -5
- data/lib/aasm/persistence/core_data_query_persistence.rb +2 -1
- data/lib/aasm/persistence/dynamoid_persistence.rb +1 -1
- data/lib/aasm/persistence/mongoid_persistence.rb +26 -32
- 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 +16 -11
- data/lib/aasm/persistence/sequel_persistence.rb +36 -64
- data/lib/aasm/persistence.rb +3 -3
- 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 +5 -1
- data/lib/aasm/state_machine.rb +4 -2
- data/lib/aasm/state_machine_store.rb +5 -2
- data/lib/aasm/version.rb +1 -1
- data/lib/aasm.rb +5 -2
- data/lib/generators/aasm/orm_helpers.rb +6 -0
- data/lib/generators/active_record/aasm_generator.rb +3 -1
- data/lib/generators/active_record/templates/migration.rb +1 -1
- data/lib/generators/active_record/templates/migration_existing.rb +1 -1
- data/lib/generators/nobrainer/aasm_generator.rb +28 -0
- data/lib/motion-aasm.rb +3 -1
- data/spec/database.rb +20 -7
- data/spec/en.yml +0 -3
- data/spec/generators/active_record_generator_spec.rb +49 -40
- data/spec/generators/mongoid_generator_spec.rb +4 -6
- data/spec/generators/no_brainer_generator_spec.rb +29 -0
- data/spec/{en_deprecated_style.yml → localizer_test_model_deprecated_style.yml} +6 -3
- data/spec/localizer_test_model_new_style.yml +11 -0
- data/spec/models/active_record/active_record_callback.rb +93 -0
- data/spec/models/active_record/complex_active_record_example.rb +5 -1
- data/spec/models/active_record/instance_level_skip_validation_example.rb +19 -0
- data/spec/models/{invalid_persistor.rb → active_record/invalid_persistor.rb} +0 -2
- data/spec/models/active_record/localizer_test_model.rb +11 -3
- data/spec/models/active_record/namespaced.rb +16 -0
- data/spec/models/active_record/person.rb +23 -0
- data/spec/models/{silent_persistor.rb → active_record/silent_persistor.rb} +0 -2
- data/spec/models/active_record/simple_new_dsl.rb +15 -0
- data/spec/models/active_record/timestamp_example.rb +16 -0
- data/spec/models/{transactor.rb → active_record/transactor.rb} +25 -2
- data/spec/models/{validator.rb → active_record/validator.rb} +0 -2
- data/spec/models/active_record/work.rb +3 -0
- data/spec/models/{worker.rb → active_record/worker.rb} +0 -0
- data/spec/models/callbacks/basic.rb +5 -2
- data/spec/models/callbacks/with_state_arg.rb +5 -1
- data/spec/models/callbacks/with_state_arg_multiple.rb +4 -1
- data/spec/models/default_state.rb +1 -1
- data/spec/models/guard_arguments_check.rb +17 -0
- data/spec/models/guard_with_params.rb +1 -1
- data/spec/models/guardian_without_from_specified.rb +18 -0
- data/spec/models/mongoid/invalid_persistor_mongoid.rb +39 -0
- data/spec/models/mongoid/silent_persistor_mongoid.rb +39 -0
- data/spec/models/mongoid/timestamp_example_mongoid.rb +20 -0
- data/spec/models/mongoid/validator_mongoid.rb +100 -0
- data/spec/models/multiple_transitions_that_differ_only_by_guard.rb +31 -0
- data/spec/models/namespaced_multiple_example.rb +14 -0
- data/spec/models/nobrainer/complex_no_brainer_example.rb +36 -0
- data/spec/models/nobrainer/invalid_persistor_no_brainer.rb +39 -0
- data/spec/models/nobrainer/no_scope_no_brainer.rb +21 -0
- data/spec/models/nobrainer/nobrainer_relationships.rb +25 -0
- data/spec/models/nobrainer/silent_persistor_no_brainer.rb +39 -0
- data/spec/models/nobrainer/simple_new_dsl_nobrainer.rb +25 -0
- data/spec/models/{mongo_mapper/simple_mongo_mapper.rb → nobrainer/simple_no_brainer.rb} +8 -8
- data/spec/models/nobrainer/validator_no_brainer.rb +98 -0
- data/spec/models/parametrised_event.rb +7 -0
- data/spec/models/{mongo_mapper/complex_mongo_mapper_example.rb → redis/complex_redis_example.rb} +8 -5
- data/spec/models/redis/redis_multiple.rb +20 -0
- data/spec/models/redis/redis_simple.rb +20 -0
- data/spec/models/sequel/complex_sequel_example.rb +4 -3
- data/spec/models/sequel/invalid_persistor.rb +52 -0
- data/spec/models/sequel/sequel_multiple.rb +13 -13
- data/spec/models/sequel/sequel_simple.rb +13 -12
- data/spec/models/sequel/silent_persistor.rb +50 -0
- data/spec/models/sequel/transactor.rb +112 -0
- data/spec/models/sequel/validator.rb +93 -0
- data/spec/models/sequel/worker.rb +12 -0
- data/spec/models/simple_example.rb +8 -0
- data/spec/models/simple_example_with_guard_args.rb +17 -0
- data/spec/models/simple_multiple_example.rb +12 -0
- data/spec/models/sub_class.rb +34 -0
- data/spec/models/timestamps_example.rb +19 -0
- data/spec/models/timestamps_with_named_machine_example.rb +13 -0
- data/spec/spec_helper.rb +15 -33
- data/spec/spec_helpers/active_record.rb +8 -0
- data/spec/spec_helpers/dynamoid.rb +35 -0
- data/spec/spec_helpers/mongoid.rb +26 -0
- data/spec/spec_helpers/nobrainer.rb +15 -0
- data/spec/spec_helpers/redis.rb +18 -0
- data/spec/spec_helpers/remove_warnings.rb +1 -0
- data/spec/spec_helpers/sequel.rb +7 -0
- data/spec/unit/abstract_class_spec.rb +27 -0
- data/spec/unit/api_spec.rb +79 -72
- data/spec/unit/callback_multiple_spec.rb +7 -3
- data/spec/unit/callbacks_spec.rb +37 -2
- data/spec/unit/complex_example_spec.rb +12 -3
- data/spec/unit/complex_multiple_example_spec.rb +20 -4
- data/spec/unit/event_multiple_spec.rb +1 -1
- data/spec/unit/event_spec.rb +29 -4
- data/spec/unit/exception_spec.rb +1 -1
- data/spec/unit/guard_arguments_check_spec.rb +9 -0
- data/spec/unit/guard_spec.rb +17 -0
- data/spec/unit/guard_with_params_spec.rb +4 -0
- data/spec/unit/guard_without_from_specified_spec.rb +10 -0
- data/spec/unit/inspection_multiple_spec.rb +9 -5
- data/spec/unit/inspection_spec.rb +7 -3
- data/spec/unit/invoker_spec.rb +189 -0
- data/spec/unit/invokers/base_invoker_spec.rb +72 -0
- data/spec/unit/invokers/class_invoker_spec.rb +95 -0
- data/spec/unit/invokers/literal_invoker_spec.rb +86 -0
- data/spec/unit/invokers/proc_invoker_spec.rb +86 -0
- data/spec/unit/localizer_spec.rb +85 -52
- data/spec/unit/multiple_transitions_that_differ_only_by_guard_spec.rb +14 -0
- data/spec/unit/namespaced_multiple_example_spec.rb +22 -0
- data/spec/unit/override_warning_spec.rb +8 -0
- data/spec/unit/persistence/active_record_persistence_multiple_spec.rb +468 -447
- data/spec/unit/persistence/active_record_persistence_spec.rb +639 -486
- data/spec/unit/persistence/dynamoid_persistence_multiple_spec.rb +4 -9
- data/spec/unit/persistence/dynamoid_persistence_spec.rb +4 -9
- data/spec/unit/persistence/mongoid_persistence_multiple_spec.rb +83 -13
- data/spec/unit/persistence/mongoid_persistence_spec.rb +97 -13
- data/spec/unit/persistence/no_brainer_persistence_multiple_spec.rb +198 -0
- data/spec/unit/persistence/no_brainer_persistence_spec.rb +158 -0
- data/spec/unit/persistence/redis_persistence_multiple_spec.rb +88 -0
- data/spec/unit/persistence/redis_persistence_spec.rb +8 -32
- data/spec/unit/persistence/sequel_persistence_multiple_spec.rb +6 -11
- data/spec/unit/persistence/sequel_persistence_spec.rb +278 -10
- data/spec/unit/rspec_matcher_spec.rb +9 -0
- data/spec/unit/simple_example_spec.rb +15 -0
- data/spec/unit/simple_multiple_example_spec.rb +28 -0
- data/spec/unit/state_spec.rb +23 -7
- data/spec/unit/subclassing_multiple_spec.rb +37 -2
- data/spec/unit/subclassing_spec.rb +17 -2
- data/spec/unit/timestamps_spec.rb +32 -0
- data/spec/unit/transition_spec.rb +1 -1
- data/test/minitest_helper.rb +57 -0
- data/test/unit/minitest_matcher_test.rb +80 -0
- metadata +213 -37
- data/callbacks.txt +0 -51
- data/gemfiles/rails_3.2_stable.gemfile +0 -15
- data/gemfiles/rails_4.0.gemfile +0 -16
- data/gemfiles/rails_4.0_mongo_mapper.gemfile +0 -16
- data/gemfiles/rails_4.2_mongo_mapper.gemfile +0 -17
- data/lib/aasm/persistence/mongo_mapper_persistence.rb +0 -163
- data/spec/models/mongo_mapper/no_scope_mongo_mapper.rb +0 -21
- data/spec/models/mongo_mapper/simple_new_dsl_mongo_mapper.rb +0 -25
- data/spec/unit/persistence/mongo_mapper_persistence_multiple_spec.rb +0 -149
- data/spec/unit/persistence/mongo_mapper_persistence_spec.rb +0 -96
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,19 +73,19 @@ class Job
|
|
27
73
|
include AASM
|
28
74
|
|
29
75
|
aasm do
|
30
|
-
state :sleeping, :
|
76
|
+
state :sleeping, initial: true
|
31
77
|
state :running, :cleaning
|
32
78
|
|
33
79
|
event :run do
|
34
|
-
transitions :
|
80
|
+
transitions from: :sleeping, to: :running
|
35
81
|
end
|
36
82
|
|
37
83
|
event :clean do
|
38
|
-
transitions :
|
84
|
+
transitions from: :running, to: :cleaning
|
39
85
|
end
|
40
86
|
|
41
87
|
event :sleep do
|
42
|
-
transitions :
|
88
|
+
transitions from: [:running, :cleaning], to: :sleeping
|
43
89
|
end
|
44
90
|
end
|
45
91
|
|
@@ -65,7 +111,7 @@ AASM not to be *whiny*:
|
|
65
111
|
```ruby
|
66
112
|
class Job
|
67
113
|
...
|
68
|
-
aasm :
|
114
|
+
aasm whiny_transitions: false do
|
69
115
|
...
|
70
116
|
end
|
71
117
|
end
|
@@ -86,27 +132,27 @@ the transition succeeds :
|
|
86
132
|
|
87
133
|
### Callbacks
|
88
134
|
|
89
|
-
You can define a number of callbacks for your transitions. These methods will be
|
90
|
-
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:
|
91
137
|
|
92
138
|
```ruby
|
93
139
|
class Job
|
94
140
|
include AASM
|
95
141
|
|
96
142
|
aasm do
|
97
|
-
state :sleeping, :
|
98
|
-
state :running
|
143
|
+
state :sleeping, initial: true, before_enter: :do_something
|
144
|
+
state :running, before_enter: Proc.new { do_something && notify_somebody }
|
99
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 :
|
109
|
-
transitions :
|
154
|
+
transitions from: :sleeping, to: :running, after: Proc.new {|*args| set_process(*args) }
|
155
|
+
transitions from: :running, to: :finished, after: LogRunTime
|
110
156
|
end
|
111
157
|
|
112
158
|
event :sleep do
|
@@ -116,7 +162,7 @@ class Job
|
|
116
162
|
error do |e|
|
117
163
|
...
|
118
164
|
end
|
119
|
-
transitions :
|
165
|
+
transitions from: :running, to: :sleeping
|
120
166
|
end
|
121
167
|
end
|
122
168
|
|
@@ -151,6 +197,8 @@ is finished.
|
|
151
197
|
|
152
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:
|
153
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.
|
201
|
+
|
154
202
|
```ruby
|
155
203
|
class LogRunTime
|
156
204
|
# optional args parameter can be omitted, but if you define initialize
|
@@ -165,6 +213,51 @@ class LogRunTime
|
|
165
213
|
end
|
166
214
|
```
|
167
215
|
|
216
|
+
#### Parameters
|
217
|
+
You can pass parameters to events:
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
job = Job.new
|
221
|
+
job.run(:defragmentation)
|
222
|
+
```
|
223
|
+
|
224
|
+
All guards and after callbacks will receive these parameters. In this case `set_process` would be called with
|
225
|
+
`:defragmentation` argument.
|
226
|
+
|
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 gaurds and callbacks
|
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
|
238
|
+
In case of an error during the event processing the error is rescued and passed to `:error`
|
239
|
+
callback, which can handle it or re-raise it for further propagation.
|
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
|
+
|
249
|
+
During the transition's `:after` callback (and reliably only then, or in the global
|
250
|
+
`after_all_transitions` callback) you can access the originating state (the from-state)
|
251
|
+
and the target state (the to state), like this:
|
252
|
+
|
253
|
+
```ruby
|
254
|
+
def set_process(name)
|
255
|
+
logger.info "from #{aasm.from_state} to #{aasm.to_state}"
|
256
|
+
end
|
257
|
+
```
|
258
|
+
|
259
|
+
#### Lifecycle
|
260
|
+
|
168
261
|
Here you can see a list of all possible callbacks, together with their order of calling:
|
169
262
|
|
170
263
|
```ruby
|
@@ -180,8 +273,9 @@ begin
|
|
180
273
|
new_state before_enter
|
181
274
|
new_state enter
|
182
275
|
...update state...
|
183
|
-
|
184
|
-
|
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
|
185
279
|
old_state after_exit
|
186
280
|
new_state after_enter
|
187
281
|
event after
|
@@ -195,29 +289,7 @@ ensure
|
|
195
289
|
end
|
196
290
|
```
|
197
291
|
|
198
|
-
|
199
|
-
|
200
|
-
```ruby
|
201
|
-
job = Job.new
|
202
|
-
job.run(:running, :defragmentation)
|
203
|
-
```
|
204
|
-
|
205
|
-
In this case the `set_process` would be called with `:defragmentation` argument.
|
206
|
-
|
207
|
-
Note that when passing arguments to a state transition, the first argument must be the desired end state. In the above example, we wish to transition to `:running` state and run the callback with `:defragmentation` argument. You can also pass in `nil` as the desired end state, and AASM will try to transition to the first end state defined for that event.
|
208
|
-
|
209
|
-
In case of an error during the event processing the error is rescued and passed to `:error`
|
210
|
-
callback, which can handle it or re-raise it for further propagation.
|
211
|
-
|
212
|
-
During the transition's `:after` callback (and reliably only then, or in the global
|
213
|
-
`after_all_transitions` callback) you can access the originating state (the from-state)
|
214
|
-
and the target state (the to state), like this:
|
215
|
-
|
216
|
-
```ruby
|
217
|
-
def set_process(name)
|
218
|
-
logger.info "from #{aasm.from_state} to #{aasm.to_state}"
|
219
|
-
end
|
220
|
-
```
|
292
|
+
Use event's `after_commit` callback if it should be fired after database update.
|
221
293
|
|
222
294
|
#### The current event triggered
|
223
295
|
|
@@ -256,26 +328,34 @@ class Cleaner
|
|
256
328
|
include AASM
|
257
329
|
|
258
330
|
aasm do
|
259
|
-
state :idle, :
|
331
|
+
state :idle, initial: true
|
260
332
|
state :cleaning
|
261
333
|
|
262
334
|
event :clean do
|
263
|
-
transitions :
|
335
|
+
transitions from: :idle, to: :cleaning, guard: :cleaning_needed?
|
264
336
|
end
|
265
337
|
|
266
338
|
event :clean_if_needed do
|
267
|
-
transitions :
|
339
|
+
transitions from: :idle, to: :cleaning do
|
268
340
|
guard do
|
269
341
|
cleaning_needed?
|
270
342
|
end
|
271
343
|
end
|
272
|
-
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?
|
273
349
|
end
|
274
350
|
end
|
275
351
|
|
276
352
|
def cleaning_needed?
|
277
353
|
false
|
278
354
|
end
|
355
|
+
|
356
|
+
def if_dirty?(status)
|
357
|
+
status == :dirty
|
358
|
+
end
|
279
359
|
end
|
280
360
|
|
281
361
|
job = Cleaner.new
|
@@ -283,6 +363,9 @@ job.may_clean? # => false
|
|
283
363
|
job.clean # => raises AASM::InvalidTransition
|
284
364
|
job.may_clean_if_needed? # => true
|
285
365
|
job.clean_if_needed! # idle
|
366
|
+
|
367
|
+
job.clean_if_dirty(:clean) # => false
|
368
|
+
job.clean_if_dirty(:dirty) # => true
|
286
369
|
```
|
287
370
|
|
288
371
|
You can even provide a number of guards, which all have to succeed to proceed
|
@@ -291,16 +374,16 @@ You can even provide a number of guards, which all have to succeed to proceed
|
|
291
374
|
def walked_the_dog?; ...; end
|
292
375
|
|
293
376
|
event :sleep do
|
294
|
-
transitions :
|
377
|
+
transitions from: :running, to: :sleeping, guards: [:cleaning_needed?, :walked_the_dog?]
|
295
378
|
end
|
296
379
|
```
|
297
380
|
|
298
381
|
If you want to provide guards for all transitions within an event, you can use event guards
|
299
382
|
|
300
383
|
```ruby
|
301
|
-
event :sleep, :
|
302
|
-
transitions :
|
303
|
-
transitions :
|
384
|
+
event :sleep, guards: [:walked_the_dog?] do
|
385
|
+
transitions from: :running, to: :sleeping, guards: [:cleaning_needed?]
|
386
|
+
transitions from: :cleaning, to: :sleeping
|
304
387
|
end
|
305
388
|
```
|
306
389
|
|
@@ -308,15 +391,30 @@ If you prefer a more Ruby-like guard syntax, you can use `if` and `unless` as we
|
|
308
391
|
|
309
392
|
```ruby
|
310
393
|
event :clean do
|
311
|
-
transitions :
|
394
|
+
transitions from: :running, to: :cleaning, if: :cleaning_needed?
|
312
395
|
end
|
313
396
|
|
314
397
|
event :sleep do
|
315
|
-
transitions :
|
398
|
+
transitions from: :running, to: :sleeping, unless: :cleaning_needed?
|
316
399
|
end
|
317
400
|
end
|
318
401
|
```
|
319
402
|
|
403
|
+
You can invoke a Class instead a method since this 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
|
+
```
|
320
418
|
|
321
419
|
### Transitions
|
322
420
|
|
@@ -329,7 +427,7 @@ class Job
|
|
329
427
|
include AASM
|
330
428
|
|
331
429
|
aasm do
|
332
|
-
state :stage1, :
|
430
|
+
state :stage1, initial: true
|
333
431
|
state :stage2
|
334
432
|
state :stage3
|
335
433
|
state :completed
|
@@ -350,40 +448,67 @@ job.stage1_completed
|
|
350
448
|
job.aasm.current_state # stage3
|
351
449
|
```
|
352
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
|
+
```
|
353
478
|
|
354
479
|
### Multiple state machines per class
|
355
480
|
|
356
481
|
Multiple state machines per class are supported. Be aware though that _AASM_ has been
|
357
|
-
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.
|
358
483
|
|
359
484
|
```ruby
|
360
485
|
class SimpleMultipleExample
|
361
486
|
include AASM
|
362
|
-
aasm(:move) do
|
363
|
-
state :standing, :
|
487
|
+
aasm(:move, column: 'move_state') do
|
488
|
+
state :standing, initial: true
|
364
489
|
state :walking
|
365
490
|
state :running
|
366
491
|
|
367
492
|
event :walk do
|
368
|
-
transitions :
|
493
|
+
transitions from: :standing, to: :walking
|
369
494
|
end
|
370
495
|
event :run do
|
371
|
-
transitions :
|
496
|
+
transitions from: [:standing, :walking], to: :running
|
372
497
|
end
|
373
498
|
event :hold do
|
374
|
-
transitions :
|
499
|
+
transitions from: [:walking, :running], to: :standing
|
375
500
|
end
|
376
501
|
end
|
377
502
|
|
378
|
-
aasm(:work) do
|
379
|
-
state :sleeping, :
|
503
|
+
aasm(:work, column: 'work_state') do
|
504
|
+
state :sleeping, initial: true
|
380
505
|
state :processing
|
381
506
|
|
382
507
|
event :start do
|
383
|
-
transitions :
|
508
|
+
transitions from: :sleeping, to: :processing
|
384
509
|
end
|
385
510
|
event :stop do
|
386
|
-
transitions :
|
511
|
+
transitions from: :processing, to: :sleeping
|
387
512
|
end
|
388
513
|
end
|
389
514
|
end
|
@@ -392,31 +517,114 @@ simple = SimpleMultipleExample.new
|
|
392
517
|
|
393
518
|
simple.aasm(:move).current_state
|
394
519
|
# => :standing
|
395
|
-
simple.aasm(:work).
|
520
|
+
simple.aasm(:work).current_state
|
396
521
|
# => :sleeping
|
397
522
|
|
398
523
|
simple.start
|
399
524
|
simple.aasm(:move).current_state
|
400
525
|
# => :standing
|
401
|
-
simple.aasm(:work).
|
526
|
+
simple.aasm(:work).current_state
|
402
527
|
# => :processing
|
403
528
|
|
404
529
|
```
|
405
530
|
|
406
|
-
|
407
|
-
|
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:
|
408
536
|
`SimpleMultipleExample: overriding method 'run'!`.
|
409
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
|
+
```
|
580
|
+
|
410
581
|
All _AASM_ class- and instance-level `aasm` methods accept a state machine selector.
|
411
582
|
So, for example, to use inspection on a class level, you have to use
|
412
583
|
|
413
584
|
```ruby
|
414
|
-
SimpleMultipleExample.aasm(:
|
585
|
+
SimpleMultipleExample.aasm(:move).states.map(&:name)
|
415
586
|
# => [:standing, :walking, :running]
|
416
587
|
```
|
417
588
|
|
418
|
-
|
419
|
-
|
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
|
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
|
+
```
|
420
628
|
|
421
629
|
### Auto-generated Status Constants
|
422
630
|
|
@@ -451,7 +659,7 @@ class CustomAASMBase < AASM::Base
|
|
451
659
|
# A custom transiton that we want available across many AASM models.
|
452
660
|
def count_transitions!
|
453
661
|
klass.class_eval do
|
454
|
-
aasm :
|
662
|
+
aasm with_klass: CustomAASMBase do
|
455
663
|
after_all_transitions :increment_transition_count
|
456
664
|
end
|
457
665
|
end
|
@@ -481,26 +689,26 @@ class CustomAASMBase < AASM::Base
|
|
481
689
|
end
|
482
690
|
```
|
483
691
|
|
484
|
-
When we declare our model that has an AASM state machine, we simply declare the AASM block with a `:
|
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.
|
485
693
|
|
486
694
|
```ruby
|
487
695
|
class SimpleCustomExample
|
488
696
|
include AASM
|
489
697
|
|
490
698
|
# Let's build an AASM state machine with our custom class.
|
491
|
-
aasm :
|
699
|
+
aasm with_klass: CustomAASMBase do
|
492
700
|
requires_guards!
|
493
701
|
count_transitions!
|
494
702
|
|
495
|
-
state :initialised, :
|
703
|
+
state :initialised, initial: true
|
496
704
|
state :filled_out
|
497
705
|
state :authorised
|
498
706
|
|
499
707
|
event :fill_out do
|
500
|
-
transitions :
|
708
|
+
transitions from: :initialised, to: :filled_out, guard: :fillable?
|
501
709
|
end
|
502
710
|
event :authorise do
|
503
|
-
transitions :
|
711
|
+
transitions from: :filled_out, to: :authorised, guard: :authorizable?
|
504
712
|
end
|
505
713
|
end
|
506
714
|
end
|
@@ -512,36 +720,46 @@ end
|
|
512
720
|
AASM comes with support for ActiveRecord and allows automatic persisting of the object's
|
513
721
|
state in the database.
|
514
722
|
|
723
|
+
Add `gem 'after_commit_everywhere', '~> 1.0'` to your Gemfile.
|
724
|
+
|
515
725
|
```ruby
|
516
726
|
class Job < ActiveRecord::Base
|
517
727
|
include AASM
|
518
728
|
|
519
729
|
aasm do # default column: aasm_state
|
520
|
-
state :sleeping, :
|
730
|
+
state :sleeping, initial: true
|
521
731
|
state :running
|
522
732
|
|
523
733
|
event :run do
|
524
|
-
transitions :
|
734
|
+
transitions from: :sleeping, to: :running
|
525
735
|
end
|
526
736
|
|
527
737
|
event :sleep do
|
528
|
-
transitions :
|
738
|
+
transitions from: :running, to: :sleeping
|
529
739
|
end
|
530
740
|
end
|
531
741
|
|
532
742
|
end
|
533
743
|
```
|
534
744
|
|
745
|
+
### Bang events
|
746
|
+
|
535
747
|
You can tell AASM to auto-save the object or leave it unsaved
|
536
748
|
|
537
749
|
```ruby
|
538
750
|
job = Job.new
|
539
751
|
job.run # not saved
|
540
752
|
job.run! # saved
|
753
|
+
|
754
|
+
# or
|
755
|
+
job.aasm.fire(:run) # not saved
|
756
|
+
job.aasm.fire!(:run) # saved
|
541
757
|
```
|
542
758
|
|
543
|
-
Saving includes running all validations on the `Job` class
|
544
|
-
|
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.
|
545
763
|
|
546
764
|
If you want make sure the state gets saved without running validations (and
|
547
765
|
thereby maybe persisting an invalid object state), simply tell AASM to skip the
|
@@ -552,22 +770,30 @@ be updated in the database (just like ActiveRecord `update_column` is working).
|
|
552
770
|
class Job < ActiveRecord::Base
|
553
771
|
include AASM
|
554
772
|
|
555
|
-
aasm :
|
556
|
-
state :sleeping, :
|
773
|
+
aasm skip_validation_on_save: true do
|
774
|
+
state :sleeping, initial: true
|
557
775
|
state :running
|
558
776
|
|
559
777
|
event :run do
|
560
|
-
transitions :
|
778
|
+
transitions from: :sleeping, to: :running
|
561
779
|
end
|
562
780
|
|
563
781
|
event :sleep do
|
564
|
-
transitions :
|
782
|
+
transitions from: :running, to: :sleeping
|
565
783
|
end
|
566
784
|
end
|
567
785
|
|
568
786
|
end
|
569
787
|
```
|
570
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
|
+
|
571
797
|
If you want to make sure that the _AASM_ column for storing the state is not directly assigned,
|
572
798
|
configure _AASM_ to not allow direct assignment, like this:
|
573
799
|
|
@@ -575,12 +801,12 @@ configure _AASM_ to not allow direct assignment, like this:
|
|
575
801
|
class Job < ActiveRecord::Base
|
576
802
|
include AASM
|
577
803
|
|
578
|
-
aasm :
|
579
|
-
state :sleeping, :
|
804
|
+
aasm no_direct_assignment: true do
|
805
|
+
state :sleeping, initial: true
|
580
806
|
state :running
|
581
807
|
|
582
808
|
event :run do
|
583
|
-
transitions :
|
809
|
+
transitions from: :sleeping, to: :running
|
584
810
|
end
|
585
811
|
end
|
586
812
|
|
@@ -596,6 +822,37 @@ job.aasm_state = :running # => raises AASM::NoDirectAssignmentError
|
|
596
822
|
job.aasm_state # => 'sleeping'
|
597
823
|
```
|
598
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
|
+
|
599
856
|
#### ActiveRecord enums
|
600
857
|
|
601
858
|
You can use
|
@@ -611,8 +868,8 @@ class Job < ActiveRecord::Base
|
|
611
868
|
running: 99
|
612
869
|
}
|
613
870
|
|
614
|
-
aasm :
|
615
|
-
state :sleeping, :
|
871
|
+
aasm column: :state, enum: true do
|
872
|
+
state :sleeping, initial: true
|
616
873
|
state :running
|
617
874
|
end
|
618
875
|
end
|
@@ -632,7 +889,7 @@ to ```false```.
|
|
632
889
|
|
633
890
|
### Sequel
|
634
891
|
|
635
|
-
AASM also supports [Sequel](http://sequel.jeremyevans.net/) besides _ActiveRecord_,
|
892
|
+
AASM also supports [Sequel](http://sequel.jeremyevans.net/) besides _ActiveRecord_, and _Mongoid_.
|
636
893
|
|
637
894
|
```ruby
|
638
895
|
class Job < Sequel::Model
|
@@ -668,17 +925,17 @@ class Job
|
|
668
925
|
end
|
669
926
|
```
|
670
927
|
|
671
|
-
###
|
928
|
+
### NoBrainer
|
672
929
|
|
673
|
-
AASM also supports persistence to
|
674
|
-
|
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.
|
675
933
|
|
676
934
|
```ruby
|
677
935
|
class Job
|
678
|
-
include
|
936
|
+
include NoBrainer::Document
|
679
937
|
include AASM
|
680
|
-
|
681
|
-
key :aasm_state, Symbol
|
938
|
+
field :aasm_state
|
682
939
|
aasm do
|
683
940
|
...
|
684
941
|
end
|
@@ -687,8 +944,10 @@ end
|
|
687
944
|
|
688
945
|
### Redis
|
689
946
|
|
690
|
-
AASM also supports persistence in Redis
|
691
|
-
|
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.
|
692
951
|
|
693
952
|
```ruby
|
694
953
|
class User
|
@@ -709,7 +968,7 @@ class Job < ActiveRecord::Base
|
|
709
968
|
include AASM
|
710
969
|
|
711
970
|
aasm do
|
712
|
-
state :sleeping, :
|
971
|
+
state :sleeping, initial: true
|
713
972
|
state :running
|
714
973
|
state :cleaning
|
715
974
|
end
|
@@ -738,8 +997,8 @@ defining the `AASM` states, like this:
|
|
738
997
|
class Job < ActiveRecord::Base
|
739
998
|
include AASM
|
740
999
|
|
741
|
-
aasm :
|
742
|
-
state :sleeping, :
|
1000
|
+
aasm create_scopes: false do
|
1001
|
+
state :sleeping, initial: true
|
743
1002
|
state :running
|
744
1003
|
state :cleaning
|
745
1004
|
end
|
@@ -772,11 +1031,11 @@ class Job < ActiveRecord::Base
|
|
772
1031
|
include AASM
|
773
1032
|
|
774
1033
|
aasm do
|
775
|
-
state :sleeping, :
|
1034
|
+
state :sleeping, initial: true
|
776
1035
|
state :running
|
777
1036
|
|
778
|
-
event :run, :
|
779
|
-
transitions :
|
1037
|
+
event :run, after_commit: :notify_about_running_job do
|
1038
|
+
transitions from: :sleeping, to: :running
|
780
1039
|
end
|
781
1040
|
end
|
782
1041
|
|
@@ -798,18 +1057,24 @@ job.run
|
|
798
1057
|
job.save! #notify_about_running_job is not run
|
799
1058
|
```
|
800
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
|
+
|
801
1066
|
If you want to encapsulate state changes within an own transaction, the behavior
|
802
1067
|
of this nested transaction might be confusing. Take a look at
|
803
1068
|
[ActiveRecord Nested Transactions](http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html)
|
804
1069
|
if you want to know more about this. Nevertheless, AASM by default requires a new transaction
|
805
|
-
`transaction(:
|
1070
|
+
`transaction(requires_new: true)`. You can override this behavior by changing
|
806
1071
|
the configuration
|
807
1072
|
|
808
1073
|
```ruby
|
809
1074
|
class Job < ActiveRecord::Base
|
810
1075
|
include AASM
|
811
1076
|
|
812
|
-
aasm :
|
1077
|
+
aasm requires_new_transaction: false do
|
813
1078
|
...
|
814
1079
|
end
|
815
1080
|
|
@@ -817,7 +1082,25 @@ class Job < ActiveRecord::Base
|
|
817
1082
|
end
|
818
1083
|
```
|
819
1084
|
|
820
|
-
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 active record 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
|
+
```
|
821
1104
|
|
822
1105
|
### Pessimistic Locking
|
823
1106
|
|
@@ -834,7 +1117,7 @@ AASM supports [Active Record pessimistic locking via `with_lock`](http://api.rub
|
|
834
1117
|
class Job < ActiveRecord::Base
|
835
1118
|
include AASM
|
836
1119
|
|
837
|
-
aasm :
|
1120
|
+
aasm requires_lock: true do
|
838
1121
|
...
|
839
1122
|
end
|
840
1123
|
|
@@ -846,7 +1129,7 @@ end
|
|
846
1129
|
class Job < ActiveRecord::Base
|
847
1130
|
include AASM
|
848
1131
|
|
849
|
-
aasm :
|
1132
|
+
aasm requires_lock: 'FOR UPDATE NOWAIT' do
|
850
1133
|
...
|
851
1134
|
end
|
852
1135
|
|
@@ -864,15 +1147,21 @@ this by defining your favorite column name, using `:column` like this:
|
|
864
1147
|
class Job < ActiveRecord::Base
|
865
1148
|
include AASM
|
866
1149
|
|
867
|
-
aasm :
|
1150
|
+
aasm column: :my_state do
|
868
1151
|
...
|
869
1152
|
end
|
870
1153
|
|
1154
|
+
aasm :another_state_machine, column: :second_state do
|
1155
|
+
...
|
1156
|
+
end
|
871
1157
|
end
|
872
1158
|
```
|
873
1159
|
|
874
1160
|
Whatever column name is used, make sure to add a migration to provide this column
|
875
|
-
(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.
|
876
1165
|
|
877
1166
|
```ruby
|
878
1167
|
class AddJobState < ActiveRecord::Migration
|
@@ -886,6 +1175,13 @@ class AddJobState < ActiveRecord::Migration
|
|
886
1175
|
end
|
887
1176
|
```
|
888
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
|
+
|
889
1185
|
### Inspection
|
890
1186
|
|
891
1187
|
AASM supports query methods for states and events
|
@@ -897,19 +1193,19 @@ class Job
|
|
897
1193
|
include AASM
|
898
1194
|
|
899
1195
|
aasm do
|
900
|
-
state :sleeping, :
|
1196
|
+
state :sleeping, initial: true
|
901
1197
|
state :running, :cleaning
|
902
1198
|
|
903
1199
|
event :run do
|
904
|
-
transitions :
|
1200
|
+
transitions from: :sleeping, to: :running
|
905
1201
|
end
|
906
1202
|
|
907
1203
|
event :clean do
|
908
|
-
transitions :
|
1204
|
+
transitions from: :running, to: :cleaning, guard: :cleaning_needed?
|
909
1205
|
end
|
910
1206
|
|
911
1207
|
event :sleep do
|
912
|
-
transitions :
|
1208
|
+
transitions from: [:running, :cleaning], to: :sleeping
|
913
1209
|
end
|
914
1210
|
end
|
915
1211
|
|
@@ -921,21 +1217,25 @@ end
|
|
921
1217
|
|
922
1218
|
```ruby
|
923
1219
|
# show all states
|
924
|
-
Job.aasm.states.map(&:name)
|
1220
|
+
Job.aasm.states.map(&:name)
|
925
1221
|
#=> [:sleeping, :running, :cleaning]
|
926
1222
|
|
927
1223
|
job = Job.new
|
928
1224
|
|
929
1225
|
# show all permitted states (from initial state)
|
930
|
-
job.aasm.states(:
|
1226
|
+
job.aasm.states(permitted: true).map(&:name)
|
931
1227
|
#=> [:running]
|
932
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
|
+
|
933
1233
|
job.run
|
934
|
-
job.aasm.states(:
|
1234
|
+
job.aasm.states(permitted: true).map(&:name)
|
935
1235
|
#=> [:sleeping]
|
936
1236
|
|
937
1237
|
# show all non permitted states
|
938
|
-
job.aasm.states(:
|
1238
|
+
job.aasm.states(permitted: false).map(&:name)
|
939
1239
|
#=> [:cleaning]
|
940
1240
|
|
941
1241
|
# show all possible (triggerable) events from the current state
|
@@ -943,23 +1243,23 @@ job.aasm.events.map(&:name)
|
|
943
1243
|
#=> [:clean, :sleep]
|
944
1244
|
|
945
1245
|
# show all permitted events
|
946
|
-
job.aasm.events(:
|
1246
|
+
job.aasm.events(permitted: true).map(&:name)
|
947
1247
|
#=> [:sleep]
|
948
1248
|
|
949
1249
|
# show all non permitted events
|
950
|
-
job.aasm.events(:
|
1250
|
+
job.aasm.events(permitted: false).map(&:name)
|
951
1251
|
#=> [:clean]
|
952
1252
|
|
953
1253
|
# show all possible events except a specific one
|
954
|
-
job.aasm.events(:
|
1254
|
+
job.aasm.events(reject: :sleep).map(&:name)
|
955
1255
|
#=> [:clean]
|
956
1256
|
|
957
1257
|
# list states for select
|
958
1258
|
Job.aasm.states_for_select
|
959
|
-
|
1259
|
+
#=> [["Sleeping", "sleeping"], ["Running", "running"], ["Cleaning", "cleaning"]]
|
960
1260
|
|
961
1261
|
# show permitted states with guard parameter
|
962
|
-
job.aasm.states({:
|
1262
|
+
job.aasm.states({permitted: true}, guard_parameter).map(&:name)
|
963
1263
|
```
|
964
1264
|
|
965
1265
|
|
@@ -972,13 +1272,13 @@ use
|
|
972
1272
|
class Job
|
973
1273
|
include AASM
|
974
1274
|
|
975
|
-
aasm :
|
1275
|
+
aasm logger: Rails.logger do
|
976
1276
|
...
|
977
1277
|
end
|
978
1278
|
end
|
979
1279
|
```
|
980
1280
|
|
981
|
-
|
1281
|
+
You can hide warnings by setting `AASM::Configuration.hide_warnings = true`
|
982
1282
|
|
983
1283
|
### RubyMotion support
|
984
1284
|
|
@@ -994,7 +1294,17 @@ the 'instance method symbol / string' way whenever possible when defining guardi
|
|
994
1294
|
|
995
1295
|
### Testing
|
996
1296
|
|
997
|
-
|
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:
|
998
1308
|
|
999
1309
|
```ruby
|
1000
1310
|
# classes with only the default state machine
|
@@ -1007,7 +1317,7 @@ expect(job).to allow_event :run
|
|
1007
1317
|
expect(job).to_not allow_event :clean
|
1008
1318
|
expect(job).to allow_transition_to(:running)
|
1009
1319
|
expect(job).to_not allow_transition_to(:cleaning)
|
1010
|
-
# on_event also accept arguments
|
1320
|
+
# on_event also accept multiple arguments
|
1011
1321
|
expect(job).to transition_from(:sleeping).to(:running).on_event(:run, :defragmentation)
|
1012
1322
|
|
1013
1323
|
# classes with multiple state machine
|
@@ -1028,6 +1338,96 @@ expect(multiple).to allow_event(:start).on(:move)
|
|
1028
1338
|
expect(multiple).to_not allow_event(:stop).on(:move)
|
1029
1339
|
expect(multiple).to allow_transition_to(:processing).on(:move)
|
1030
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
|
1031
1431
|
```
|
1032
1432
|
|
1033
1433
|
## <a id="installation">Installation ##
|
@@ -1063,6 +1463,14 @@ Replace NAME with the Model name, COLUMN_NAME is optional(default is 'aasm_state
|
|
1063
1463
|
This will create a model (if one does not exist) and configure it with aasm block.
|
1064
1464
|
For Active record orm a migration file is added to add aasm state column to table.
|
1065
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
|
+
|
1066
1474
|
## Latest changes ##
|
1067
1475
|
|
1068
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.
|
@@ -1083,14 +1491,7 @@ Feel free to
|
|
1083
1491
|
* [Anil Maurya](http://github.com/anilmaurya) (since 2016)
|
1084
1492
|
|
1085
1493
|
|
1086
|
-
## Contributing
|
1087
|
-
|
1088
|
-
1. Read the [Contributor Code of Conduct](https://github.com/aasm/aasm/blob/master/CODE_OF_CONDUCT.md)
|
1089
|
-
2. Fork it
|
1090
|
-
3. Create your feature branch (git checkout -b my-new-feature)
|
1091
|
-
4. Commit your changes (git commit -am 'Added some feature')
|
1092
|
-
5. Push to the branch (git push origin my-new-feature)
|
1093
|
-
6. Create new Pull Request
|
1494
|
+
## [Contributing](CONTRIBUTING.md)
|
1094
1495
|
|
1095
1496
|
## Warranty ##
|
1096
1497
|
|
@@ -1101,7 +1502,7 @@ purpose.
|
|
1101
1502
|
|
1102
1503
|
## License ##
|
1103
1504
|
|
1104
|
-
Copyright (c) 2006-
|
1505
|
+
Copyright (c) 2006-2017 Scott Barron
|
1105
1506
|
|
1106
1507
|
Permission is hereby granted, free of charge, to any person obtaining
|
1107
1508
|
a copy of this software and associated documentation files (the
|