aasm 4.12.3 → 5.5.0

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 (218) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +284 -119
  3. data/lib/aasm/aasm.rb +30 -27
  4. data/lib/aasm/base.rb +61 -11
  5. data/lib/aasm/configuration.rb +3 -0
  6. data/lib/aasm/core/event.rb +26 -30
  7. data/lib/aasm/core/invoker.rb +129 -0
  8. data/lib/aasm/core/invokers/base_invoker.rb +75 -0
  9. data/lib/aasm/core/invokers/class_invoker.rb +52 -0
  10. data/lib/aasm/core/invokers/literal_invoker.rb +49 -0
  11. data/lib/aasm/core/invokers/proc_invoker.rb +59 -0
  12. data/lib/aasm/core/state.rb +16 -14
  13. data/lib/aasm/core/transition.rb +8 -69
  14. data/lib/aasm/dsl_helper.rb +24 -22
  15. data/lib/aasm/errors.rb +5 -3
  16. data/lib/aasm/instance_base.rb +28 -5
  17. data/lib/aasm/localizer.rb +13 -3
  18. data/lib/aasm/persistence/active_record_persistence.rb +25 -5
  19. data/lib/aasm/persistence/base.rb +14 -3
  20. data/lib/aasm/persistence/core_data_query_persistence.rb +2 -1
  21. data/lib/aasm/persistence/dynamoid_persistence.rb +1 -1
  22. data/lib/aasm/persistence/mongoid_persistence.rb +1 -1
  23. data/lib/aasm/persistence/no_brainer_persistence.rb +105 -0
  24. data/lib/aasm/persistence/orm.rb +23 -19
  25. data/lib/aasm/persistence/plain_persistence.rb +2 -1
  26. data/lib/aasm/persistence/redis_persistence.rb +1 -1
  27. data/lib/aasm/persistence/sequel_persistence.rb +0 -1
  28. data/lib/aasm/persistence.rb +3 -0
  29. data/lib/aasm/rspec/allow_event.rb +5 -1
  30. data/lib/aasm/rspec/allow_transition_to.rb +5 -1
  31. data/lib/aasm/rspec/transition_from.rb +5 -1
  32. data/lib/aasm/version.rb +1 -1
  33. data/lib/aasm.rb +5 -2
  34. data/lib/generators/aasm/orm_helpers.rb +7 -1
  35. data/lib/generators/active_record/aasm_generator.rb +3 -1
  36. data/lib/generators/active_record/templates/migration.rb +1 -1
  37. data/lib/generators/nobrainer/aasm_generator.rb +28 -0
  38. data/lib/motion-aasm.rb +1 -0
  39. metadata +42 -344
  40. data/.document +0 -6
  41. data/.gitignore +0 -20
  42. data/.travis.yml +0 -52
  43. data/API +0 -34
  44. data/Appraisals +0 -43
  45. data/CHANGELOG.md +0 -370
  46. data/CODE_OF_CONDUCT.md +0 -13
  47. data/CONTRIBUTING.md +0 -24
  48. data/Gemfile +0 -7
  49. data/Gemfile.lock_old +0 -151
  50. data/HOWTO +0 -12
  51. data/PLANNED_CHANGES.md +0 -11
  52. data/README_FROM_VERSION_3_TO_4.md +0 -240
  53. data/Rakefile +0 -31
  54. data/TESTING.md +0 -25
  55. data/aasm.gemspec +0 -35
  56. data/callbacks.txt +0 -51
  57. data/gemfiles/rails_3.2.gemfile +0 -13
  58. data/gemfiles/rails_4.0.gemfile +0 -15
  59. data/gemfiles/rails_4.2.gemfile +0 -16
  60. data/gemfiles/rails_4.2_mongoid_5.gemfile +0 -11
  61. data/gemfiles/rails_5.0.gemfile +0 -14
  62. data/spec/database.rb +0 -44
  63. data/spec/database.yml +0 -3
  64. data/spec/en.yml +0 -12
  65. data/spec/en_deprecated_style.yml +0 -10
  66. data/spec/generators/active_record_generator_spec.rb +0 -47
  67. data/spec/generators/mongoid_generator_spec.rb +0 -31
  68. data/spec/models/active_record/basic_active_record_two_state_machines_example.rb +0 -25
  69. data/spec/models/active_record/complex_active_record_example.rb +0 -37
  70. data/spec/models/active_record/derivate_new_dsl.rb +0 -7
  71. data/spec/models/active_record/false_state.rb +0 -35
  72. data/spec/models/active_record/gate.rb +0 -39
  73. data/spec/models/active_record/invalid_persistor.rb +0 -29
  74. data/spec/models/active_record/localizer_test_model.rb +0 -34
  75. data/spec/models/active_record/no_direct_assignment.rb +0 -21
  76. data/spec/models/active_record/no_scope.rb +0 -21
  77. data/spec/models/active_record/persisted_state.rb +0 -12
  78. data/spec/models/active_record/provided_and_persisted_state.rb +0 -24
  79. data/spec/models/active_record/reader.rb +0 -7
  80. data/spec/models/active_record/readme_job.rb +0 -21
  81. data/spec/models/active_record/silent_persistor.rb +0 -29
  82. data/spec/models/active_record/simple_new_dsl.rb +0 -17
  83. data/spec/models/active_record/thief.rb +0 -29
  84. data/spec/models/active_record/transactor.rb +0 -124
  85. data/spec/models/active_record/transient.rb +0 -6
  86. data/spec/models/active_record/validator.rb +0 -118
  87. data/spec/models/active_record/with_enum.rb +0 -39
  88. data/spec/models/active_record/with_enum_without_column.rb +0 -38
  89. data/spec/models/active_record/with_false_enum.rb +0 -31
  90. data/spec/models/active_record/with_true_enum.rb +0 -39
  91. data/spec/models/active_record/worker.rb +0 -2
  92. data/spec/models/active_record/writer.rb +0 -6
  93. data/spec/models/basic_two_state_machines_example.rb +0 -25
  94. data/spec/models/callbacks/basic.rb +0 -98
  95. data/spec/models/callbacks/basic_multiple.rb +0 -75
  96. data/spec/models/callbacks/guard_within_block.rb +0 -67
  97. data/spec/models/callbacks/guard_within_block_multiple.rb +0 -66
  98. data/spec/models/callbacks/multiple_transitions_transition_guard.rb +0 -66
  99. data/spec/models/callbacks/multiple_transitions_transition_guard_multiple.rb +0 -65
  100. data/spec/models/callbacks/private_method.rb +0 -44
  101. data/spec/models/callbacks/private_method_multiple.rb +0 -44
  102. data/spec/models/callbacks/with_args.rb +0 -62
  103. data/spec/models/callbacks/with_args_multiple.rb +0 -61
  104. data/spec/models/callbacks/with_state_arg.rb +0 -30
  105. data/spec/models/callbacks/with_state_arg_multiple.rb +0 -26
  106. data/spec/models/complex_example.rb +0 -222
  107. data/spec/models/conversation.rb +0 -93
  108. data/spec/models/default_state.rb +0 -12
  109. data/spec/models/double_definer.rb +0 -21
  110. data/spec/models/dynamoid/complex_dynamoid_example.rb +0 -37
  111. data/spec/models/dynamoid/dynamoid_multiple.rb +0 -18
  112. data/spec/models/dynamoid/dynamoid_simple.rb +0 -18
  113. data/spec/models/foo.rb +0 -106
  114. data/spec/models/foo_callback_multiple.rb +0 -45
  115. data/spec/models/guard_arguments_check.rb +0 -17
  116. data/spec/models/guard_with_params.rb +0 -24
  117. data/spec/models/guard_with_params_multiple.rb +0 -18
  118. data/spec/models/guardian.rb +0 -58
  119. data/spec/models/guardian_multiple.rb +0 -48
  120. data/spec/models/guardian_without_from_specified.rb +0 -18
  121. data/spec/models/initial_state_proc.rb +0 -31
  122. data/spec/models/mongoid/complex_mongoid_example.rb +0 -37
  123. data/spec/models/mongoid/invalid_persistor_mongoid.rb +0 -39
  124. data/spec/models/mongoid/mongoid_relationships.rb +0 -26
  125. data/spec/models/mongoid/no_scope_mongoid.rb +0 -21
  126. data/spec/models/mongoid/silent_persistor_mongoid.rb +0 -39
  127. data/spec/models/mongoid/simple_mongoid.rb +0 -23
  128. data/spec/models/mongoid/simple_new_dsl_mongoid.rb +0 -25
  129. data/spec/models/mongoid/validator_mongoid.rb +0 -100
  130. data/spec/models/multi_transitioner.rb +0 -34
  131. data/spec/models/multiple_transitions_that_differ_only_by_guard.rb +0 -31
  132. data/spec/models/namespaced_multiple_example.rb +0 -42
  133. data/spec/models/no_initial_state.rb +0 -25
  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 -15
  153. data/spec/models/simple_multiple_example.rb +0 -42
  154. data/spec/models/state_machine_with_failed_event.rb +0 -20
  155. data/spec/models/states_on_one_line_example.rb +0 -8
  156. data/spec/models/sub_class.rb +0 -41
  157. data/spec/models/sub_class_with_more_states.rb +0 -18
  158. data/spec/models/sub_classing.rb +0 -3
  159. data/spec/models/super_class.rb +0 -46
  160. data/spec/models/this_name_better_not_be_in_use.rb +0 -11
  161. data/spec/models/valid_state_name.rb +0 -23
  162. data/spec/spec_helper.rb +0 -26
  163. data/spec/spec_helpers/active_record.rb +0 -7
  164. data/spec/spec_helpers/dynamoid.rb +0 -33
  165. data/spec/spec_helpers/mongoid.rb +0 -7
  166. data/spec/spec_helpers/redis.rb +0 -15
  167. data/spec/spec_helpers/remove_warnings.rb +0 -1
  168. data/spec/spec_helpers/sequel.rb +0 -7
  169. data/spec/unit/api_spec.rb +0 -100
  170. data/spec/unit/basic_two_state_machines_example_spec.rb +0 -10
  171. data/spec/unit/callback_multiple_spec.rb +0 -300
  172. data/spec/unit/callbacks_spec.rb +0 -491
  173. data/spec/unit/complex_example_spec.rb +0 -94
  174. data/spec/unit/complex_multiple_example_spec.rb +0 -115
  175. data/spec/unit/edge_cases_spec.rb +0 -16
  176. data/spec/unit/event_multiple_spec.rb +0 -73
  177. data/spec/unit/event_naming_spec.rb +0 -16
  178. data/spec/unit/event_spec.rb +0 -381
  179. data/spec/unit/exception_spec.rb +0 -11
  180. data/spec/unit/guard_arguments_check_spec.rb +0 -9
  181. data/spec/unit/guard_multiple_spec.rb +0 -60
  182. data/spec/unit/guard_spec.rb +0 -89
  183. data/spec/unit/guard_with_params_multiple_spec.rb +0 -10
  184. data/spec/unit/guard_with_params_spec.rb +0 -14
  185. data/spec/unit/guard_without_from_specified_spec.rb +0 -10
  186. data/spec/unit/initial_state_multiple_spec.rb +0 -15
  187. data/spec/unit/initial_state_spec.rb +0 -12
  188. data/spec/unit/inspection_multiple_spec.rb +0 -201
  189. data/spec/unit/inspection_spec.rb +0 -149
  190. data/spec/unit/localizer_spec.rb +0 -78
  191. data/spec/unit/memory_leak_spec.rb +0 -38
  192. data/spec/unit/multiple_transitions_that_differ_only_by_guard_spec.rb +0 -14
  193. data/spec/unit/namespaced_multiple_example_spec.rb +0 -75
  194. data/spec/unit/new_dsl_spec.rb +0 -12
  195. data/spec/unit/override_warning_spec.rb +0 -94
  196. data/spec/unit/persistence/active_record_persistence_multiple_spec.rb +0 -618
  197. data/spec/unit/persistence/active_record_persistence_spec.rb +0 -735
  198. data/spec/unit/persistence/dynamoid_persistence_multiple_spec.rb +0 -135
  199. data/spec/unit/persistence/dynamoid_persistence_spec.rb +0 -84
  200. data/spec/unit/persistence/mongoid_persistence_multiple_spec.rb +0 -204
  201. data/spec/unit/persistence/mongoid_persistence_spec.rb +0 -169
  202. data/spec/unit/persistence/redis_persistence_multiple_spec.rb +0 -88
  203. data/spec/unit/persistence/redis_persistence_spec.rb +0 -53
  204. data/spec/unit/persistence/sequel_persistence_multiple_spec.rb +0 -148
  205. data/spec/unit/persistence/sequel_persistence_spec.rb +0 -368
  206. data/spec/unit/readme_spec.rb +0 -41
  207. data/spec/unit/reloading_spec.rb +0 -15
  208. data/spec/unit/rspec_matcher_spec.rb +0 -79
  209. data/spec/unit/simple_custom_example_spec.rb +0 -39
  210. data/spec/unit/simple_example_spec.rb +0 -42
  211. data/spec/unit/simple_multiple_example_spec.rb +0 -91
  212. data/spec/unit/state_spec.rb +0 -89
  213. data/spec/unit/states_on_one_line_example_spec.rb +0 -16
  214. data/spec/unit/subclassing_multiple_spec.rb +0 -74
  215. data/spec/unit/subclassing_spec.rb +0 -46
  216. data/spec/unit/transition_spec.rb +0 -436
  217. data/test/minitest_helper.rb +0 -57
  218. data/test/unit/minitest_matcher_test.rb +0 -80
data/lib/aasm/aasm.rb CHANGED
@@ -99,25 +99,10 @@ private
99
99
  begin
100
100
  old_state = aasm(state_machine_name).state_object_for_name(aasm(state_machine_name).current_state)
101
101
 
102
- event.fire_global_callbacks(
103
- :before_all_events,
104
- self,
105
- *process_args(event, aasm(state_machine_name).current_state, *args)
106
- )
107
-
108
- # new event before callback
109
- event.fire_callbacks(
110
- :before,
111
- self,
112
- *process_args(event, aasm(state_machine_name).current_state, *args)
113
- )
102
+ fire_default_callbacks(event, *process_args(event, aasm(state_machine_name).current_state, *args))
114
103
 
115
104
  if may_fire_to = event.may_fire?(self, *args)
116
- old_state.fire_callbacks(:before_exit, self,
117
- *process_args(event, aasm(state_machine_name).current_state, *args))
118
- old_state.fire_callbacks(:exit, self,
119
- *process_args(event, aasm(state_machine_name).current_state, *args))
120
-
105
+ fire_exit_callbacks(old_state, *process_args(event, aasm(state_machine_name).current_state, *args))
121
106
  if new_state_name = event.fire(self, {:may_fire => may_fire_to}, *args)
122
107
  aasm_fired(state_machine_name, event, old_state, new_state_name, options, *args, &block)
123
108
  else
@@ -130,31 +115,51 @@ private
130
115
  event.fire_callbacks(:error, self, e, *process_args(event, aasm(state_machine_name).current_state, *args)) ||
131
116
  event.fire_global_callbacks(:error_on_all_events, self, e, *process_args(event, aasm(state_machine_name).current_state, *args)) ||
132
117
  raise(e)
118
+ false
133
119
  ensure
134
120
  event.fire_callbacks(:ensure, self, *process_args(event, aasm(state_machine_name).current_state, *args))
135
121
  event.fire_global_callbacks(:ensure_on_all_events, self, *process_args(event, aasm(state_machine_name).current_state, *args))
136
122
  end
137
123
  end
138
124
 
125
+ def fire_default_callbacks(event, *processed_args)
126
+ event.fire_global_callbacks(
127
+ :before_all_events,
128
+ self,
129
+ *processed_args
130
+ )
131
+
132
+ # new event before callback
133
+ event.fire_callbacks(
134
+ :before,
135
+ self,
136
+ *processed_args
137
+ )
138
+ end
139
+
140
+ def fire_exit_callbacks(old_state, *processed_args)
141
+ old_state.fire_callbacks(:before_exit, self, *processed_args)
142
+ old_state.fire_callbacks(:exit, self, *processed_args)
143
+ end
144
+
139
145
  def aasm_fired(state_machine_name, event, old_state, new_state_name, options, *args)
140
146
  persist = options[:persist]
141
147
 
142
148
  new_state = aasm(state_machine_name).state_object_for_name(new_state_name)
149
+ callback_args = process_args(event, aasm(state_machine_name).current_state, *args)
143
150
 
144
- new_state.fire_callbacks(:before_enter, self,
145
- *process_args(event, aasm(state_machine_name).current_state, *args))
151
+ new_state.fire_callbacks(:before_enter, self, *callback_args)
146
152
 
147
- new_state.fire_callbacks(:enter, self,
148
- *process_args(event, aasm(state_machine_name).current_state, *args)) # TODO: remove for AASM 4?
153
+ new_state.fire_callbacks(:enter, self, *callback_args) # TODO: remove for AASM 4?
149
154
 
150
155
  persist_successful = true
151
156
  if persist
152
157
  persist_successful = aasm(state_machine_name).set_current_state_with_persistence(new_state_name)
153
158
  if persist_successful
154
159
  yield if block_given?
155
- event.fire_callbacks(:before_success, self)
160
+ event.fire_callbacks(:before_success, self, *callback_args)
156
161
  event.fire_transition_callbacks(self, *process_args(event, old_state.name, *args))
157
- event.fire_callbacks(:success, self)
162
+ event.fire_callbacks(:success, self, *callback_args)
158
163
  end
159
164
  else
160
165
  aasm(state_machine_name).current_state = new_state_name
@@ -167,10 +172,8 @@ private
167
172
  end
168
173
 
169
174
  if persist_successful
170
- old_state.fire_callbacks(:after_exit, self,
171
- *process_args(event, aasm(state_machine_name).current_state, *args))
172
- new_state.fire_callbacks(:after_enter, self,
173
- *process_args(event, aasm(state_machine_name).current_state, *args))
175
+ old_state.fire_callbacks(:after_exit, self, *callback_args)
176
+ new_state.fire_callbacks(:after_enter, self, *callback_args)
174
177
  event.fire_callbacks(
175
178
  :after,
176
179
  self,
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,18 +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
- klass.send :define_method, "#{@state_machine.config.column}=", ->(state_name) do
58
- if self.class.aasm(:"#{aasm_name}").state_machine.config.no_direct_assignment
59
- raise AASM::NoDirectAssignmentError.new(
60
- 'direct assignment of AASM column has been disabled (see AASM configuration for this class)'
61
- )
62
- else
63
- super(state_name)
64
- end
65
- end
62
+ setup_no_direct_assignment(@name)
66
63
  end
67
64
 
68
65
  # This method is both a getter and a setter
@@ -134,6 +131,8 @@ module AASM
134
131
  aasm_fire_event(aasm_name, event, {:persist => false}, *args, &block)
135
132
  end
136
133
 
134
+ skip_instance_level_validation(event, name, aasm_name, klass)
135
+
137
136
  # Create aliases for the event methods. Keep the old names to maintain backwards compatibility.
138
137
  if namespace?
139
138
  klass.send(:alias_method, "may_#{name}_#{namespace}?", "may_#{name}?")
@@ -224,7 +223,20 @@ module AASM
224
223
  end
225
224
  end
226
225
 
227
- 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
228
240
  end
229
241
 
230
242
  def namespace?
@@ -249,5 +261,43 @@ module AASM
249
261
  end
250
262
  end
251
263
 
264
+ def skip_instance_level_validation(event, name, aasm_name, klass)
265
+ # Overrides the skip_validation config for an instance (If skip validation is set to false in original config) and
266
+ # restores it back to the original value after the event is fired.
267
+ safely_define_method klass, "#{name}_without_validation!", ->(*args, &block) do
268
+ original_config = AASM::StateMachineStore.fetch(self.class, true).machine(aasm_name).config.skip_validation_on_save
269
+ begin
270
+ AASM::StateMachineStore.fetch(self.class, true).machine(aasm_name).config.skip_validation_on_save = true unless original_config
271
+ aasm(aasm_name).current_event = :"#{name}!"
272
+ aasm_fire_event(aasm_name, event, {:persist => true}, *args, &block)
273
+ ensure
274
+ AASM::StateMachineStore.fetch(self.class, true).machine(aasm_name).config.skip_validation_on_save = original_config
275
+ end
276
+ end
277
+ end
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
+
252
302
  end
253
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
 
@@ -1,16 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AASM::Core
2
4
  class Event
3
- include DslHelper
5
+ include AASM::DslHelper
4
6
 
5
- attr_reader :name, :state_machine, :options
7
+ attr_reader :name, :state_machine, :options, :default_display_name
6
8
 
7
9
  def initialize(name, state_machine, options = {}, &block)
8
10
  @name = name
9
11
  @state_machine = state_machine
10
12
  @transitions = []
11
- @valid_transitions = {}
13
+ @valid_transitions = Hash.new { |h, k| h[k] = {} }
12
14
  @guards = Array(options[:guard] || options[:guards] || options[:if])
13
15
  @unless = Array(options[:unless]) #TODO: This could use a better name
16
+ @default_display_name = name.to_s.gsub(/_/, ' ').capitalize
14
17
 
15
18
  # from aasm4
16
19
  @options = options # QUESTION: .dup ?
@@ -76,8 +79,9 @@ module AASM::Core
76
79
 
77
80
  def fire_transition_callbacks(obj, *args)
78
81
  from_state = obj.aasm(state_machine.name).current_state
79
- transition = @valid_transitions[from_state]
80
- @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)
81
85
  end
82
86
 
83
87
  def ==(event)
@@ -107,6 +111,10 @@ module AASM::Core
107
111
  transitions.flat_map(&:failures)
108
112
  end
109
113
 
114
+ def to_s
115
+ name.to_s
116
+ end
117
+
110
118
  private
111
119
 
112
120
  def attach_event_guards(definitions)
@@ -123,18 +131,19 @@ module AASM::Core
123
131
 
124
132
  def _fire(obj, options={}, to_state=::AASM::NO_VALUE, *args)
125
133
  result = options[:test_only] ? false : nil
134
+ clear_failed_callbacks
126
135
  transitions = @transitions.select { |t| t.from == obj.aasm(state_machine.name).current_state || t.from == nil}
127
136
  return result if transitions.size == 0
128
137
 
129
138
  if to_state == ::AASM::NO_VALUE
130
139
  to_state = nil
131
- elsif to_state.respond_to?(:to_sym) && transitions.map(&:to).flatten.include?(to_state.to_sym)
132
- # nop, to_state is a valid to-state
133
- else
140
+ elsif !(to_state.respond_to?(:to_sym) && transitions.map(&:to).flatten.include?(to_state.to_sym))
134
141
  # to_state is an argument
135
142
  args.unshift(to_state)
136
143
  to_state = nil
137
144
  end
145
+
146
+ # nop, to_state is a valid to-state
138
147
 
139
148
  transitions.each do |transition|
140
149
  next if to_state and !Array(transition.to).include?(to_state)
@@ -145,7 +154,7 @@ module AASM::Core
145
154
  result = transition
146
155
  else
147
156
  result = to_state || Array(transition.to).first
148
- Array(transition.to).each {|to| @valid_transitions[to] = transition }
157
+ Array(transition.to).each {|to| @valid_transitions[obj.object_id][to] = transition }
149
158
  transition.execute(obj, *args)
150
159
  end
151
160
 
@@ -155,28 +164,15 @@ module AASM::Core
155
164
  result
156
165
  end
157
166
 
158
- def invoke_callbacks(code, record, args)
159
- case code
160
- when Symbol, String
161
- unless record.respond_to?(code, true)
162
- raise NoMethodError.new("NoMethodError: undefined method `#{code}' for #{record.inspect}:#{record.class}")
163
- end
164
- arity = record.__send__(:method, code.to_sym).arity
165
- record.__send__(code, *(arity < 0 ? args : args[0...arity]))
166
- true
167
-
168
- when Proc
169
- arity = code.arity
170
- record.instance_exec(*(arity < 0 ? args : args[0...arity]), &code)
171
- true
172
-
173
- when Array
174
- code.each {|a| invoke_callbacks(a, record, args)}
175
- true
167
+ def clear_failed_callbacks
168
+ # https://github.com/aasm/aasm/issues/383, https://github.com/aasm/aasm/issues/599
169
+ transitions.each { |transition| transition.failures.clear }
170
+ end
176
171
 
177
- else
178
- false
179
- end
172
+ def invoke_callbacks(code, record, args)
173
+ Invoker.new(code, record, args)
174
+ .with_default_return_value(false)
175
+ .invoke
180
176
  end
181
177
  end
182
178
  end # AASM
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AASM
4
+ module Core
5
+ ##
6
+ # main invoker class which encapsulates the logic
7
+ # for invoking literal-based, proc-based, class-based
8
+ # and array-based callbacks for different entities.
9
+ class Invoker
10
+ DEFAULT_RETURN_VALUE = true
11
+
12
+ ##
13
+ # Initialize a new invoker instance.
14
+ # NOTE that invoker must be used per-subject/record
15
+ # (one instance per subject/record)
16
+ #
17
+ # ==Options:
18
+ #
19
+ # +subject+ - invoking subject, may be Proc,
20
+ # Class, String, Symbol or Array
21
+ # +record+ - invoking record
22
+ # +args+ - arguments which will be passed to the callback
23
+
24
+ def initialize(subject, record, args)
25
+ @subject = subject
26
+ @record = record
27
+ @args = args
28
+ @options = {}
29
+ @failures = []
30
+ @default_return_value = DEFAULT_RETURN_VALUE
31
+ end
32
+
33
+ ##
34
+ # Pass additional options to concrete invoker
35
+ #
36
+ # ==Options:
37
+ #
38
+ # +options+ - hash of options which will be passed to
39
+ # concrete invokers
40
+ #
41
+ # ==Example:
42
+ #
43
+ # with_options(guard: proc {...})
44
+
45
+ def with_options(options)
46
+ @options = options
47
+ self
48
+ end
49
+
50
+ ##
51
+ # Collect failures to a specified buffer
52
+ #
53
+ # ==Options:
54
+ #
55
+ # +failures+ - failures buffer to collect failures
56
+
57
+ def with_failures(failures)
58
+ @failures = failures
59
+ self
60
+ end
61
+
62
+ ##
63
+ # Change default return value of #invoke method
64
+ # if none of invokers processed the request.
65
+ #
66
+ # The default return value is #DEFAULT_RETURN_VALUE
67
+ #
68
+ # ==Options:
69
+ #
70
+ # +value+ - default return value for #invoke method
71
+
72
+ def with_default_return_value(value)
73
+ @default_return_value = value
74
+ self
75
+ end
76
+
77
+ ##
78
+ # Find concrete invoker for specified subject and invoker it,
79
+ # or return default value set by #DEFAULT_RETURN_VALUE or
80
+ # overridden by #with_default_return_value
81
+
82
+ # rubocop:disable Metrics/AbcSize
83
+ def invoke
84
+ return invoke_array if subject.is_a?(Array)
85
+ return literal_invoker.invoke if literal_invoker.may_invoke?
86
+ return proc_invoker.invoke if proc_invoker.may_invoke?
87
+ return class_invoker.invoke if class_invoker.may_invoke?
88
+ default_return_value
89
+ end
90
+ # rubocop:enable Metrics/AbcSize
91
+
92
+ private
93
+
94
+ attr_reader :subject, :record, :args, :options, :failures,
95
+ :default_return_value
96
+
97
+ def invoke_array
98
+ return subject.all? { |item| sub_invoke(item) } if options[:guard]
99
+ return subject.all? { |item| !sub_invoke(item) } if options[:unless]
100
+ subject.map { |item| sub_invoke(item) }
101
+ end
102
+
103
+ def sub_invoke(new_subject)
104
+ self.class.new(new_subject, record, args)
105
+ .with_failures(failures)
106
+ .with_options(options)
107
+ .invoke
108
+ end
109
+
110
+ def proc_invoker
111
+ @proc_invoker ||= Invokers::ProcInvoker
112
+ .new(subject, record, args)
113
+ .with_failures(failures)
114
+ end
115
+
116
+ def class_invoker
117
+ @class_invoker ||= Invokers::ClassInvoker
118
+ .new(subject, record, args)
119
+ .with_failures(failures)
120
+ end
121
+
122
+ def literal_invoker
123
+ @literal_invoker ||= Invokers::LiteralInvoker
124
+ .new(subject, record, args)
125
+ .with_failures(failures)
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AASM
4
+ module Core
5
+ module Invokers
6
+ ##
7
+ # Base concrete invoker class which contain basic
8
+ # invoking and logging definitions
9
+ class BaseInvoker
10
+ attr_reader :failures, :subject, :record, :args, :result
11
+
12
+ ##
13
+ # Initialize a new concrete invoker instance.
14
+ # NOTE that concrete invoker must be used per-subject/record
15
+ # (one instance per subject/record)
16
+ #
17
+ # ==Options:
18
+ #
19
+ # +subject+ - invoking subject comparable with this invoker
20
+ # +record+ - invoking record
21
+ # +args+ - arguments which will be passed to the callback
22
+
23
+ def initialize(subject, record, args)
24
+ @subject = subject
25
+ @record = record
26
+ @args = args
27
+ @result = false
28
+ @failures = []
29
+ end
30
+
31
+ ##
32
+ # Collect failures to a specified buffer
33
+ #
34
+ # ==Options:
35
+ #
36
+ # +failures+ - failures buffer to collect failures
37
+
38
+ def with_failures(failures_buffer)
39
+ @failures = failures_buffer
40
+ self
41
+ end
42
+
43
+ ##
44
+ # Execute concrete invoker, log the error and return result
45
+
46
+ def invoke
47
+ return unless may_invoke?
48
+ log_failure unless invoke_subject
49
+ result
50
+ end
51
+
52
+ ##
53
+ # Check if concrete invoker may be invoked for a specified subject
54
+
55
+ def may_invoke?
56
+ raise NoMethodError, '"#may_invoke?" is not implemented'
57
+ end
58
+
59
+ ##
60
+ # Log failed invoking
61
+
62
+ def log_failure
63
+ raise NoMethodError, '"#log_failure" is not implemented'
64
+ end
65
+
66
+ ##
67
+ # Execute concrete invoker
68
+
69
+ def invoke_subject
70
+ raise NoMethodError, '"#invoke_subject" is not implemented'
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AASM
4
+ module Core
5
+ module Invokers
6
+ ##
7
+ # Class invoker which allows to use classes which respond to #call
8
+ # to be used as state/event/transition callbacks.
9
+ class ClassInvoker < BaseInvoker
10
+ def may_invoke?
11
+ subject.is_a?(Class) && subject.instance_methods.include?(:call)
12
+ end
13
+
14
+ def log_failure
15
+ return log_source_location if Method.method_defined?(:source_location)
16
+ log_method_info
17
+ end
18
+
19
+ def invoke_subject
20
+ @result = instance.call
21
+ end
22
+
23
+ private
24
+
25
+ def log_source_location
26
+ failures << instance.method(:call).source_location.join('#')
27
+ end
28
+
29
+ def log_method_info
30
+ failures << instance.method(:call)
31
+ end
32
+
33
+ def instance
34
+ @instance ||= retrieve_instance
35
+ end
36
+
37
+ # rubocop:disable Metrics/AbcSize
38
+ def retrieve_instance
39
+ return subject.new if subject_arity.zero?
40
+ return subject.new(record) if subject_arity == 1
41
+ return subject.new(record, *args) if subject_arity < 0
42
+ subject.new(record, *args[0..(subject_arity - 2)])
43
+ end
44
+ # rubocop:enable Metrics/AbcSize
45
+
46
+ def subject_arity
47
+ @arity ||= subject.instance_method(:initialize).arity
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AASM
4
+ module Core
5
+ module Invokers
6
+ ##
7
+ # Literal invoker which allows to use strings or symbols to call
8
+ # record methods as state/event/transition callbacks.
9
+ class LiteralInvoker < BaseInvoker
10
+ def may_invoke?
11
+ subject.is_a?(String) || subject.is_a?(Symbol)
12
+ end
13
+
14
+ def log_failure
15
+ failures << subject
16
+ end
17
+
18
+ def invoke_subject
19
+ @result = exec_subject
20
+ end
21
+
22
+ private
23
+
24
+ def subject_arity
25
+ @arity ||= record.__send__(:method, subject.to_sym).arity
26
+ end
27
+
28
+ # rubocop:disable Metrics/AbcSize
29
+ def exec_subject
30
+ raise(*record_error) unless record.respond_to?(subject, true)
31
+ return record.__send__(subject) if subject_arity.zero?
32
+ return record.__send__(subject, *args) if subject_arity < 0
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)
36
+ end
37
+ # rubocop:enable Metrics/AbcSize
38
+
39
+ def record_error
40
+ [
41
+ NoMethodError,
42
+ 'NoMethodError: undefined method ' \
43
+ "`#{subject}' for #{record.inspect}:#{record.class}"
44
+ ]
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AASM
4
+ module Core
5
+ module Invokers
6
+ ##
7
+ # Proc invoker which allows to use Procs as
8
+ # state/event/transition callbacks.
9
+ class ProcInvoker < BaseInvoker
10
+ def may_invoke?
11
+ subject.is_a?(Proc)
12
+ end
13
+
14
+ def log_failure
15
+ return log_source_location if Method.method_defined?(:source_location)
16
+ log_proc_info
17
+ end
18
+
19
+ def invoke_subject
20
+ @result = if support_parameters?
21
+ exec_proc(parameters_to_arity)
22
+ else
23
+ exec_proc(subject.arity)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def support_parameters?
30
+ subject.respond_to?(:parameters)
31
+ end
32
+
33
+ # rubocop:disable Metrics/AbcSize
34
+ def exec_proc(parameters_size)
35
+ return record.instance_exec(&subject) if parameters_size.zero?
36
+ return record.instance_exec(*args, &subject) if parameters_size < 0
37
+ record.instance_exec(*args[0..(parameters_size - 1)], &subject)
38
+ end
39
+ # rubocop:enable Metrics/AbcSize
40
+
41
+ def log_source_location
42
+ failures << subject.source_location.join('#')
43
+ end
44
+
45
+ def log_proc_info
46
+ failures << subject
47
+ end
48
+
49
+ def parameters_to_arity
50
+ subject.parameters.inject(0) do |memo, parameter|
51
+ memo += 1
52
+ memo *= -1 if parameter[0] == :rest && memo > 0
53
+ memo
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end