micro-max-box 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/aasm-6.0.0/CHANGELOG.md +497 -0
  3. data/aasm-6.0.0/LICENSE +20 -0
  4. data/aasm-6.0.0/README.md +1534 -0
  5. data/aasm-6.0.0/lib/aasm/aasm.rb +208 -0
  6. data/aasm-6.0.0/lib/aasm/base.rb +300 -0
  7. data/aasm-6.0.0/lib/aasm/configuration.rb +48 -0
  8. data/aasm-6.0.0/lib/aasm/core/event.rb +178 -0
  9. data/aasm-6.0.0/lib/aasm/core/invoker.rb +129 -0
  10. data/aasm-6.0.0/lib/aasm/core/invokers/base_invoker.rb +85 -0
  11. data/aasm-6.0.0/lib/aasm/core/invokers/class_invoker.rb +75 -0
  12. data/aasm-6.0.0/lib/aasm/core/invokers/literal_invoker.rb +90 -0
  13. data/aasm-6.0.0/lib/aasm/core/invokers/proc_invoker.rb +94 -0
  14. data/aasm-6.0.0/lib/aasm/core/state.rb +91 -0
  15. data/aasm-6.0.0/lib/aasm/core/transition.rb +83 -0
  16. data/aasm-6.0.0/lib/aasm/dsl_helper.rb +32 -0
  17. data/aasm-6.0.0/lib/aasm/errors.rb +22 -0
  18. data/aasm-6.0.0/lib/aasm/instance_base.rb +153 -0
  19. data/aasm-6.0.0/lib/aasm/localizer.rb +64 -0
  20. data/aasm-6.0.0/lib/aasm/minitest/allow_event.rb +13 -0
  21. data/aasm-6.0.0/lib/aasm/minitest/allow_transition_to.rb +13 -0
  22. data/aasm-6.0.0/lib/aasm/minitest/have_state.rb +13 -0
  23. data/aasm-6.0.0/lib/aasm/minitest/transition_from.rb +21 -0
  24. data/aasm-6.0.0/lib/aasm/minitest.rb +5 -0
  25. data/aasm-6.0.0/lib/aasm/minitest_spec.rb +15 -0
  26. data/aasm-6.0.0/lib/aasm/persistence/active_record_persistence.rb +174 -0
  27. data/aasm-6.0.0/lib/aasm/persistence/base.rb +89 -0
  28. data/aasm-6.0.0/lib/aasm/persistence/core_data_query_persistence.rb +94 -0
  29. data/aasm-6.0.0/lib/aasm/persistence/dynamoid_persistence.rb +92 -0
  30. data/aasm-6.0.0/lib/aasm/persistence/mongoid_persistence.rb +126 -0
  31. data/aasm-6.0.0/lib/aasm/persistence/no_brainer_persistence.rb +105 -0
  32. data/aasm-6.0.0/lib/aasm/persistence/orm.rb +154 -0
  33. data/aasm-6.0.0/lib/aasm/persistence/plain_persistence.rb +26 -0
  34. data/aasm-6.0.0/lib/aasm/persistence/redis_persistence.rb +112 -0
  35. data/aasm-6.0.0/lib/aasm/persistence/sequel_persistence.rb +83 -0
  36. data/aasm-6.0.0/lib/aasm/persistence.rb +54 -0
  37. data/aasm-6.0.0/lib/aasm/rspec/allow_event.rb +26 -0
  38. data/aasm-6.0.0/lib/aasm/rspec/allow_transition_to.rb +26 -0
  39. data/aasm-6.0.0/lib/aasm/rspec/have_state.rb +22 -0
  40. data/aasm-6.0.0/lib/aasm/rspec/transition_from.rb +36 -0
  41. data/aasm-6.0.0/lib/aasm/rspec.rb +5 -0
  42. data/aasm-6.0.0/lib/aasm/state_machine.rb +53 -0
  43. data/aasm-6.0.0/lib/aasm/state_machine_store.rb +77 -0
  44. data/aasm-6.0.0/lib/aasm/version.rb +3 -0
  45. data/aasm-6.0.0/lib/aasm.rb +21 -0
  46. data/aasm-6.0.0/lib/generators/aasm/aasm_generator.rb +16 -0
  47. data/aasm-6.0.0/lib/generators/aasm/orm_helpers.rb +41 -0
  48. data/aasm-6.0.0/lib/generators/active_record/aasm_generator.rb +40 -0
  49. data/aasm-6.0.0/lib/generators/active_record/templates/migration.rb +8 -0
  50. data/aasm-6.0.0/lib/generators/active_record/templates/migration_existing.rb +5 -0
  51. data/aasm-6.0.0/lib/generators/mongoid/aasm_generator.rb +28 -0
  52. data/aasm-6.0.0/lib/generators/nobrainer/aasm_generator.rb +28 -0
  53. data/aasm-6.0.0/lib/motion-aasm.rb +37 -0
  54. data/micro-max-box.gemspec +12 -0
  55. metadata +94 -0
@@ -0,0 +1,1534 @@
1
+ # AASM - Ruby state machines
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/aasm.svg)](http://badge.fury.io/rb/aasm)
4
+ [![Build Status](https://github.com/aasm/aasm/actions/workflows/build.yml/badge.svg)](https://github.com/aasm/aasm/actions/workflows/build.yml)
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 5 to 6](#upgrade-from-version-5-to-6)
10
+ - [Upgrade from version 3 to 4](#upgrade-from-version-3-to-4)
11
+ - [Usage](#usage)
12
+ - [Callbacks](#callbacks)
13
+ - [Lifecycle](#lifecycle)
14
+ - [The current event triggered](#the-current-event-triggered)
15
+ - [Guards](#guards)
16
+ - [Transitions](#transitions)
17
+ - [Multiple state machines per class](#multiple-state-machines-per-class)
18
+ - [Handling naming conflicts between multiple state machines](#handling-naming-conflicts-between-multiple-state-machines)
19
+ - [Binding event](#binding-event)
20
+ - [Auto-generated Status Constants](#auto-generated-status-constants)
21
+ - [Extending AASM](#extending-aasm)
22
+ - [ActiveRecord](#activerecord)
23
+ - [Bang events](#bang-events)
24
+ - [Timestamps](#timestamps)
25
+ - [ActiveRecord enums](#activerecord-enums)
26
+ - [Sequel](#sequel)
27
+ - [Dynamoid](#dynamoid)
28
+ - [Mongoid](#mongoid)
29
+ - [Nobrainer](#nobrainer)
30
+ - [Redis](#redis)
31
+ - [Automatic Scopes](#automatic-scopes)
32
+ - [Transaction support](#transaction-support)
33
+ - [Pessimistic Locking](#pessimistic-locking)
34
+ - [Column name & migration](#column-name--migration)
35
+ - [Log State Changes](#log-state-changes)
36
+ - [Inspection](#inspection)
37
+ - [Warning output](#warning-output)
38
+ - [RubyMotion support](#rubymotion-support)
39
+ - [Testing](#testing)
40
+ - [RSpec](#rspec)
41
+ - [Minitest](#minitest)
42
+ - [Assertions](#assertions)
43
+ - [Expectations](#expectations)
44
+ - [Installation](#installation)
45
+ - [Manually from RubyGems.org](#manually-from-rubygemsorg)
46
+ - [Bundler](#or-if-you-are-using-bundler)
47
+ - [Building your own gems](#building-your-own-gems)
48
+ - [Generators](#generators)
49
+ - [Test suite with Docker](#docker)
50
+ - [Latest changes](#latest-changes)
51
+ - [Questions?](#questions)
52
+ - [Maintainers](#maintainers)
53
+ - [Contributing](CONTRIBUTING.md)
54
+ - [Warranty](#warranty)
55
+ - [License](#license)
56
+
57
+ This package contains AASM, a library for adding finite state machines to Ruby classes.
58
+
59
+ AASM started as the *acts_as_state_machine* plugin but has evolved into a more generic library
60
+ that no longer targets only ActiveRecord models. It currently provides adapters for many
61
+ ORMs but it can be used for any Ruby class, no matter what parent class it has (if any).
62
+
63
+ ## Upgrade from version 5 to 6
64
+
65
+ Take a look at the [README_FROM_VERSION_5_TO_6](https://github.com/aasm/aasm/blob/master/README_FROM_VERSION_5_TO_6.md) for details how to switch from version 5.x to 6.0 of _AASM_.
66
+
67
+ ## Upgrade from version 3 to 4
68
+
69
+ Take a look at the [README_FROM_VERSION_3_TO_4](https://github.com/aasm/aasm/blob/master/README_FROM_VERSION_3_TO_4.md) for details how to switch from version 3.x to 4.0 of _AASM_.
70
+
71
+ ## Usage
72
+
73
+ Adding a state machine is as simple as including the AASM module and start defining
74
+ **states** and **events** together with their **transitions**:
75
+
76
+ ```ruby
77
+ class Job
78
+ include AASM
79
+
80
+ aasm do
81
+ state :sleeping, initial: true
82
+ state :running, :cleaning
83
+
84
+ event :run do
85
+ transitions from: :sleeping, to: :running
86
+ end
87
+
88
+ event :clean do
89
+ transitions from: :running, to: :cleaning
90
+ end
91
+
92
+ event :sleep do
93
+ transitions from: [:running, :cleaning], to: :sleeping
94
+ end
95
+ end
96
+
97
+ end
98
+ ```
99
+
100
+ This provides you with a couple of public methods for instances of the class `Job`:
101
+
102
+ ```ruby
103
+ job = Job.new
104
+ job.sleeping? # => true
105
+ job.may_run? # => true
106
+ job.run
107
+ job.running? # => true
108
+ job.sleeping? # => false
109
+ job.may_run? # => false
110
+ job.run # => raises AASM::InvalidTransition
111
+ ```
112
+
113
+ If you don't like exceptions and prefer a simple `true` or `false` as response, tell
114
+ AASM not to be *whiny*:
115
+
116
+ ```ruby
117
+ class Job
118
+ ...
119
+ aasm whiny_transitions: false do
120
+ ...
121
+ end
122
+ end
123
+
124
+ job.running? # => true
125
+ job.may_run? # => false
126
+ job.run # => false
127
+ ```
128
+
129
+ When firing an event, you can pass a block to the method, it will be called only if
130
+ the transition succeeds :
131
+
132
+ ```ruby
133
+ job.run do
134
+ job.user.notify_job_ran # Will be called if job.may_run? is true
135
+ end
136
+ ```
137
+
138
+ ### Callbacks
139
+
140
+ You can define a number of callbacks for your events, transitions and states. These methods, Procs or classes will be
141
+ called when certain criteria are met, like entering a particular state:
142
+
143
+ ```ruby
144
+ class Job
145
+ include AASM
146
+
147
+ aasm do
148
+ state :sleeping, initial: true, before_enter: :do_something
149
+ state :running, before_enter: Proc.new { do_something && notify_somebody }
150
+ state :finished
151
+
152
+ after_all_transitions :log_status_change
153
+
154
+ event :run, after: :notify_somebody do
155
+ before do
156
+ log('Preparing to run')
157
+ end
158
+
159
+ transitions from: :sleeping, to: :running, after: Proc.new {|*args| set_process(*args) }
160
+ transitions from: :running, to: :finished, after: LogRunTime
161
+ end
162
+
163
+ event :sleep do
164
+ after do
165
+ ...
166
+ end
167
+ error do |e|
168
+ ...
169
+ end
170
+ transitions from: :running, to: :sleeping
171
+ end
172
+ end
173
+
174
+ def log_status_change
175
+ puts "changing from #{aasm.from_state} to #{aasm.to_state} (event: #{aasm.current_event})"
176
+ end
177
+
178
+ def set_process(name)
179
+ ...
180
+ end
181
+
182
+ def do_something
183
+ ...
184
+ end
185
+
186
+ def notify_somebody
187
+ ...
188
+ end
189
+
190
+ end
191
+
192
+ class LogRunTime
193
+ def call
194
+ log "Job was running for X seconds"
195
+ end
196
+ end
197
+ ```
198
+
199
+ In this case `do_something` is called before actually entering the state `sleeping`,
200
+ while `notify_somebody` is called after the transition `run` (from `sleeping` to `running`)
201
+ is finished.
202
+
203
+ 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:
204
+
205
+ 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.
206
+
207
+ ```ruby
208
+ class LogRunTime
209
+ # optional args parameter can be omitted, but if you define initialize
210
+ # you must accept the model instance as the first parameter to it.
211
+ def initialize(job, args = {})
212
+ @job = job
213
+ end
214
+
215
+ def call
216
+ log "Job was running for #{@job.run_time} seconds"
217
+ end
218
+ end
219
+ ```
220
+
221
+ #### Parameters
222
+ You can pass parameters to events:
223
+
224
+ ```ruby
225
+ job = Job.new
226
+ job.run(:defragmentation)
227
+ ```
228
+
229
+ All guards and after callbacks will receive these parameters. In this case `set_process` would be called with
230
+ `:defragmentation` argument.
231
+
232
+ If the first argument to the event is a state (e.g. `:running` or `:finished`), the first argument is consumed and
233
+ the state machine will attempt to transition to that state. Add comma separated parameter for guards and callbacks
234
+
235
+ ```ruby
236
+ job = Job.new
237
+ job.run(:running, :defragmentation)
238
+ ```
239
+ In this case `set_process` won't be called, job will transition to running state and callback will receive
240
+ `:defragmentation` as parameter
241
+
242
+ #### Error Handling
243
+ In case of an error during the event processing the error is rescued and passed to `:error`
244
+ callback, which can handle it or re-raise it for further propagation.
245
+
246
+ Also, you can define a method that will be called if any event fails:
247
+
248
+ ```ruby
249
+ def aasm_event_failed(event_name, old_state_name)
250
+ # use custom exception/messages, report metrics, etc
251
+ end
252
+ ```
253
+
254
+ During the transition's `:after` callback (and reliably only then, or in the global
255
+ `after_all_transitions` callback) you can access the originating state (the from-state)
256
+ and the target state (the to state), like this:
257
+
258
+ ```ruby
259
+ def set_process(name)
260
+ logger.info "from #{aasm.from_state} to #{aasm.to_state}"
261
+ end
262
+ ```
263
+
264
+ #### Lifecycle
265
+
266
+ Here you can see a list of all possible callbacks, together with their order of calling:
267
+
268
+ ```ruby
269
+ begin
270
+ event before_all_events
271
+ event before
272
+ event guards
273
+ transition guards
274
+ old_state before_exit
275
+ old_state exit
276
+ after_all_transitions
277
+ transition after
278
+ new_state before_enter
279
+ new_state enter
280
+ ...update state...
281
+ event before_success # if persist successful
282
+ transition success # if persist successful, database update not guaranteed
283
+ event success # if persist successful, database update not guaranteed
284
+ old_state after_exit
285
+ new_state after_enter
286
+ event after
287
+ event after_all_events
288
+ rescue
289
+ event error
290
+ event error_on_all_events
291
+ ensure
292
+ event ensure
293
+ event ensure_on_all_events
294
+ end
295
+ ```
296
+
297
+ Use event's `after_commit` callback if it should be fired after database update.
298
+
299
+ #### The current event triggered
300
+
301
+ While running the callbacks you can easily retrieve the name of the event triggered
302
+ by using `aasm.current_event`:
303
+
304
+ ```ruby
305
+ # taken the example callback from above
306
+ def do_something
307
+ puts "triggered #{aasm.current_event}"
308
+ end
309
+ ```
310
+
311
+ and then
312
+
313
+ ```ruby
314
+ job = Job.new
315
+
316
+ # without bang
317
+ job.sleep # => triggered :sleep
318
+
319
+ # with bang
320
+ job.sleep! # => triggered :sleep!
321
+ ```
322
+
323
+
324
+ ### Guards
325
+
326
+ Let's assume you want to allow particular transitions only if a defined condition is
327
+ given. For this you can set up a guard per transition, which will run before actually
328
+ running the transition. If the guard returns `false` the transition will be
329
+ denied (raising `AASM::InvalidTransition`):
330
+
331
+ ```ruby
332
+ class Cleaner
333
+ include AASM
334
+
335
+ aasm do
336
+ state :idle, initial: true
337
+ state :cleaning
338
+
339
+ event :clean do
340
+ transitions from: :idle, to: :cleaning, guard: :cleaning_needed?
341
+ end
342
+
343
+ event :clean_if_needed do
344
+ transitions from: :idle, to: :cleaning do
345
+ guard do
346
+ cleaning_needed?
347
+ end
348
+ end
349
+ transitions from: :idle, to: :idle
350
+ end
351
+
352
+ event :clean_if_dirty do
353
+ transitions from: :idle, to: :cleaning, guard: :if_dirty?
354
+ end
355
+ end
356
+
357
+ def cleaning_needed?
358
+ false
359
+ end
360
+
361
+ def if_dirty?(status)
362
+ status == :dirty
363
+ end
364
+ end
365
+
366
+ job = Cleaner.new
367
+ job.may_clean? # => false
368
+ job.clean # => raises AASM::InvalidTransition
369
+ job.may_clean_if_needed? # => true
370
+ job.clean_if_needed! # idle
371
+
372
+ job.clean_if_dirty(:clean) # => raises AASM::InvalidTransition
373
+ job.clean_if_dirty(:dirty) # => true
374
+ ```
375
+
376
+ You can even provide a number of guards, which all have to succeed to proceed
377
+
378
+ ```ruby
379
+ def walked_the_dog?; ...; end
380
+
381
+ event :sleep do
382
+ transitions from: :running, to: :sleeping, guards: [:cleaning_needed?, :walked_the_dog?]
383
+ end
384
+ ```
385
+
386
+ If you want to provide guards for all transitions within an event, you can use event guards
387
+
388
+ ```ruby
389
+ event :sleep, guards: [:walked_the_dog?] do
390
+ transitions from: :running, to: :sleeping, guards: [:cleaning_needed?]
391
+ transitions from: :cleaning, to: :sleeping
392
+ end
393
+ ```
394
+
395
+ If you prefer a more Ruby-like guard syntax, you can use `if` and `unless` as well:
396
+
397
+ ```ruby
398
+ event :clean do
399
+ transitions from: :running, to: :cleaning, if: :cleaning_needed?
400
+ end
401
+
402
+ event :sleep do
403
+ transitions from: :running, to: :sleeping, unless: :cleaning_needed?
404
+ end
405
+ end
406
+ ```
407
+
408
+ You can invoke a Class instead of a method if the Class responds to `call`
409
+
410
+ ```ruby
411
+ event :sleep do
412
+ transitions from: :running, to: :sleeping, guards: Dog
413
+ end
414
+ ```
415
+ ```ruby
416
+ class Dog
417
+ def call
418
+ cleaning_needed? && walked?
419
+ end
420
+ ...
421
+ end
422
+ ```
423
+
424
+ ### Transitions
425
+
426
+ In the event of having multiple transitions for an event, the first transition that successfully completes will stop other transitions in the same event from being processed.
427
+
428
+ ```ruby
429
+ require 'aasm'
430
+
431
+ class Job
432
+ include AASM
433
+
434
+ aasm do
435
+ state :stage1, initial: true
436
+ state :stage2
437
+ state :stage3
438
+ state :completed
439
+
440
+ event :stage1_completed do
441
+ transitions from: :stage1, to: :stage3, guard: :stage2_completed?
442
+ transitions from: :stage1, to: :stage2
443
+ end
444
+ end
445
+
446
+ def stage2_completed?
447
+ true
448
+ end
449
+ end
450
+
451
+ job = Job.new
452
+ job.stage1_completed
453
+ job.aasm.current_state # stage3
454
+ ```
455
+
456
+ You can define transition from any defined state by omitting `from`:
457
+
458
+ ```ruby
459
+ event :abort do
460
+ transitions to: :aborted
461
+ end
462
+ ```
463
+
464
+ ### Display name for state
465
+
466
+ You can define display name for state using :display option
467
+
468
+ ```ruby
469
+ class Job
470
+ include AASM
471
+
472
+ aasm do
473
+ state :stage1, initial: true, display: 'First Stage'
474
+ state :stage2
475
+ state :stage3
476
+ end
477
+ end
478
+
479
+ job = Job.new
480
+ job.aasm.human_state
481
+
482
+ ```
483
+
484
+ ### Multiple state machines per class
485
+
486
+ Multiple state machines per class are supported. Be aware though that _AASM_ has been
487
+ 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.
488
+
489
+ ```ruby
490
+ class SimpleMultipleExample
491
+ include AASM
492
+ aasm(:move, column: 'move_state') do
493
+ state :standing, initial: true
494
+ state :walking
495
+ state :running
496
+
497
+ event :walk do
498
+ transitions from: :standing, to: :walking
499
+ end
500
+ event :run do
501
+ transitions from: [:standing, :walking], to: :running
502
+ end
503
+ event :hold do
504
+ transitions from: [:walking, :running], to: :standing
505
+ end
506
+ end
507
+
508
+ aasm(:work, column: 'work_state') do
509
+ state :sleeping, initial: true
510
+ state :processing
511
+
512
+ event :start do
513
+ transitions from: :sleeping, to: :processing
514
+ end
515
+ event :stop do
516
+ transitions from: :processing, to: :sleeping
517
+ end
518
+ end
519
+ end
520
+
521
+ simple = SimpleMultipleExample.new
522
+
523
+ simple.aasm(:move).current_state
524
+ # => :standing
525
+ simple.aasm(:work).current_state
526
+ # => :sleeping
527
+
528
+ simple.start
529
+ simple.aasm(:move).current_state
530
+ # => :standing
531
+ simple.aasm(:work).current_state
532
+ # => :processing
533
+
534
+ ```
535
+
536
+ #### Handling naming conflicts between multiple state machines
537
+
538
+ _AASM_ doesn't prohibit to define the same event in more than one state
539
+ machine. If no namespace is provided, the latest definition "wins" and
540
+ overrides previous definitions. Nonetheless, a warning is issued:
541
+ `SimpleMultipleExample: overriding method 'run'!`.
542
+
543
+ Alternatively, you can provide a namespace for each state machine. When a namespace
544
+ is used, event methods are defined with the namespace as a suffix, preventing
545
+ collisions between state machines that share event names:
546
+
547
+ ```ruby
548
+ class NamespacedMultipleExample
549
+ include AASM
550
+ aasm(:status) do
551
+ state :unapproved, initial: true
552
+ state :approved
553
+
554
+ event :approve do
555
+ transitions from: :unapproved, to: :approved
556
+ end
557
+
558
+ event :unapprove do
559
+ transitions from: :approved, to: :unapproved
560
+ end
561
+ end
562
+
563
+ aasm(:review_status, namespace: :review) do
564
+ state :unapproved, initial: true
565
+ state :approved
566
+
567
+ event :approve do
568
+ transitions from: :unapproved, to: :approved
569
+ end
570
+
571
+ event :unapprove do
572
+ transitions from: :approved, to: :unapproved
573
+ end
574
+ end
575
+ end
576
+
577
+ namespaced = NamespacedMultipleExample.new
578
+
579
+ namespaced.aasm(:status).current_state
580
+ # => :unapproved
581
+ namespaced.aasm(:review_status).current_state
582
+ # => :unapproved
583
+ namespaced.approve_review
584
+ namespaced.aasm(:review_status).current_state
585
+ # => :approved
586
+ ```
587
+
588
+ All _AASM_ class- and instance-level `aasm` methods accept a state machine selector.
589
+ So, for example, to use inspection on a class level, you have to use
590
+
591
+ ```ruby
592
+ SimpleMultipleExample.aasm(:move).states.map(&:name)
593
+ # => [:standing, :walking, :running]
594
+ ```
595
+
596
+ ### Binding event
597
+
598
+ Allow an event to be bound to another
599
+ ```ruby
600
+ class Example
601
+ include AASM
602
+
603
+ aasm(:work) do
604
+ state :sleeping, initial: true
605
+ state :processing
606
+
607
+ event :start do
608
+ transitions from: :sleeping, to: :processing
609
+ end
610
+ event :stop do
611
+ transitions from: :processing, to: :sleeping
612
+ end
613
+ end
614
+
615
+ aasm(:question) do
616
+ state :answered, initial: true
617
+ state :asked
618
+
619
+ event :ask, binding_event: :start do
620
+ transitions from: :answered, to: :asked
621
+ end
622
+ event :answer, binding_event: :stop do
623
+ transitions from: :asked, to: :answered
624
+ end
625
+ end
626
+ end
627
+
628
+ example = Example.new
629
+ example.aasm(:work).current_state #=> :sleeping
630
+ example.aasm(:question).current_state #=> :answered
631
+ example.ask
632
+ example.aasm(:work).current_state #=> :processing
633
+ example.aasm(:question).current_state #=> :asked
634
+ ```
635
+
636
+ ### Auto-generated Status Constants
637
+
638
+ AASM automatically [generates constants](https://github.com/aasm/aasm/pull/60)
639
+ for each status so you don't have to explicitly define them.
640
+
641
+ ```ruby
642
+ class Foo
643
+ include AASM
644
+
645
+ aasm do
646
+ state :initialized
647
+ state :calculated
648
+ state :finalized
649
+ end
650
+ end
651
+
652
+ > Foo::STATE_INITIALIZED
653
+ #=> :initialized
654
+ > Foo::STATE_CALCULATED
655
+ #=> :calculated
656
+ ```
657
+
658
+ ### Extending AASM
659
+
660
+ AASM allows you to easily extend `AASM::Base` for your own application purposes.
661
+
662
+ Let's suppose we have common logic across many AASM models. We can embody this logic in a sub-class of `AASM::Base`.
663
+
664
+ ```ruby
665
+ class CustomAASMBase < AASM::Base
666
+ # A custom transition that we want available across many AASM models.
667
+ def count_transitions!
668
+ klass.class_eval do
669
+ aasm with_klass: CustomAASMBase do
670
+ after_all_transitions :increment_transition_count
671
+ end
672
+ end
673
+ end
674
+
675
+ # A custom annotation that we want available across many AASM models.
676
+ def requires_guards!
677
+ klass.class_eval do
678
+ attr_reader :authorizable_called,
679
+ :transition_count,
680
+ :fillable_called
681
+
682
+ def authorizable?
683
+ @authorizable_called = true
684
+ end
685
+
686
+ def fillable?
687
+ @fillable_called = true
688
+ end
689
+
690
+ def increment_transition_count
691
+ @transition_count ||= 0
692
+ @transition_count += 1
693
+ end
694
+ end
695
+ end
696
+ end
697
+ ```
698
+
699
+ 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.
700
+
701
+ ```ruby
702
+ class SimpleCustomExample
703
+ include AASM
704
+
705
+ # Let's build an AASM state machine with our custom class.
706
+ aasm with_klass: CustomAASMBase do
707
+ requires_guards!
708
+ count_transitions!
709
+
710
+ state :initialised, initial: true
711
+ state :filled_out
712
+ state :authorised
713
+
714
+ event :fill_out do
715
+ transitions from: :initialised, to: :filled_out, guard: :fillable?
716
+ end
717
+ event :authorise do
718
+ transitions from: :filled_out, to: :authorised, guard: :authorizable?
719
+ end
720
+ end
721
+ end
722
+ ```
723
+
724
+
725
+ ### ActiveRecord
726
+
727
+ AASM comes with support for ActiveRecord and allows automatic persisting of the object's
728
+ state in the database.
729
+
730
+ Add `gem 'after_commit_everywhere', '~> 1.0'` to your Gemfile.
731
+
732
+ ```ruby
733
+ class Job < ActiveRecord::Base
734
+ include AASM
735
+
736
+ aasm do # default column: aasm_state
737
+ state :sleeping, initial: true
738
+ state :running
739
+
740
+ event :run do
741
+ transitions from: :sleeping, to: :running
742
+ end
743
+
744
+ event :sleep do
745
+ transitions from: :running, to: :sleeping
746
+ end
747
+ end
748
+
749
+ end
750
+ ```
751
+
752
+ ### Bang events
753
+
754
+ You can tell AASM to auto-save the object or leave it unsaved
755
+
756
+ ```ruby
757
+ job = Job.new
758
+ job.run # not saved
759
+ job.run! # saved
760
+
761
+ # or
762
+ job.aasm.fire(:run) # not saved
763
+ job.aasm.fire!(:run) # saved
764
+ ```
765
+
766
+ Saving includes running all validations on the `Job` class. If
767
+ `whiny_persistence` flag is set to `true`, exception is raised in case of
768
+ failure. If `whiny_persistence` flag is set to `false`, methods with a bang return
769
+ `true` if the state transition is successful or `false` if an error occurs.
770
+
771
+ If you want make sure the state gets saved without running validations (and
772
+ thereby maybe persisting an invalid object state), simply tell AASM to skip the
773
+ validations. Be aware that when skipping validations, only the state column will
774
+ be updated in the database (just like ActiveRecord `update_column` is working).
775
+
776
+ ```ruby
777
+ class Job < ActiveRecord::Base
778
+ include AASM
779
+
780
+ aasm skip_validation_on_save: true do
781
+ state :sleeping, initial: true
782
+ state :running
783
+
784
+ event :run do
785
+ transitions from: :sleeping, to: :running
786
+ end
787
+
788
+ event :sleep do
789
+ transitions from: :running, to: :sleeping
790
+ end
791
+ end
792
+
793
+ end
794
+ ```
795
+
796
+ Also, you can skip the validation at instance level with `some_event_name_without_validation!` method.
797
+ With this you have the flexibility of having validation for all your transitions by default and then skip it wherever required.
798
+ Please note that only state column will be updated as mentioned in the above example.
799
+
800
+ ```ruby
801
+ job.run_without_validation!
802
+ ```
803
+
804
+ If you want to make sure that the _AASM_ column for storing the state is not directly assigned,
805
+ configure _AASM_ to not allow direct assignment, like this:
806
+
807
+ ```ruby
808
+ class Job < ActiveRecord::Base
809
+ include AASM
810
+
811
+ aasm no_direct_assignment: true do
812
+ state :sleeping, initial: true
813
+ state :running
814
+
815
+ event :run do
816
+ transitions from: :sleeping, to: :running
817
+ end
818
+ end
819
+
820
+ end
821
+ ```
822
+
823
+ resulting in this:
824
+
825
+ ```ruby
826
+ job = Job.create
827
+ job.aasm_state # => 'sleeping'
828
+ job.aasm_state = :running # => raises AASM::NoDirectAssignmentError
829
+ job.aasm_state # => 'sleeping'
830
+ ```
831
+
832
+ ### Timestamps
833
+
834
+ You can tell _AASM_ to try to write a timestamp whenever a new state is entered.
835
+ If `timestamps: true` is set, _AASM_ will look for a field named like the new state plus `_at` and try to fill it:
836
+
837
+ ```ruby
838
+ class Job < ActiveRecord::Base
839
+ include AASM
840
+
841
+ aasm timestamps: true do
842
+ state :sleeping, initial: true
843
+ state :running
844
+
845
+ event :run do
846
+ transitions from: :sleeping, to: :running
847
+ end
848
+ end
849
+ end
850
+ ```
851
+
852
+ resulting in this:
853
+
854
+ ```ruby
855
+ job = Job.create
856
+ job.running_at # => nil
857
+ job.run!
858
+ job.running_at # => 2020-02-20 20:00:00
859
+ ```
860
+
861
+ 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.
862
+
863
+ #### ActiveRecord enums
864
+
865
+ You can use
866
+ [enumerations](http://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html)
867
+ in Rails 4.1+ for your state column:
868
+
869
+ ```ruby
870
+ class Job < ActiveRecord::Base
871
+ include AASM
872
+
873
+ enum state: {
874
+ sleeping: 5,
875
+ running: 99
876
+ }
877
+
878
+ aasm column: :state, enum: true do
879
+ state :sleeping, initial: true
880
+ state :running
881
+ end
882
+ end
883
+ ```
884
+
885
+ You can explicitly pass the name of the method which provides access
886
+ to the enumeration mapping as a value of ```enum```, or you can simply
887
+ set it to ```true```. In the latter case AASM will try to use
888
+ pluralized column name to access possible enum states.
889
+
890
+ Furthermore, if your column has integer type (which is normally the
891
+ case when you're working with Rails enums), you can omit ```:enum```
892
+ setting --- AASM auto-detects this situation and enabled enum
893
+ support. If anything goes wrong, you can disable enum functionality
894
+ and fall back to the default behavior by setting ```:enum```
895
+ to ```false```.
896
+
897
+ ### Sequel
898
+
899
+ AASM also supports [Sequel](http://sequel.jeremyevans.net/) besides _ActiveRecord_, and _Mongoid_.
900
+
901
+ ```ruby
902
+ class Job < Sequel::Model
903
+ include AASM
904
+
905
+ aasm do # default column: aasm_state
906
+ ...
907
+ end
908
+ end
909
+ ```
910
+
911
+ However it's not yet as feature complete as _ActiveRecord_. For example, there are
912
+ scopes defined yet. See [Automatic Scopes](#automatic-scopes).
913
+
914
+ ### Dynamoid
915
+
916
+ Since version `4.8.0` _AASM_ also supports [Dynamoid](http://joshsymonds.com/Dynamoid/) as
917
+ persistence ORM.
918
+
919
+ ### Mongoid
920
+
921
+ AASM also supports persistence to Mongodb if you're using Mongoid. Make sure
922
+ to include Mongoid::Document before you include AASM.
923
+
924
+ ```ruby
925
+ class Job
926
+ include Mongoid::Document
927
+ include AASM
928
+ field :aasm_state
929
+ aasm do
930
+ ...
931
+ end
932
+ end
933
+ ```
934
+
935
+ ### NoBrainer
936
+
937
+ AASM also supports persistence to [RethinkDB](https://www.rethinkdb.com/)
938
+ if you're using [Nobrainer](http://nobrainer.io/).
939
+ Make sure to include NoBrainer::Document before you include AASM.
940
+
941
+ ```ruby
942
+ class Job
943
+ include NoBrainer::Document
944
+ include AASM
945
+ field :aasm_state
946
+ aasm do
947
+ ...
948
+ end
949
+ end
950
+ ```
951
+
952
+ ### Redis
953
+
954
+ AASM also supports persistence in Redis via
955
+ [Redis::Objects](https://github.com/nateware/redis-objects).
956
+ Make sure to include Redis::Objects before you include AASM. Note that non-bang
957
+ events will work as bang events, persisting the changes on every call.
958
+
959
+ ```ruby
960
+ class User
961
+ include Redis::Objects
962
+ include AASM
963
+
964
+ aasm do
965
+ end
966
+ end
967
+ ```
968
+
969
+ ### Automatic Scopes
970
+
971
+ AASM will automatically create scope methods for each state in the model.
972
+
973
+ ```ruby
974
+ class Job < ActiveRecord::Base
975
+ include AASM
976
+
977
+ aasm do
978
+ state :sleeping, initial: true
979
+ state :running
980
+ state :cleaning
981
+ end
982
+
983
+ def self.sleeping
984
+ "This method name is already in use"
985
+ end
986
+ end
987
+ ```
988
+
989
+ ```ruby
990
+ class JobsController < ApplicationController
991
+ def index
992
+ @running_jobs = Job.running
993
+ @recent_cleaning_jobs = Job.cleaning.where('created_at >= ?', 3.days.ago)
994
+
995
+ # @sleeping_jobs = Job.sleeping #=> "This method name is already in use"
996
+ end
997
+ end
998
+ ```
999
+
1000
+ If you don't need scopes (or simply don't want them), disable their creation when
1001
+ defining the `AASM` states, like this:
1002
+
1003
+ ```ruby
1004
+ class Job < ActiveRecord::Base
1005
+ include AASM
1006
+
1007
+ aasm create_scopes: false do
1008
+ state :sleeping, initial: true
1009
+ state :running
1010
+ state :cleaning
1011
+ end
1012
+ end
1013
+ ```
1014
+
1015
+
1016
+ ### Transaction support
1017
+
1018
+ Since version *3.0.13* AASM supports ActiveRecord transactions. So whenever a transition
1019
+ callback or the state update fails, all changes to any database record are rolled back.
1020
+ Mongodb does not support transactions.
1021
+
1022
+ There are currently 3 transactional callbacks that can be handled on the event, and 2 transactional callbacks for all events.
1023
+
1024
+ ```ruby
1025
+ event before_all_transactions
1026
+ event before_transaction
1027
+ event aasm_fire_event (within transaction)
1028
+ event after_commit (if event successful)
1029
+ event after_transaction
1030
+ event after_all_transactions
1031
+ ```
1032
+
1033
+ If you want to make sure a depending action happens only after the transaction is committed,
1034
+ use the `after_commit` callback along with the auto-save (bang) methods, like this:
1035
+
1036
+ ```ruby
1037
+ class Job < ActiveRecord::Base
1038
+ include AASM
1039
+
1040
+ aasm do
1041
+ state :sleeping, initial: true
1042
+ state :running
1043
+
1044
+ event :run, after_commit: :notify_about_running_job do
1045
+ transitions from: :sleeping, to: :running
1046
+ end
1047
+ end
1048
+
1049
+ def notify_about_running_job
1050
+ ...
1051
+ end
1052
+ end
1053
+
1054
+ job = Job.where(state: 'sleeping').first!
1055
+ job.run! # Saves the model and triggers the after_commit callback
1056
+ ```
1057
+
1058
+ Note that the following will not run the `after_commit` callbacks because
1059
+ the auto-save method is not used:
1060
+
1061
+ ```ruby
1062
+ job = Job.where(state: 'sleeping').first!
1063
+ job.run
1064
+ job.save! #notify_about_running_job is not run
1065
+ ```
1066
+
1067
+ Please note that `:after_commit` AASM callbacks behaves around custom implementation
1068
+ of transaction pattern rather than a real-life DB transaction. This fact still causes
1069
+ the race conditions and redundant callback calls within nested transaction. In order
1070
+ to fix that it's highly recommended to add `gem 'after_commit_everywhere', '~> 1.0'`
1071
+ to your `Gemfile`.
1072
+
1073
+ If you want to encapsulate state changes within an own transaction, the behavior
1074
+ of this nested transaction might be confusing. Take a look at
1075
+ [ActiveRecord Nested Transactions](http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html)
1076
+ if you want to know more about this. Nevertheless, AASM by default requires a new transaction
1077
+ `transaction(requires_new: true)`. You can override this behavior by changing
1078
+ the configuration
1079
+
1080
+ ```ruby
1081
+ class Job < ActiveRecord::Base
1082
+ include AASM
1083
+
1084
+ aasm requires_new_transaction: false do
1085
+ ...
1086
+ end
1087
+
1088
+ ...
1089
+ end
1090
+ ```
1091
+
1092
+ which then leads to `transaction(requires_new: false)`, the Rails default.
1093
+
1094
+ Additionally, if you do not want any of your ActiveRecord actions to be
1095
+ wrapped in a transaction, you can specify the `use_transactions` flag. This can
1096
+ be useful if you want want to persist things to the database that happen as a
1097
+ result of a transaction or callback, even when some error occurs. The
1098
+ `use_transactions` flag is true by default.
1099
+
1100
+ ```ruby
1101
+ class Job < ActiveRecord::Base
1102
+ include AASM
1103
+
1104
+ aasm use_transactions: false do
1105
+ ...
1106
+ end
1107
+
1108
+ ...
1109
+ end
1110
+ ```
1111
+
1112
+ ### Pessimistic Locking
1113
+
1114
+ 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.
1115
+
1116
+ | Option | Purpose |
1117
+ | ------ | ------- |
1118
+ | `false` (default) | No lock is obtained | |
1119
+ | `true` | Obtain a blocking pessimistic lock e.g. `FOR UPDATE` |
1120
+ | String | Obtain a lock based on the SQL string e.g. `FOR UPDATE NOWAIT` |
1121
+
1122
+
1123
+ ```ruby
1124
+ class Job < ActiveRecord::Base
1125
+ include AASM
1126
+
1127
+ aasm requires_lock: true do
1128
+ ...
1129
+ end
1130
+
1131
+ ...
1132
+ end
1133
+ ```
1134
+
1135
+ ```ruby
1136
+ class Job < ActiveRecord::Base
1137
+ include AASM
1138
+
1139
+ aasm requires_lock: 'FOR UPDATE NOWAIT' do
1140
+ ...
1141
+ end
1142
+
1143
+ ...
1144
+ end
1145
+ ```
1146
+
1147
+
1148
+ ### Column name & migration
1149
+
1150
+ As a default AASM uses the column `aasm_state` to store the states. You can override
1151
+ this by defining your favorite column name, using `:column` like this:
1152
+
1153
+ ```ruby
1154
+ class Job < ActiveRecord::Base
1155
+ include AASM
1156
+
1157
+ aasm column: :my_state do
1158
+ ...
1159
+ end
1160
+
1161
+ aasm :another_state_machine, column: :second_state do
1162
+ ...
1163
+ end
1164
+ end
1165
+ ```
1166
+
1167
+ Whatever column name is used, make sure to add a migration to provide this column
1168
+ (of type `string`).
1169
+ Do not add default value for column at the database level. If you add default
1170
+ value in database then AASM callbacks on the initial state will not be fired upon
1171
+ instantiation of the model.
1172
+
1173
+ ```ruby
1174
+ class AddJobState < ActiveRecord::Migration
1175
+ def self.up
1176
+ add_column :jobs, :aasm_state, :string
1177
+ end
1178
+
1179
+ def self.down
1180
+ remove_column :jobs, :aasm_state
1181
+ end
1182
+ end
1183
+ ```
1184
+
1185
+ ### Log State Changes
1186
+
1187
+ Logging state change can be done using [paper_trail](https://github.com/paper-trail-gem/paper_trail) gem
1188
+
1189
+ Example of implementation can be found here [https://github.com/nitsujri/aasm-papertrail-example](https://github.com/nitsujri/aasm-papertrail-example)
1190
+
1191
+
1192
+ ### Inspection
1193
+
1194
+ AASM supports query methods for states and events
1195
+
1196
+ Given the following `Job` class:
1197
+
1198
+ ```ruby
1199
+ class Job
1200
+ include AASM
1201
+
1202
+ aasm do
1203
+ state :sleeping, initial: true
1204
+ state :running, :cleaning
1205
+
1206
+ event :run do
1207
+ transitions from: :sleeping, to: :running
1208
+ end
1209
+
1210
+ event :clean do
1211
+ transitions from: :running, to: :cleaning, guard: :cleaning_needed?
1212
+ end
1213
+
1214
+ event :sleep do
1215
+ transitions from: [:running, :cleaning], to: :sleeping
1216
+ end
1217
+ end
1218
+
1219
+ def cleaning_needed?
1220
+ false
1221
+ end
1222
+ end
1223
+ ```
1224
+
1225
+ ```ruby
1226
+ # show all states
1227
+ Job.aasm.states.map(&:name)
1228
+ #=> [:sleeping, :running, :cleaning]
1229
+
1230
+ job = Job.new
1231
+
1232
+ # show all permitted states (from initial state)
1233
+ job.aasm.states(permitted: true).map(&:name)
1234
+ #=> [:running]
1235
+
1236
+ # List all the permitted transitions(event and state pairs) from initial state
1237
+ job.aasm.permitted_transitions
1238
+ #=> [{ :event => :run, :state => :running }]
1239
+
1240
+ job.run
1241
+ job.aasm.states(permitted: true).map(&:name)
1242
+ #=> [:sleeping]
1243
+
1244
+ # show all non permitted states
1245
+ job.aasm.states(permitted: false).map(&:name)
1246
+ #=> [:cleaning]
1247
+
1248
+ # show all possible (triggerable) events from the current state
1249
+ job.aasm.events.map(&:name)
1250
+ #=> [:clean, :sleep]
1251
+
1252
+ # show all permitted events
1253
+ job.aasm.events(permitted: true).map(&:name)
1254
+ #=> [:sleep]
1255
+
1256
+ # show all non permitted events
1257
+ job.aasm.events(permitted: false).map(&:name)
1258
+ #=> [:clean]
1259
+
1260
+ # show all possible events except a specific one
1261
+ job.aasm.events(reject: :sleep).map(&:name)
1262
+ #=> [:clean]
1263
+
1264
+ # list states for select
1265
+ Job.aasm.states_for_select
1266
+ #=> [["Sleeping", "sleeping"], ["Running", "running"], ["Cleaning", "cleaning"]]
1267
+
1268
+ # show permitted states with guard parameter
1269
+ job.aasm.states({permitted: true}, guard_parameter).map(&:name)
1270
+ ```
1271
+
1272
+
1273
+ ### Warning output
1274
+
1275
+ Warnings are by default printed to `STDERR`. If you want to log those warnings to another output,
1276
+ use
1277
+
1278
+ ```ruby
1279
+ class Job
1280
+ include AASM
1281
+
1282
+ aasm logger: Rails.logger do
1283
+ ...
1284
+ end
1285
+ end
1286
+ ```
1287
+
1288
+ You can hide warnings by setting `AASM::Configuration.hide_warnings = true`
1289
+
1290
+ ### RubyMotion support
1291
+
1292
+ Now supports [CodeDataQuery](https://github.com/infinitered/cdq.git) !
1293
+ However I'm still in the process of submitting my compatibility updates to their repository.
1294
+ 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.
1295
+
1296
+ Warnings:
1297
+ - Due to RubyMotion Proc's lack of 'source_location' method, it may be harder
1298
+ to find out the origin of a "cannot transition from" error. I would recommend using
1299
+ the 'instance method symbol / string' way whenever possible when defining guardians and callbacks.
1300
+
1301
+
1302
+ ### Testing
1303
+
1304
+ #### RSpec
1305
+
1306
+ AASM provides some matchers for [RSpec](http://rspec.info):
1307
+ * `transition_from`,
1308
+ * `have_state`, `allow_event`
1309
+ * and `allow_transition_to`.
1310
+
1311
+ ##### Installation Instructions:
1312
+ * Add `require 'aasm/rspec'` to your `spec_helper.rb` file.
1313
+
1314
+ ##### Examples Of Usage in Rspec:
1315
+
1316
+ ```ruby
1317
+ # classes with only the default state machine
1318
+ job = Job.new
1319
+ expect(job).to transition_from(:sleeping).to(:running).on_event(:run)
1320
+ expect(job).not_to transition_from(:sleeping).to(:cleaning).on_event(:run)
1321
+ expect(job).to have_state(:sleeping)
1322
+ expect(job).not_to have_state(:running)
1323
+ expect(job).to allow_event :run
1324
+ expect(job).to_not allow_event :clean
1325
+ expect(job).to allow_transition_to(:running)
1326
+ expect(job).to_not allow_transition_to(:cleaning)
1327
+ # on_event also accept multiple arguments
1328
+ expect(job).to transition_from(:sleeping).to(:running).on_event(:run, :defragmentation)
1329
+
1330
+ # classes with multiple state machine
1331
+ multiple = SimpleMultipleExample.new
1332
+ expect(multiple).to transition_from(:standing).to(:walking).on_event(:walk).on(:move)
1333
+ expect(multiple).to_not transition_from(:standing).to(:running).on_event(:walk).on(:move)
1334
+ expect(multiple).to have_state(:standing).on(:move)
1335
+ expect(multiple).not_to have_state(:walking).on(:move)
1336
+ expect(multiple).to allow_event(:walk).on(:move)
1337
+ expect(multiple).to_not allow_event(:hold).on(:move)
1338
+ expect(multiple).to allow_transition_to(:walking).on(:move)
1339
+ expect(multiple).to_not allow_transition_to(:running).on(:move)
1340
+ expect(multiple).to transition_from(:sleeping).to(:processing).on_event(:start).on(:work)
1341
+ expect(multiple).to_not transition_from(:sleeping).to(:sleeping).on_event(:start).on(:work)
1342
+ expect(multiple).to have_state(:sleeping).on(:work)
1343
+ expect(multiple).not_to have_state(:processing).on(:work)
1344
+ expect(multiple).to allow_event(:start).on(:move)
1345
+ expect(multiple).to_not allow_event(:stop).on(:move)
1346
+ expect(multiple).to allow_transition_to(:processing).on(:move)
1347
+ expect(multiple).to_not allow_transition_to(:sleeping).on(:move)
1348
+ # allow_event also accepts arguments
1349
+ expect(job).to allow_event(:run).with(:defragmentation)
1350
+
1351
+ ```
1352
+
1353
+ #### Minitest
1354
+
1355
+ AASM provides assertions and rspec-like expectations for [Minitest](https://github.com/seattlerb/minitest).
1356
+
1357
+ ##### Assertions
1358
+
1359
+ 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`.
1360
+
1361
+
1362
+ ##### Examples Of Usage (Minitest):
1363
+
1364
+ Add `require 'aasm/minitest'` to your `test_helper.rb` file and use them like this:
1365
+
1366
+ ```ruby
1367
+ # classes with only the default state machine
1368
+ job = Job.new
1369
+ assert_transitions_from job, :sleeping, to: :running, on_event: :run
1370
+ refute_transitions_from job, :sleeping, to: :cleaning, on_event: :run
1371
+ assert_have_state job, :sleeping
1372
+ refute_have_state job, :running
1373
+ assert_event_allowed job, :run
1374
+ refute_event_allowed job, :clean
1375
+ assert_transition_to_allowed job, :running
1376
+ refute_transition_to_allowed job, :cleaning
1377
+ # on_event also accept arguments
1378
+ assert_transitions_from job, :sleeping, :defragmentation, to: :running, on_event: :run
1379
+
1380
+ # classes with multiple state machine
1381
+ multiple = SimpleMultipleExample.new
1382
+ assert_transitions_from multiple, :standing, to: :walking, on_event: :walk, on: :move
1383
+ refute_transitions_from multiple, :standing, to: :running, on_event: :walk, on: :move
1384
+ assert_have_state multiple, :standing, on: :move
1385
+ refute_have_state multiple, :walking, on: :move
1386
+ assert_event_allowed multiple, :walk, on: :move
1387
+ refute_event_allowed multiple, :hold, on: :move
1388
+ assert_transition_to_allowed multiple, :walking, on: :move
1389
+ refute_transition_to_allowed multiple, :running, on: :move
1390
+ assert_transitions_from multiple, :sleeping, to: :processing, on_event: :start, on: :work
1391
+ refute_transitions_from multiple, :sleeping, to: :sleeping, on_event: :start, on: :work
1392
+ assert_have_state multiple, :sleeping, on: :work
1393
+ refute_have_state multiple, :processing, on: :work
1394
+ assert_event_allowed multiple, :start, on: :move
1395
+ refute_event_allowed multiple, :stop, on: :move
1396
+ assert_transition_to_allowed multiple, :processing, on: :move
1397
+ refute_transition_to_allowed multiple, :sleeping, on: :move
1398
+ ```
1399
+
1400
+ ##### Expectations
1401
+
1402
+ 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`.
1403
+
1404
+ Add `require 'aasm/minitest_spec'` to your `test_helper.rb` file and use them like this:
1405
+
1406
+ ```ruby
1407
+ # classes with only the default state machine
1408
+ job = Job.new
1409
+ job.must_transition_from :sleeping, to: :running, on_event: :run
1410
+ job.wont_transition_from :sleeping, to: :cleaning, on_event: :run
1411
+ job.must_have_state :sleeping
1412
+ job.wont_have_state :running
1413
+ job.must_allow_event :run
1414
+ job.wont_allow_event :clean
1415
+ job.must_allow_transition_to :running
1416
+ job.wont_allow_transition_to :cleaning
1417
+ # on_event also accept arguments
1418
+ job.must_transition_from :sleeping, :defragmentation, to: :running, on_event: :run
1419
+
1420
+ # classes with multiple state machine
1421
+ multiple = SimpleMultipleExample.new
1422
+ multiple.must_transition_from :standing, to: :walking, on_event: :walk, on: :move
1423
+ multiple.wont_transition_from :standing, to: :running, on_event: :walk, on: :move
1424
+ multiple.must_have_state :standing, on: :move
1425
+ multiple.wont_have_state :walking, on: :move
1426
+ multiple.must_allow_event :walk, on: :move
1427
+ multiple.wont_allow_event :hold, on: :move
1428
+ multiple.must_allow_transition_to :walking, on: :move
1429
+ multiple.wont_allow_transition_to :running, on: :move
1430
+ multiple.must_transition_from :sleeping, to: :processing, on_event: :start, on: :work
1431
+ multiple.wont_transition_from :sleeping, to: :sleeping, on_event: :start, on: :work
1432
+ multiple.must_have_state :sleeping, on: :work
1433
+ multiple.wont_have_state :processing, on: :work
1434
+ multiple.must_allow_event :start, on: :move
1435
+ multiple.wont_allow_event :stop, on: :move
1436
+ multiple.must_allow_transition_to :processing, on: :move
1437
+ multiple.wont_allow_transition_to :sleeping, on: :move
1438
+ ```
1439
+
1440
+ ## <a id="installation">Installation ##
1441
+
1442
+ ### Manually from RubyGems.org ###
1443
+
1444
+ ```sh
1445
+ % gem install aasm
1446
+ ```
1447
+
1448
+ ### Or if you are using Bundler ###
1449
+
1450
+ ```ruby
1451
+ # Gemfile
1452
+ gem 'aasm'
1453
+ ```
1454
+
1455
+ ### Building your own gems ###
1456
+
1457
+ ```sh
1458
+ % rake build
1459
+ % sudo gem install pkg/aasm-x.y.z.gem
1460
+ ```
1461
+
1462
+ ### Generators
1463
+
1464
+ After installing AASM you can run generator:
1465
+
1466
+ ```sh
1467
+ % rails generate aasm NAME [COLUMN_NAME]
1468
+ ```
1469
+ Replace NAME with the Model name, COLUMN_NAME is optional(default is 'aasm_state').
1470
+ This will create a model (if one does not exist) and configure it with aasm block.
1471
+ For ActiveRecord orm a migration file is added to add aasm state column to table.
1472
+
1473
+ ### Docker
1474
+
1475
+ Run test suite easily on docker
1476
+ ```
1477
+ 1. docker-compose build aasm
1478
+ 2. docker-compose run --rm aasm
1479
+ ```
1480
+
1481
+ ## Latest changes ##
1482
+
1483
+ Take a look at the [CHANGELOG](https://github.com/aasm/aasm/blob/master/CHANGELOG.md) for details about recent changes to the current version.
1484
+
1485
+ ## Questions? ##
1486
+
1487
+ Feel free to
1488
+
1489
+ * [create an issue on GitHub](https://github.com/aasm/aasm/issues)
1490
+ * [ask a question on StackOverflow](http://stackoverflow.com) (tag with `aasm`)
1491
+
1492
+ ## Maintainers ##
1493
+
1494
+ * [Scott Barron](https://github.com/rubyist) (2006–2009, original author)
1495
+ * [Travis Tilley](https://github.com/ttilley) (2009–2011)
1496
+ * [Thorsten Böttger](http://github.com/alto) (since 2011)
1497
+ * [Anil Maurya](http://github.com/anilmaurya) (since 2016)
1498
+
1499
+ ## Stargazers over time
1500
+
1501
+ [![Stargazers over time](https://starchart.cc/aasm/aasm.svg)](https://starchart.cc/aasm/aasm)
1502
+
1503
+
1504
+ ## [Contributing](CONTRIBUTING.md)
1505
+
1506
+ ## Warranty ##
1507
+
1508
+ This software is provided "as is" and without any express or
1509
+ implied warranties, including, without limitation, the implied
1510
+ warranties of merchantability and fitness for a particular
1511
+ purpose.
1512
+
1513
+ ## License ##
1514
+
1515
+ Copyright (c) 2006-2017 Scott Barron
1516
+
1517
+ Permission is hereby granted, free of charge, to any person obtaining
1518
+ a copy of this software and associated documentation files (the
1519
+ "Software"), to deal in the Software without restriction, including
1520
+ without limitation the rights to use, copy, modify, merge, publish,
1521
+ distribute, sublicense, and/or sell copies of the Software, and to
1522
+ permit persons to whom the Software is furnished to do so, subject to
1523
+ the following conditions:
1524
+
1525
+ The above copyright notice and this permission notice shall be
1526
+ included in all copies or substantial portions of the Software.
1527
+
1528
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1529
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1530
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1531
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
1532
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
1533
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
1534
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.