aasm 5.1.1 → 5.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (228) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +74 -15
  3. data/lib/aasm/base.rb +44 -12
  4. data/lib/aasm/configuration.rb +3 -0
  5. data/lib/aasm/core/event.rb +12 -6
  6. data/lib/aasm/core/invokers/class_invoker.rb +1 -1
  7. data/lib/aasm/core/invokers/literal_invoker.rb +3 -1
  8. data/lib/aasm/core/state.rb +6 -5
  9. data/lib/aasm/core/transition.rb +1 -1
  10. data/lib/aasm/dsl_helper.rb +24 -22
  11. data/lib/aasm/errors.rb +1 -0
  12. data/lib/aasm/instance_base.rb +14 -3
  13. data/lib/aasm/localizer.rb +13 -3
  14. data/lib/aasm/persistence/active_record_persistence.rb +1 -1
  15. data/lib/aasm/persistence/base.rb +13 -2
  16. data/lib/aasm/version.rb +1 -1
  17. data/lib/generators/aasm/orm_helpers.rb +1 -1
  18. data/lib/generators/active_record/templates/migration.rb +1 -1
  19. metadata +6 -402
  20. data/.document +0 -6
  21. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -27
  22. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  23. data/.gitignore +0 -20
  24. data/.travis.yml +0 -82
  25. data/API +0 -34
  26. data/Appraisals +0 -67
  27. data/CHANGELOG.md +0 -442
  28. data/CODE_OF_CONDUCT.md +0 -13
  29. data/CONTRIBUTING.md +0 -24
  30. data/Dockerfile +0 -44
  31. data/Gemfile +0 -6
  32. data/Gemfile.lock_old +0 -151
  33. data/HOWTO +0 -12
  34. data/PLANNED_CHANGES.md +0 -11
  35. data/README_FROM_VERSION_3_TO_4.md +0 -240
  36. data/Rakefile +0 -31
  37. data/TESTING.md +0 -25
  38. data/aasm.gemspec +0 -37
  39. data/docker-compose.yml +0 -40
  40. data/gemfiles/norails.gemfile +0 -10
  41. data/gemfiles/rails_4.2.gemfile +0 -17
  42. data/gemfiles/rails_4.2_mongoid_5.gemfile +0 -12
  43. data/gemfiles/rails_4.2_nobrainer.gemfile +0 -9
  44. data/gemfiles/rails_5.0.gemfile +0 -14
  45. data/gemfiles/rails_5.0_nobrainer.gemfile +0 -9
  46. data/gemfiles/rails_5.1.gemfile +0 -14
  47. data/gemfiles/rails_5.2.gemfile +0 -14
  48. data/spec/database.rb +0 -59
  49. data/spec/database.yml +0 -3
  50. data/spec/en.yml +0 -9
  51. data/spec/generators/active_record_generator_spec.rb +0 -53
  52. data/spec/generators/mongoid_generator_spec.rb +0 -31
  53. data/spec/generators/no_brainer_generator_spec.rb +0 -29
  54. data/spec/localizer_test_model_deprecated_style.yml +0 -6
  55. data/spec/localizer_test_model_new_style.yml +0 -5
  56. data/spec/models/active_record/active_record_callback.rb +0 -93
  57. data/spec/models/active_record/basic_active_record_two_state_machines_example.rb +0 -25
  58. data/spec/models/active_record/complex_active_record_example.rb +0 -37
  59. data/spec/models/active_record/derivate_new_dsl.rb +0 -7
  60. data/spec/models/active_record/false_state.rb +0 -35
  61. data/spec/models/active_record/gate.rb +0 -39
  62. data/spec/models/active_record/instance_level_skip_validation_example.rb +0 -19
  63. data/spec/models/active_record/invalid_persistor.rb +0 -29
  64. data/spec/models/active_record/localizer_test_model.rb +0 -34
  65. data/spec/models/active_record/no_direct_assignment.rb +0 -21
  66. data/spec/models/active_record/no_scope.rb +0 -21
  67. data/spec/models/active_record/persisted_state.rb +0 -12
  68. data/spec/models/active_record/person.rb +0 -23
  69. data/spec/models/active_record/provided_and_persisted_state.rb +0 -24
  70. data/spec/models/active_record/reader.rb +0 -7
  71. data/spec/models/active_record/readme_job.rb +0 -21
  72. data/spec/models/active_record/silent_persistor.rb +0 -29
  73. data/spec/models/active_record/simple_new_dsl.rb +0 -32
  74. data/spec/models/active_record/thief.rb +0 -29
  75. data/spec/models/active_record/transactor.rb +0 -124
  76. data/spec/models/active_record/transient.rb +0 -6
  77. data/spec/models/active_record/validator.rb +0 -118
  78. data/spec/models/active_record/with_enum.rb +0 -39
  79. data/spec/models/active_record/with_enum_without_column.rb +0 -38
  80. data/spec/models/active_record/with_false_enum.rb +0 -31
  81. data/spec/models/active_record/with_true_enum.rb +0 -39
  82. data/spec/models/active_record/work.rb +0 -3
  83. data/spec/models/active_record/worker.rb +0 -2
  84. data/spec/models/active_record/writer.rb +0 -6
  85. data/spec/models/basic_two_state_machines_example.rb +0 -25
  86. data/spec/models/callbacks/basic.rb +0 -98
  87. data/spec/models/callbacks/basic_multiple.rb +0 -75
  88. data/spec/models/callbacks/guard_within_block.rb +0 -67
  89. data/spec/models/callbacks/guard_within_block_multiple.rb +0 -66
  90. data/spec/models/callbacks/multiple_transitions_transition_guard.rb +0 -66
  91. data/spec/models/callbacks/multiple_transitions_transition_guard_multiple.rb +0 -65
  92. data/spec/models/callbacks/private_method.rb +0 -44
  93. data/spec/models/callbacks/private_method_multiple.rb +0 -44
  94. data/spec/models/callbacks/with_args.rb +0 -62
  95. data/spec/models/callbacks/with_args_multiple.rb +0 -61
  96. data/spec/models/callbacks/with_state_arg.rb +0 -34
  97. data/spec/models/callbacks/with_state_arg_multiple.rb +0 -29
  98. data/spec/models/complex_example.rb +0 -222
  99. data/spec/models/conversation.rb +0 -93
  100. data/spec/models/default_state.rb +0 -12
  101. data/spec/models/double_definer.rb +0 -21
  102. data/spec/models/dynamoid/complex_dynamoid_example.rb +0 -37
  103. data/spec/models/dynamoid/dynamoid_multiple.rb +0 -18
  104. data/spec/models/dynamoid/dynamoid_simple.rb +0 -18
  105. data/spec/models/foo.rb +0 -106
  106. data/spec/models/foo_callback_multiple.rb +0 -45
  107. data/spec/models/guard_arguments_check.rb +0 -17
  108. data/spec/models/guard_with_params.rb +0 -24
  109. data/spec/models/guard_with_params_multiple.rb +0 -18
  110. data/spec/models/guardian.rb +0 -58
  111. data/spec/models/guardian_multiple.rb +0 -48
  112. data/spec/models/guardian_without_from_specified.rb +0 -18
  113. data/spec/models/initial_state_proc.rb +0 -31
  114. data/spec/models/mongoid/complex_mongoid_example.rb +0 -37
  115. data/spec/models/mongoid/invalid_persistor_mongoid.rb +0 -39
  116. data/spec/models/mongoid/mongoid_relationships.rb +0 -26
  117. data/spec/models/mongoid/no_scope_mongoid.rb +0 -21
  118. data/spec/models/mongoid/silent_persistor_mongoid.rb +0 -39
  119. data/spec/models/mongoid/simple_mongoid.rb +0 -23
  120. data/spec/models/mongoid/simple_new_dsl_mongoid.rb +0 -25
  121. data/spec/models/mongoid/validator_mongoid.rb +0 -100
  122. data/spec/models/multi_transitioner.rb +0 -34
  123. data/spec/models/multiple_transitions_that_differ_only_by_guard.rb +0 -31
  124. data/spec/models/namespaced_multiple_example.rb +0 -42
  125. data/spec/models/no_initial_state.rb +0 -25
  126. data/spec/models/nobrainer/complex_no_brainer_example.rb +0 -36
  127. data/spec/models/nobrainer/invalid_persistor_no_brainer.rb +0 -39
  128. data/spec/models/nobrainer/no_scope_no_brainer.rb +0 -21
  129. data/spec/models/nobrainer/nobrainer_relationships.rb +0 -25
  130. data/spec/models/nobrainer/silent_persistor_no_brainer.rb +0 -39
  131. data/spec/models/nobrainer/simple_new_dsl_nobrainer.rb +0 -25
  132. data/spec/models/nobrainer/simple_no_brainer.rb +0 -23
  133. data/spec/models/nobrainer/validator_no_brainer.rb +0 -98
  134. data/spec/models/not_auto_loaded/process.rb +0 -21
  135. data/spec/models/parametrised_event.rb +0 -42
  136. data/spec/models/parametrised_event_multiple.rb +0 -29
  137. data/spec/models/process_with_new_dsl.rb +0 -31
  138. data/spec/models/provided_state.rb +0 -24
  139. data/spec/models/redis/complex_redis_example.rb +0 -40
  140. data/spec/models/redis/redis_multiple.rb +0 -20
  141. data/spec/models/redis/redis_simple.rb +0 -20
  142. data/spec/models/sequel/complex_sequel_example.rb +0 -46
  143. data/spec/models/sequel/invalid_persistor.rb +0 -52
  144. data/spec/models/sequel/sequel_multiple.rb +0 -25
  145. data/spec/models/sequel/sequel_simple.rb +0 -26
  146. data/spec/models/sequel/silent_persistor.rb +0 -50
  147. data/spec/models/sequel/transactor.rb +0 -112
  148. data/spec/models/sequel/validator.rb +0 -93
  149. data/spec/models/sequel/worker.rb +0 -12
  150. data/spec/models/silencer.rb +0 -27
  151. data/spec/models/simple_custom_example.rb +0 -53
  152. data/spec/models/simple_example.rb +0 -23
  153. data/spec/models/simple_example_with_guard_args.rb +0 -17
  154. data/spec/models/simple_multiple_example.rb +0 -42
  155. data/spec/models/state_machine_with_failed_event.rb +0 -20
  156. data/spec/models/states_on_one_line_example.rb +0 -8
  157. data/spec/models/sub_class.rb +0 -41
  158. data/spec/models/sub_class_with_more_states.rb +0 -18
  159. data/spec/models/sub_classing.rb +0 -3
  160. data/spec/models/super_class.rb +0 -46
  161. data/spec/models/this_name_better_not_be_in_use.rb +0 -11
  162. data/spec/models/valid_state_name.rb +0 -23
  163. data/spec/spec_helper.rb +0 -41
  164. data/spec/spec_helpers/active_record.rb +0 -8
  165. data/spec/spec_helpers/dynamoid.rb +0 -35
  166. data/spec/spec_helpers/mongoid.rb +0 -26
  167. data/spec/spec_helpers/nobrainer.rb +0 -15
  168. data/spec/spec_helpers/redis.rb +0 -18
  169. data/spec/spec_helpers/remove_warnings.rb +0 -1
  170. data/spec/spec_helpers/sequel.rb +0 -7
  171. data/spec/unit/abstract_class_spec.rb +0 -27
  172. data/spec/unit/api_spec.rb +0 -104
  173. data/spec/unit/basic_two_state_machines_example_spec.rb +0 -10
  174. data/spec/unit/callback_multiple_spec.rb +0 -304
  175. data/spec/unit/callbacks_spec.rb +0 -521
  176. data/spec/unit/complex_example_spec.rb +0 -93
  177. data/spec/unit/complex_multiple_example_spec.rb +0 -115
  178. data/spec/unit/edge_cases_spec.rb +0 -16
  179. data/spec/unit/event_multiple_spec.rb +0 -73
  180. data/spec/unit/event_naming_spec.rb +0 -16
  181. data/spec/unit/event_spec.rb +0 -394
  182. data/spec/unit/exception_spec.rb +0 -11
  183. data/spec/unit/guard_arguments_check_spec.rb +0 -9
  184. data/spec/unit/guard_multiple_spec.rb +0 -60
  185. data/spec/unit/guard_spec.rb +0 -89
  186. data/spec/unit/guard_with_params_multiple_spec.rb +0 -10
  187. data/spec/unit/guard_with_params_spec.rb +0 -14
  188. data/spec/unit/guard_without_from_specified_spec.rb +0 -10
  189. data/spec/unit/initial_state_multiple_spec.rb +0 -15
  190. data/spec/unit/initial_state_spec.rb +0 -12
  191. data/spec/unit/inspection_multiple_spec.rb +0 -205
  192. data/spec/unit/inspection_spec.rb +0 -153
  193. data/spec/unit/invoker_spec.rb +0 -189
  194. data/spec/unit/invokers/base_invoker_spec.rb +0 -72
  195. data/spec/unit/invokers/class_invoker_spec.rb +0 -95
  196. data/spec/unit/invokers/literal_invoker_spec.rb +0 -86
  197. data/spec/unit/invokers/proc_invoker_spec.rb +0 -86
  198. data/spec/unit/localizer_spec.rb +0 -77
  199. data/spec/unit/memory_leak_spec.rb +0 -38
  200. data/spec/unit/multiple_transitions_that_differ_only_by_guard_spec.rb +0 -14
  201. data/spec/unit/namespaced_multiple_example_spec.rb +0 -75
  202. data/spec/unit/new_dsl_spec.rb +0 -12
  203. data/spec/unit/override_warning_spec.rb +0 -94
  204. data/spec/unit/persistence/active_record_persistence_multiple_spec.rb +0 -618
  205. data/spec/unit/persistence/active_record_persistence_spec.rb +0 -840
  206. data/spec/unit/persistence/dynamoid_persistence_multiple_spec.rb +0 -135
  207. data/spec/unit/persistence/dynamoid_persistence_spec.rb +0 -84
  208. data/spec/unit/persistence/mongoid_persistence_multiple_spec.rb +0 -200
  209. data/spec/unit/persistence/mongoid_persistence_spec.rb +0 -165
  210. data/spec/unit/persistence/no_brainer_persistence_multiple_spec.rb +0 -198
  211. data/spec/unit/persistence/no_brainer_persistence_spec.rb +0 -158
  212. data/spec/unit/persistence/redis_persistence_multiple_spec.rb +0 -88
  213. data/spec/unit/persistence/redis_persistence_spec.rb +0 -53
  214. data/spec/unit/persistence/sequel_persistence_multiple_spec.rb +0 -148
  215. data/spec/unit/persistence/sequel_persistence_spec.rb +0 -368
  216. data/spec/unit/readme_spec.rb +0 -41
  217. data/spec/unit/reloading_spec.rb +0 -15
  218. data/spec/unit/rspec_matcher_spec.rb +0 -88
  219. data/spec/unit/simple_custom_example_spec.rb +0 -39
  220. data/spec/unit/simple_example_spec.rb +0 -57
  221. data/spec/unit/simple_multiple_example_spec.rb +0 -91
  222. data/spec/unit/state_spec.rb +0 -105
  223. data/spec/unit/states_on_one_line_example_spec.rb +0 -16
  224. data/spec/unit/subclassing_multiple_spec.rb +0 -74
  225. data/spec/unit/subclassing_spec.rb +0 -46
  226. data/spec/unit/transition_spec.rb +0 -436
  227. data/test/minitest_helper.rb +0 -57
  228. data/test/unit/minitest_matcher_test.rb +0 -80
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f2f4099a66f8d753c2bd7c06b3c378dcd37b47acf9b2f7487784e7762742c1c4
4
- data.tar.gz: 8971b36f1b066c08d3a4d278a99d7c1e5386be72182da62b51a82db1e60e9e9f
3
+ metadata.gz: a088e1285940890f3c1a87b6cf0534e80702a96d2fb6eb42b43ff0cf50e6c57d
4
+ data.tar.gz: b96c64d2458639d217540b8787b1f718d80cd8e64b2661825435a795d5796a93
5
5
  SHA512:
6
- metadata.gz: c24e544b82cfc4616f3e3ff033b7a5ab68bff7bd834b115797e9127ca361effe241ffb1adaedcdb595711a6757f2e0a8e0a8d606a9bc812c28707a1ee845ef40
7
- data.tar.gz: 421636d1e59e52b8d6b4ddff7dd373fecd9a19ef67b90935e856b1ee980bae9a5a4a8526e62238de9ecc84a7783362e8d5b2005c53b9c7754368be79724c6255
6
+ metadata.gz: 85a0532a6257477070680b2864c27191f618a61091fe9ba0c2c0672e99aa53c4b61968e4987607c4e5747bb42686cff6667966c90cf4592e87ad0324175e711c
7
+ data.tar.gz: 1abfc89101b1cdf7440a82446b6c600d65fe6f2bfc83575a85dc2ef0edb04968cc509c9ff88ec2ce68f4aa8b72283f75301a0fcfefffa16fc0347cb31f11c492
data/README.md CHANGED
@@ -20,6 +20,7 @@
20
20
  - [Extending AASM](#extending-aasm)
21
21
  - [ActiveRecord](#activerecord)
22
22
  - [Bang events](#bang-events)
23
+ - [Timestamps](#timestamps)
23
24
  - [ActiveRecord enums](#activerecord-enums)
24
25
  - [Sequel](#sequel)
25
26
  - [Dynamoid](#dynamoid)
@@ -212,15 +213,28 @@ class LogRunTime
212
213
  end
213
214
  ```
214
215
 
215
- Also, you can pass parameters to events:
216
+ #### Parameters
217
+ You can pass parameters to events:
216
218
 
217
219
  ```ruby
218
220
  job = Job.new
219
221
  job.run(:defragmentation)
220
222
  ```
221
223
 
222
- In this case the `set_process` would be called with `:defragmentation` argument.
224
+ All guards and after callbacks will receive these parameters. In this case `set_process` would be called with
225
+ `:defragmentation` argument.
223
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 guards 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
224
238
  In case of an error during the event processing the error is rescued and passed to `:error`
225
239
  callback, which can handle it or re-raise it for further propagation.
226
240
 
@@ -307,7 +321,7 @@ and then
307
321
  Let's assume you want to allow particular transitions only if a defined condition is
308
322
  given. For this you can set up a guard per transition, which will run before actually
309
323
  running the transition. If the guard returns `false` the transition will be
310
- denied (raising `AASM::InvalidTransition` or returning `false` itself):
324
+ denied (raising `AASM::InvalidTransition`):
311
325
 
312
326
  ```ruby
313
327
  class Cleaner
@@ -350,7 +364,7 @@ job.clean # => raises AASM::InvalidTransition
350
364
  job.may_clean_if_needed? # => true
351
365
  job.clean_if_needed! # idle
352
366
 
353
- job.clean_if_dirty(:clean) # => false
367
+ job.clean_if_dirty(:clean) # => raises AASM::InvalidTransition
354
368
  job.clean_if_dirty(:dirty) # => true
355
369
  ```
356
370
 
@@ -386,7 +400,7 @@ If you prefer a more Ruby-like guard syntax, you can use `if` and `unless` as we
386
400
  end
387
401
  ```
388
402
 
389
- You can invoke a Class instead a method since this Class responds to `call`
403
+ You can invoke a Class instead of a method if the Class responds to `call`
390
404
 
391
405
  ```ruby
392
406
  event :sleep do
@@ -434,6 +448,14 @@ job.stage1_completed
434
448
  job.aasm.current_state # stage3
435
449
  ```
436
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
+
437
459
  ### Display name for state
438
460
 
439
461
  You can define display name for state using :display option
@@ -495,13 +517,13 @@ simple = SimpleMultipleExample.new
495
517
 
496
518
  simple.aasm(:move).current_state
497
519
  # => :standing
498
- simple.aasm(:work).current
520
+ simple.aasm(:work).current_state
499
521
  # => :sleeping
500
522
 
501
523
  simple.start
502
524
  simple.aasm(:move).current_state
503
525
  # => :standing
504
- simple.aasm(:work).current
526
+ simple.aasm(:work).current_state
505
527
  # => :processing
506
528
 
507
529
  ```
@@ -698,7 +720,7 @@ end
698
720
  AASM comes with support for ActiveRecord and allows automatic persisting of the object's
699
721
  state in the database.
700
722
 
701
- Add `gem 'after_commit_everywhere', '~> 0.1', '>= 0.1.5'` to your Gemfile
723
+ Add `gem 'after_commit_everywhere', '~> 1.0'` to your Gemfile.
702
724
 
703
725
  ```ruby
704
726
  class Job < ActiveRecord::Base
@@ -736,7 +758,7 @@ job.aasm.fire!(:run) # saved
736
758
 
737
759
  Saving includes running all validations on the `Job` class. If
738
760
  `whiny_persistence` flag is set to `true`, exception is raised in case of
739
- failure. If `whiny_persistence` flag is set to false, methods with a bang return
761
+ failure. If `whiny_persistence` flag is set to `false`, methods with a bang return
740
762
  `true` if the state transition is successful or `false` if an error occurs.
741
763
 
742
764
  If you want make sure the state gets saved without running validations (and
@@ -764,7 +786,7 @@ class Job < ActiveRecord::Base
764
786
  end
765
787
  ```
766
788
 
767
- Also You can skip the validation at instance level with `some_event_name_without_validation!` method.
789
+ Also, you can skip the validation at instance level with `some_event_name_without_validation!` method.
768
790
  With this you have the flexibility of having validation for all your transitions by default and then skip it wherever required.
769
791
  Please note that only state column will be updated as mentioned in the above example.
770
792
 
@@ -800,6 +822,37 @@ job.aasm_state = :running # => raises AASM::NoDirectAssignmentError
800
822
  job.aasm_state # => 'sleeping'
801
823
  ```
802
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
+
803
856
  #### ActiveRecord enums
804
857
 
805
858
  You can use
@@ -1007,7 +1060,7 @@ job.save! #notify_about_running_job is not run
1007
1060
  Please note that `:after_commit` AASM callbacks behaves around custom implementation
1008
1061
  of transaction pattern rather than a real-life DB transaction. This fact still causes
1009
1062
  the race conditions and redundant callback calls within nested transaction. In order
1010
- to fix that it's highly recommended to add `gem 'after_commit_everywhere', '~> 0.1', '>= 0.1.5'`
1063
+ to fix that it's highly recommended to add `gem 'after_commit_everywhere', '~> 1.0'`
1011
1064
  to your `Gemfile`.
1012
1065
 
1013
1066
  If you want to encapsulate state changes within an own transaction, the behavior
@@ -1031,7 +1084,7 @@ end
1031
1084
 
1032
1085
  which then leads to `transaction(requires_new: false)`, the Rails default.
1033
1086
 
1034
- Additionally, if you do not want any of your active record actions to be
1087
+ Additionally, if you do not want any of your ActiveRecord actions to be
1035
1088
  wrapped in a transaction, you can specify the `use_transactions` flag. This can
1036
1089
  be useful if you want want to persist things to the database that happen as a
1037
1090
  result of a transaction or callback, even when some error occurs. The
@@ -1051,7 +1104,7 @@ end
1051
1104
 
1052
1105
  ### Pessimistic Locking
1053
1106
 
1054
- AASM supports [Active Record pessimistic locking via `with_lock`](http://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html#method-i-with_lock) for database persistence layers.
1107
+ AASM supports [ActiveRecord pessimistic locking via `with_lock`](http://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html#method-i-with_lock) for database persistence layers.
1055
1108
 
1056
1109
  | Option | Purpose |
1057
1110
  | ------ | ------- |
@@ -1107,7 +1160,7 @@ end
1107
1160
  Whatever column name is used, make sure to add a migration to provide this column
1108
1161
  (of type `string`).
1109
1162
  Do not add default value for column at the database level. If you add default
1110
- value in database then AASM callbacks on the initial state will not be fired upon
1163
+ value in database then AASM callbacks on the initial state will not be fired upon
1111
1164
  instantiation of the model.
1112
1165
 
1113
1166
  ```ruby
@@ -1408,7 +1461,7 @@ After installing AASM you can run generator:
1408
1461
  ```
1409
1462
  Replace NAME with the Model name, COLUMN_NAME is optional(default is 'aasm_state').
1410
1463
  This will create a model (if one does not exist) and configure it with aasm block.
1411
- For Active record orm a migration file is added to add aasm state column to table.
1464
+ For ActiveRecord orm a migration file is added to add aasm state column to table.
1412
1465
 
1413
1466
  ### Docker
1414
1467
 
@@ -1438,6 +1491,12 @@ Feel free to
1438
1491
  * [Anil Maurya](http://github.com/anilmaurya) (since 2016)
1439
1492
 
1440
1493
 
1494
+
1495
+ ## Stargazers over time
1496
+
1497
+ [![Stargazers over time](https://starchart.cc/aasm/aasm.svg)](https://starchart.cc/aasm/aasm)
1498
+
1499
+
1441
1500
  ## [Contributing](CONTRIBUTING.md)
1442
1501
 
1443
1502
  ## Warranty ##
data/lib/aasm/base.rb CHANGED
@@ -37,6 +37,9 @@ module AASM
37
37
  # string for a specific lock type i.e. FOR UPDATE NOWAIT
38
38
  configure :requires_lock, false
39
39
 
40
+ # automatically set `"#{state_name}_at" = ::Time.now` on state changes
41
+ configure :timestamps, false
42
+
40
43
  # set to true to forbid direct assignment of aasm_state column (in ActiveRecord)
41
44
  configure :no_direct_assignment, false
42
45
 
@@ -51,19 +54,12 @@ module AASM
51
54
  # Configure a logger, with default being a Logger to STDERR
52
55
  configure :logger, Logger.new(STDERR)
53
56
 
57
+ # setup timestamp-setting callback if enabled
58
+ setup_timestamps(@name)
59
+
54
60
  # make sure to raise an error if no_direct_assignment is enabled
55
61
  # and attribute is directly assigned though
56
- aasm_name = @name
57
-
58
- if @state_machine.config.no_direct_assignment
59
- @klass.send(:define_method, "#{@state_machine.config.column}=") do |state_name|
60
- if self.class.aasm(:"#{aasm_name}").state_machine.config.no_direct_assignment
61
- raise AASM::NoDirectAssignmentError.new('direct assignment of AASM column has been disabled (see AASM configuration for this class)')
62
- else
63
- super(state_name)
64
- end
65
- end
66
- end
62
+ setup_no_direct_assignment(@name)
67
63
  end
68
64
 
69
65
  # This method is both a getter and a setter
@@ -227,7 +223,20 @@ module AASM
227
223
  end
228
224
  end
229
225
 
230
- klass.send(:define_method, method_name, method_definition)
226
+ klass.send(:define_method, method_name, method_definition).tap do |sym|
227
+ apply_ruby2_keyword(klass, sym)
228
+ end
229
+ end
230
+
231
+ def apply_ruby2_keyword(klass, sym)
232
+ if RUBY_VERSION >= '2.7.1'
233
+ if klass.instance_method(sym).parameters.find { |type, _| type.to_s.start_with?('rest') }
234
+ # If there is a place where you are receiving in *args, do ruby2_keywords.
235
+ klass.module_eval do
236
+ ruby2_keywords sym
237
+ end
238
+ end
239
+ end
231
240
  end
232
241
 
233
242
  def namespace?
@@ -267,5 +276,28 @@ module AASM
267
276
  end
268
277
  end
269
278
 
279
+ def setup_timestamps(aasm_name)
280
+ return unless @state_machine.config.timestamps
281
+
282
+ after_all_transitions do
283
+ if self.class.aasm(:"#{aasm_name}").state_machine.config.timestamps
284
+ ts_setter = "#{aasm(aasm_name).to_state}_at="
285
+ respond_to?(ts_setter) && send(ts_setter, ::Time.now)
286
+ end
287
+ end
288
+ end
289
+
290
+ def setup_no_direct_assignment(aasm_name)
291
+ return unless @state_machine.config.no_direct_assignment
292
+
293
+ @klass.send(:define_method, "#{@state_machine.config.column}=") do |state_name|
294
+ if self.class.aasm(:"#{aasm_name}").state_machine.config.no_direct_assignment
295
+ raise AASM::NoDirectAssignmentError.new('direct assignment of AASM column has been disabled (see AASM configuration for this class)')
296
+ else
297
+ super(state_name)
298
+ end
299
+ end
300
+ end
301
+
270
302
  end
271
303
  end
@@ -24,6 +24,9 @@ module AASM
24
24
  # for ActiveRecord: use pessimistic locking
25
25
  attr_accessor :requires_lock
26
26
 
27
+ # automatically set `"#{state_name}_at" = ::Time.now` on state changes
28
+ attr_accessor :timestamps
29
+
27
30
  # forbid direct assignment in aasm_state column (in ActiveRecord)
28
31
  attr_accessor :no_direct_assignment
29
32
 
@@ -2,17 +2,18 @@
2
2
 
3
3
  module AASM::Core
4
4
  class Event
5
- include DslHelper
5
+ include AASM::DslHelper
6
6
 
7
- attr_reader :name, :state_machine, :options
7
+ attr_reader :name, :state_machine, :options, :default_display_name
8
8
 
9
9
  def initialize(name, state_machine, options = {}, &block)
10
10
  @name = name
11
11
  @state_machine = state_machine
12
12
  @transitions = []
13
- @valid_transitions = {}
13
+ @valid_transitions = Hash.new { |h, k| h[k] = {} }
14
14
  @guards = Array(options[:guard] || options[:guards] || options[:if])
15
15
  @unless = Array(options[:unless]) #TODO: This could use a better name
16
+ @default_display_name = name.to_s.gsub(/_/, ' ').capitalize
16
17
 
17
18
  # from aasm4
18
19
  @options = options # QUESTION: .dup ?
@@ -78,8 +79,9 @@ module AASM::Core
78
79
 
79
80
  def fire_transition_callbacks(obj, *args)
80
81
  from_state = obj.aasm(state_machine.name).current_state
81
- transition = @valid_transitions[from_state]
82
- @valid_transitions[from_state].invoke_success_callbacks(obj, *args) if transition
82
+ transition = @valid_transitions[obj.object_id][from_state]
83
+ transition.invoke_success_callbacks(obj, *args) if transition
84
+ @valid_transitions.delete(obj.object_id)
83
85
  end
84
86
 
85
87
  def ==(event)
@@ -109,6 +111,10 @@ module AASM::Core
109
111
  transitions.flat_map(&:failures)
110
112
  end
111
113
 
114
+ def to_s
115
+ name.to_s
116
+ end
117
+
112
118
  private
113
119
 
114
120
  def attach_event_guards(definitions)
@@ -148,7 +154,7 @@ module AASM::Core
148
154
  result = transition
149
155
  else
150
156
  result = to_state || Array(transition.to).first
151
- Array(transition.to).each {|to| @valid_transitions[to] = transition }
157
+ Array(transition.to).each {|to| @valid_transitions[obj.object_id][to] = transition }
152
158
  transition.execute(obj, *args)
153
159
  end
154
160
 
@@ -17,7 +17,7 @@ module AASM
17
17
  end
18
18
 
19
19
  def invoke_subject
20
- @result = retrieve_instance.call
20
+ @result = instance.call
21
21
  end
22
22
 
23
23
  private
@@ -30,7 +30,9 @@ module AASM
30
30
  raise(*record_error) unless record.respond_to?(subject, true)
31
31
  return record.__send__(subject) if subject_arity.zero?
32
32
  return record.__send__(subject, *args) if subject_arity < 0
33
- record.__send__(subject, *args[0..(subject_arity - 1)])
33
+ req_args = args[0..(subject_arity - 1)]
34
+ return record.__send__(subject, **req_args[0]) if req_args[0].is_a?(Hash)
35
+ record.__send__(subject, *req_args)
34
36
  end
35
37
  # rubocop:enable Metrics/AbcSize
36
38
 
@@ -2,12 +2,13 @@
2
2
 
3
3
  module AASM::Core
4
4
  class State
5
- attr_reader :name, :state_machine, :options
5
+ attr_reader :name, :state_machine, :options, :default_display_name
6
6
 
7
7
  def initialize(name, klass, state_machine, options={})
8
8
  @name = name
9
9
  @klass = klass
10
10
  @state_machine = state_machine
11
+ @default_display_name = name.to_s.gsub(/_/, ' ').capitalize
11
12
  update(options)
12
13
  end
13
14
 
@@ -54,11 +55,11 @@ module AASM::Core
54
55
  end
55
56
 
56
57
  def display_name
57
- @display_name ||= begin
58
+ @display_name = begin
58
59
  if Module.const_defined?(:I18n)
59
60
  localized_name
60
61
  else
61
- name.to_s.gsub(/_/, ' ').capitalize
62
+ @default_display_name
62
63
  end
63
64
  end
64
65
  end
@@ -75,8 +76,8 @@ module AASM::Core
75
76
  private
76
77
 
77
78
  def update(options = {})
78
- if options.key?(:display) then
79
- @display_name = options.delete(:display)
79
+ if options.key?(:display)
80
+ @default_display_name = options.delete(:display)
80
81
  end
81
82
  @options = options
82
83
  self
@@ -2,7 +2,7 @@
2
2
 
3
3
  module AASM::Core
4
4
  class Transition
5
- include DslHelper
5
+ include AASM::DslHelper
6
6
 
7
7
  attr_reader :from, :to, :event, :opts, :failures
8
8
  alias_method :options, :opts
@@ -1,30 +1,32 @@
1
- module DslHelper
1
+ module AASM
2
+ module DslHelper
2
3
 
3
- class Proxy
4
- attr_accessor :options
4
+ class Proxy
5
+ attr_accessor :options
5
6
 
6
- def initialize(options, valid_keys, source)
7
- @valid_keys = valid_keys
8
- @source = source
7
+ def initialize(options, valid_keys, source)
8
+ @valid_keys = valid_keys
9
+ @source = source
9
10
 
10
- @options = options
11
- end
11
+ @options = options
12
+ end
12
13
 
13
- def method_missing(name, *args, &block)
14
- if @valid_keys.include?(name)
15
- options[name] = Array(options[name])
16
- options[name] << block if block
17
- options[name] += Array(args)
18
- else
19
- @source.send name, *args, &block
14
+ def method_missing(name, *args, &block)
15
+ if @valid_keys.include?(name)
16
+ options[name] = Array(options[name])
17
+ options[name] << block if block
18
+ options[name] += Array(args)
19
+ else
20
+ @source.send name, *args, &block
21
+ end
20
22
  end
21
23
  end
22
- end
23
24
 
24
- def add_options_from_dsl(options, valid_keys, &block)
25
- proxy = Proxy.new(options, valid_keys, self)
26
- proxy.instance_eval(&block)
27
- proxy.options
28
- end
25
+ def add_options_from_dsl(options, valid_keys, &block)
26
+ proxy = Proxy.new(options, valid_keys, self)
27
+ proxy.instance_eval(&block)
28
+ proxy.options
29
+ end
29
30
 
30
- end
31
+ end
32
+ end
data/lib/aasm/errors.rb CHANGED
@@ -17,5 +17,6 @@ module AASM
17
17
  end
18
18
 
19
19
  class UndefinedState < RuntimeError; end
20
+ class UndefinedEvent < UndefinedState; end
20
21
  class NoDirectAssignmentError < RuntimeError; end
21
22
  end
@@ -1,6 +1,5 @@
1
1
  module AASM
2
2
  class InstanceBase
3
-
4
3
  attr_accessor :from_state, :to_state, :current_event
5
4
 
6
5
  def initialize(instance, name=:default) # instance of the class including AASM, name of the state machine
@@ -115,12 +114,15 @@ module AASM
115
114
  end
116
115
 
117
116
  def fire(event_name, *args, &block)
117
+ event_exists?(event_name)
118
+
118
119
  @instance.send(event_name, *args, &block)
119
120
  end
120
121
 
121
122
  def fire!(event_name, *args, &block)
122
- event_name = event_name.to_s.+("!").to_sym
123
- @instance.send(event_name, *args, &block)
123
+ event_exists?(event_name, true)
124
+ bang_event_name = "#{event_name}!".to_sym
125
+ @instance.send(bang_event_name, *args, &block)
124
126
  end
125
127
 
126
128
  def set_current_state_with_persistence(state)
@@ -129,5 +131,14 @@ module AASM
129
131
  save_success
130
132
  end
131
133
 
134
+ private
135
+
136
+ def event_exists?(event_name, bang = false)
137
+ event = @instance.class.aasm(@name).state_machine.events[event_name.to_sym]
138
+ return true if event
139
+
140
+ event_error = bang ? "#{event_name}!" : event_name
141
+ raise AASM::UndefinedEvent, "Event :#{event_error} doesn't exist" if event.nil?
142
+ end
132
143
  end
133
144
  end
@@ -5,7 +5,7 @@ module AASM
5
5
  list << :"#{i18n_scope(klass)}.events.#{i18n_klass(ancestor)}.#{event}"
6
6
  list
7
7
  end
8
- translate_queue(checklist) || I18n.translate(checklist.shift, :default => event.to_s.humanize)
8
+ translate_queue(checklist) || I18n.translate(checklist.shift, :default => default_display_name(event))
9
9
  end
10
10
 
11
11
  def human_state_name(klass, state)
@@ -14,7 +14,7 @@ module AASM
14
14
  list << item_for(klass, state, ancestor, :old_style => true)
15
15
  list
16
16
  end
17
- translate_queue(checklist) || I18n.translate(checklist.shift, :default => state.to_s.humanize)
17
+ translate_queue(checklist) || I18n.translate(checklist.shift, :default => default_display_name(state))
18
18
  end
19
19
 
20
20
  private
@@ -46,8 +46,18 @@ module AASM
46
46
  end
47
47
 
48
48
  def ancestors_list(klass)
49
+ has_active_record_base = defined?(::ActiveRecord::Base)
49
50
  klass.ancestors.select do |ancestor|
50
- ancestor.respond_to?(:model_name) unless ancestor.name == 'ActiveRecord::Base'
51
+ not_active_record_base = has_active_record_base ? (ancestor != ::ActiveRecord::Base) : true
52
+ ancestor.respond_to?(:model_name) && not_active_record_base
53
+ end
54
+ end
55
+
56
+ def default_display_name(object) # Can use better arguement name
57
+ if object.respond_to?(:default_display_name)
58
+ object.default_display_name
59
+ else
60
+ object.to_s.gsub(/_/, ' ').capitalize
51
61
  end
52
62
  end
53
63
  end
@@ -73,7 +73,7 @@ module AASM
73
73
  rescue LoadError
74
74
  warn <<-MSG
75
75
  [DEPRECATION] :after_commit AASM callback is not safe in terms of race conditions and redundant calls.
76
- Please add `gem 'after_commit_everywhere', '~> 0.1', '>= 0.1.5'` to your Gemfile in order to fix that.
76
+ Please add `gem 'after_commit_everywhere', '~> 1.0'` to your Gemfile in order to fix that.
77
77
  MSG
78
78
  yield
79
79
  end
@@ -59,7 +59,9 @@ module AASM
59
59
  # make sure to create a (named) scope for each state
60
60
  def state_with_scope(*args)
61
61
  names = state_without_scope(*args)
62
- names.each { |name| create_scope(name) if create_scope?(name) }
62
+ names.each do |name|
63
+ create_scopes(name)
64
+ end
63
65
  end
64
66
  alias_method :state_without_scope, :state
65
67
  alias_method :state, :state_with_scope
@@ -71,7 +73,16 @@ module AASM
71
73
  end
72
74
 
73
75
  def create_scope(name)
74
- @klass.aasm_create_scope(@name, name)
76
+ @klass.aasm_create_scope(@name, name) if create_scope?(name)
77
+ end
78
+
79
+ def create_scopes(name)
80
+ if namespace?
81
+ # Create default scopes even when namespace? for backward compatibility
82
+ namepaced_name = "#{namespace}_#{name}"
83
+ create_scope(namepaced_name)
84
+ end
85
+ create_scope(name)
75
86
  end
76
87
  end # Base
77
88
 
data/lib/aasm/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module AASM
2
- VERSION = "5.1.1"
2
+ VERSION = "5.5.0"
3
3
  end
@@ -29,7 +29,7 @@ RUBY
29
29
  end
30
30
 
31
31
  def model_exists?
32
- File.exists?(File.join(destination_root, model_path))
32
+ File.exist?(File.join(destination_root, model_path))
33
33
  end
34
34
 
35
35
  def model_path
@@ -1,4 +1,4 @@
1
- class AASMCreate<%= table_name.camelize %> < ActiveRecord::Migration[<%= ActiveRecord::VERSION::STRING.to_f %>]
1
+ class AasmCreate<%= table_name.camelize %> < ActiveRecord::Migration[<%= ActiveRecord::VERSION::STRING.to_f %>]
2
2
  def change
3
3
  create_table(:<%= table_name %>) do |t|
4
4
  t.string :<%= column_name %>