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.
- checksums.yaml +5 -5
- data/README.md +284 -119
- data/lib/aasm/aasm.rb +30 -27
- data/lib/aasm/base.rb +61 -11
- data/lib/aasm/configuration.rb +3 -0
- data/lib/aasm/core/event.rb +26 -30
- data/lib/aasm/core/invoker.rb +129 -0
- data/lib/aasm/core/invokers/base_invoker.rb +75 -0
- data/lib/aasm/core/invokers/class_invoker.rb +52 -0
- data/lib/aasm/core/invokers/literal_invoker.rb +49 -0
- data/lib/aasm/core/invokers/proc_invoker.rb +59 -0
- data/lib/aasm/core/state.rb +16 -14
- data/lib/aasm/core/transition.rb +8 -69
- data/lib/aasm/dsl_helper.rb +24 -22
- data/lib/aasm/errors.rb +5 -3
- data/lib/aasm/instance_base.rb +28 -5
- data/lib/aasm/localizer.rb +13 -3
- data/lib/aasm/persistence/active_record_persistence.rb +25 -5
- data/lib/aasm/persistence/base.rb +14 -3
- data/lib/aasm/persistence/core_data_query_persistence.rb +2 -1
- data/lib/aasm/persistence/dynamoid_persistence.rb +1 -1
- data/lib/aasm/persistence/mongoid_persistence.rb +1 -1
- data/lib/aasm/persistence/no_brainer_persistence.rb +105 -0
- data/lib/aasm/persistence/orm.rb +23 -19
- data/lib/aasm/persistence/plain_persistence.rb +2 -1
- data/lib/aasm/persistence/redis_persistence.rb +1 -1
- data/lib/aasm/persistence/sequel_persistence.rb +0 -1
- data/lib/aasm/persistence.rb +3 -0
- data/lib/aasm/rspec/allow_event.rb +5 -1
- data/lib/aasm/rspec/allow_transition_to.rb +5 -1
- data/lib/aasm/rspec/transition_from.rb +5 -1
- data/lib/aasm/version.rb +1 -1
- data/lib/aasm.rb +5 -2
- data/lib/generators/aasm/orm_helpers.rb +7 -1
- data/lib/generators/active_record/aasm_generator.rb +3 -1
- data/lib/generators/active_record/templates/migration.rb +1 -1
- data/lib/generators/nobrainer/aasm_generator.rb +28 -0
- data/lib/motion-aasm.rb +1 -0
- metadata +42 -344
- data/.document +0 -6
- data/.gitignore +0 -20
- data/.travis.yml +0 -52
- data/API +0 -34
- data/Appraisals +0 -43
- data/CHANGELOG.md +0 -370
- data/CODE_OF_CONDUCT.md +0 -13
- data/CONTRIBUTING.md +0 -24
- data/Gemfile +0 -7
- data/Gemfile.lock_old +0 -151
- data/HOWTO +0 -12
- data/PLANNED_CHANGES.md +0 -11
- data/README_FROM_VERSION_3_TO_4.md +0 -240
- data/Rakefile +0 -31
- data/TESTING.md +0 -25
- data/aasm.gemspec +0 -35
- data/callbacks.txt +0 -51
- data/gemfiles/rails_3.2.gemfile +0 -13
- data/gemfiles/rails_4.0.gemfile +0 -15
- data/gemfiles/rails_4.2.gemfile +0 -16
- data/gemfiles/rails_4.2_mongoid_5.gemfile +0 -11
- data/gemfiles/rails_5.0.gemfile +0 -14
- data/spec/database.rb +0 -44
- data/spec/database.yml +0 -3
- data/spec/en.yml +0 -12
- data/spec/en_deprecated_style.yml +0 -10
- data/spec/generators/active_record_generator_spec.rb +0 -47
- data/spec/generators/mongoid_generator_spec.rb +0 -31
- data/spec/models/active_record/basic_active_record_two_state_machines_example.rb +0 -25
- data/spec/models/active_record/complex_active_record_example.rb +0 -37
- data/spec/models/active_record/derivate_new_dsl.rb +0 -7
- data/spec/models/active_record/false_state.rb +0 -35
- data/spec/models/active_record/gate.rb +0 -39
- data/spec/models/active_record/invalid_persistor.rb +0 -29
- data/spec/models/active_record/localizer_test_model.rb +0 -34
- data/spec/models/active_record/no_direct_assignment.rb +0 -21
- data/spec/models/active_record/no_scope.rb +0 -21
- data/spec/models/active_record/persisted_state.rb +0 -12
- data/spec/models/active_record/provided_and_persisted_state.rb +0 -24
- data/spec/models/active_record/reader.rb +0 -7
- data/spec/models/active_record/readme_job.rb +0 -21
- data/spec/models/active_record/silent_persistor.rb +0 -29
- data/spec/models/active_record/simple_new_dsl.rb +0 -17
- data/spec/models/active_record/thief.rb +0 -29
- data/spec/models/active_record/transactor.rb +0 -124
- data/spec/models/active_record/transient.rb +0 -6
- data/spec/models/active_record/validator.rb +0 -118
- data/spec/models/active_record/with_enum.rb +0 -39
- data/spec/models/active_record/with_enum_without_column.rb +0 -38
- data/spec/models/active_record/with_false_enum.rb +0 -31
- data/spec/models/active_record/with_true_enum.rb +0 -39
- data/spec/models/active_record/worker.rb +0 -2
- data/spec/models/active_record/writer.rb +0 -6
- data/spec/models/basic_two_state_machines_example.rb +0 -25
- data/spec/models/callbacks/basic.rb +0 -98
- data/spec/models/callbacks/basic_multiple.rb +0 -75
- data/spec/models/callbacks/guard_within_block.rb +0 -67
- data/spec/models/callbacks/guard_within_block_multiple.rb +0 -66
- data/spec/models/callbacks/multiple_transitions_transition_guard.rb +0 -66
- data/spec/models/callbacks/multiple_transitions_transition_guard_multiple.rb +0 -65
- data/spec/models/callbacks/private_method.rb +0 -44
- data/spec/models/callbacks/private_method_multiple.rb +0 -44
- data/spec/models/callbacks/with_args.rb +0 -62
- data/spec/models/callbacks/with_args_multiple.rb +0 -61
- data/spec/models/callbacks/with_state_arg.rb +0 -30
- data/spec/models/callbacks/with_state_arg_multiple.rb +0 -26
- data/spec/models/complex_example.rb +0 -222
- data/spec/models/conversation.rb +0 -93
- data/spec/models/default_state.rb +0 -12
- data/spec/models/double_definer.rb +0 -21
- data/spec/models/dynamoid/complex_dynamoid_example.rb +0 -37
- data/spec/models/dynamoid/dynamoid_multiple.rb +0 -18
- data/spec/models/dynamoid/dynamoid_simple.rb +0 -18
- data/spec/models/foo.rb +0 -106
- data/spec/models/foo_callback_multiple.rb +0 -45
- data/spec/models/guard_arguments_check.rb +0 -17
- data/spec/models/guard_with_params.rb +0 -24
- data/spec/models/guard_with_params_multiple.rb +0 -18
- data/spec/models/guardian.rb +0 -58
- data/spec/models/guardian_multiple.rb +0 -48
- data/spec/models/guardian_without_from_specified.rb +0 -18
- data/spec/models/initial_state_proc.rb +0 -31
- data/spec/models/mongoid/complex_mongoid_example.rb +0 -37
- data/spec/models/mongoid/invalid_persistor_mongoid.rb +0 -39
- data/spec/models/mongoid/mongoid_relationships.rb +0 -26
- data/spec/models/mongoid/no_scope_mongoid.rb +0 -21
- data/spec/models/mongoid/silent_persistor_mongoid.rb +0 -39
- data/spec/models/mongoid/simple_mongoid.rb +0 -23
- data/spec/models/mongoid/simple_new_dsl_mongoid.rb +0 -25
- data/spec/models/mongoid/validator_mongoid.rb +0 -100
- data/spec/models/multi_transitioner.rb +0 -34
- data/spec/models/multiple_transitions_that_differ_only_by_guard.rb +0 -31
- data/spec/models/namespaced_multiple_example.rb +0 -42
- data/spec/models/no_initial_state.rb +0 -25
- data/spec/models/not_auto_loaded/process.rb +0 -21
- data/spec/models/parametrised_event.rb +0 -42
- data/spec/models/parametrised_event_multiple.rb +0 -29
- data/spec/models/process_with_new_dsl.rb +0 -31
- data/spec/models/provided_state.rb +0 -24
- data/spec/models/redis/complex_redis_example.rb +0 -40
- data/spec/models/redis/redis_multiple.rb +0 -20
- data/spec/models/redis/redis_simple.rb +0 -20
- data/spec/models/sequel/complex_sequel_example.rb +0 -46
- data/spec/models/sequel/invalid_persistor.rb +0 -52
- data/spec/models/sequel/sequel_multiple.rb +0 -25
- data/spec/models/sequel/sequel_simple.rb +0 -26
- data/spec/models/sequel/silent_persistor.rb +0 -50
- data/spec/models/sequel/transactor.rb +0 -112
- data/spec/models/sequel/validator.rb +0 -93
- data/spec/models/sequel/worker.rb +0 -12
- data/spec/models/silencer.rb +0 -27
- data/spec/models/simple_custom_example.rb +0 -53
- data/spec/models/simple_example.rb +0 -15
- data/spec/models/simple_multiple_example.rb +0 -42
- data/spec/models/state_machine_with_failed_event.rb +0 -20
- data/spec/models/states_on_one_line_example.rb +0 -8
- data/spec/models/sub_class.rb +0 -41
- data/spec/models/sub_class_with_more_states.rb +0 -18
- data/spec/models/sub_classing.rb +0 -3
- data/spec/models/super_class.rb +0 -46
- data/spec/models/this_name_better_not_be_in_use.rb +0 -11
- data/spec/models/valid_state_name.rb +0 -23
- data/spec/spec_helper.rb +0 -26
- data/spec/spec_helpers/active_record.rb +0 -7
- data/spec/spec_helpers/dynamoid.rb +0 -33
- data/spec/spec_helpers/mongoid.rb +0 -7
- data/spec/spec_helpers/redis.rb +0 -15
- data/spec/spec_helpers/remove_warnings.rb +0 -1
- data/spec/spec_helpers/sequel.rb +0 -7
- data/spec/unit/api_spec.rb +0 -100
- data/spec/unit/basic_two_state_machines_example_spec.rb +0 -10
- data/spec/unit/callback_multiple_spec.rb +0 -300
- data/spec/unit/callbacks_spec.rb +0 -491
- data/spec/unit/complex_example_spec.rb +0 -94
- data/spec/unit/complex_multiple_example_spec.rb +0 -115
- data/spec/unit/edge_cases_spec.rb +0 -16
- data/spec/unit/event_multiple_spec.rb +0 -73
- data/spec/unit/event_naming_spec.rb +0 -16
- data/spec/unit/event_spec.rb +0 -381
- data/spec/unit/exception_spec.rb +0 -11
- data/spec/unit/guard_arguments_check_spec.rb +0 -9
- data/spec/unit/guard_multiple_spec.rb +0 -60
- data/spec/unit/guard_spec.rb +0 -89
- data/spec/unit/guard_with_params_multiple_spec.rb +0 -10
- data/spec/unit/guard_with_params_spec.rb +0 -14
- data/spec/unit/guard_without_from_specified_spec.rb +0 -10
- data/spec/unit/initial_state_multiple_spec.rb +0 -15
- data/spec/unit/initial_state_spec.rb +0 -12
- data/spec/unit/inspection_multiple_spec.rb +0 -201
- data/spec/unit/inspection_spec.rb +0 -149
- data/spec/unit/localizer_spec.rb +0 -78
- data/spec/unit/memory_leak_spec.rb +0 -38
- data/spec/unit/multiple_transitions_that_differ_only_by_guard_spec.rb +0 -14
- data/spec/unit/namespaced_multiple_example_spec.rb +0 -75
- data/spec/unit/new_dsl_spec.rb +0 -12
- data/spec/unit/override_warning_spec.rb +0 -94
- data/spec/unit/persistence/active_record_persistence_multiple_spec.rb +0 -618
- data/spec/unit/persistence/active_record_persistence_spec.rb +0 -735
- data/spec/unit/persistence/dynamoid_persistence_multiple_spec.rb +0 -135
- data/spec/unit/persistence/dynamoid_persistence_spec.rb +0 -84
- data/spec/unit/persistence/mongoid_persistence_multiple_spec.rb +0 -204
- data/spec/unit/persistence/mongoid_persistence_spec.rb +0 -169
- data/spec/unit/persistence/redis_persistence_multiple_spec.rb +0 -88
- data/spec/unit/persistence/redis_persistence_spec.rb +0 -53
- data/spec/unit/persistence/sequel_persistence_multiple_spec.rb +0 -148
- data/spec/unit/persistence/sequel_persistence_spec.rb +0 -368
- data/spec/unit/readme_spec.rb +0 -41
- data/spec/unit/reloading_spec.rb +0 -15
- data/spec/unit/rspec_matcher_spec.rb +0 -79
- data/spec/unit/simple_custom_example_spec.rb +0 -39
- data/spec/unit/simple_example_spec.rb +0 -42
- data/spec/unit/simple_multiple_example_spec.rb +0 -91
- data/spec/unit/state_spec.rb +0 -89
- data/spec/unit/states_on_one_line_example_spec.rb +0 -16
- data/spec/unit/subclassing_multiple_spec.rb +0 -74
- data/spec/unit/subclassing_spec.rb +0 -46
- data/spec/unit/transition_spec.rb +0 -436
- data/test/minitest_helper.rb +0 -57
- 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.
|
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
|
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
|
-
|
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
|
-
|
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
|
data/lib/aasm/configuration.rb
CHANGED
@@ -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
|
|
data/lib/aasm/core/event.rb
CHANGED
@@ -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
|
-
|
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
|
159
|
-
|
160
|
-
|
161
|
-
|
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
|
-
|
178
|
-
|
179
|
-
|
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
|