axn 0.1.0.pre.alpha.2.4.1 → 0.1.0.pre.alpha.2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -2
- data/CHANGELOG.md +18 -2
- data/docs/recipes/testing.md +50 -0
- data/docs/reference/class.md +42 -6
- data/docs/reference/configuration.md +9 -12
- data/docs/reference/instance.md +6 -0
- data/docs/usage/using.md +30 -0
- data/lib/action/core/configuration.rb +5 -4
- data/lib/action/core/context_facade.rb +14 -5
- data/lib/action/core/contract.rb +78 -20
- data/lib/action/core/contract_for_subfields.rb +118 -0
- data/lib/action/core/event_handlers.rb +2 -2
- data/lib/action/core/exceptions.rb +7 -6
- data/lib/action/core/hoist_errors.rb +4 -2
- data/lib/action/core/logging.rb +3 -9
- data/lib/action/core/swallow_exceptions.rb +12 -30
- data/lib/action/core/top_level_around_hook.rb +43 -27
- data/lib/action/core/validation/fields.rb +38 -0
- data/lib/action/core/validation/subfields.rb +44 -0
- data/lib/action/core/validation/validators/model_validator.rb +35 -0
- data/lib/action/core/validation/validators/type_validator.rb +30 -0
- data/lib/action/core/validation/validators/validate_validator.rb +21 -0
- data/lib/action/enqueueable/enqueue_all_in_background.rb +17 -0
- data/lib/action/enqueueable/enqueue_all_worker.rb +21 -0
- data/lib/action/enqueueable/via_sidekiq.rb +76 -0
- data/lib/action/enqueueable.rb +15 -0
- data/lib/axn/factory.rb +1 -2
- data/lib/axn/version.rb +1 -1
- data/lib/axn.rb +11 -4
- metadata +12 -4
- data/lib/action/core/contract_validator.rb +0 -66
- data/lib/action/core/enqueueable.rb +0 -74
@@ -3,15 +3,16 @@
|
|
3
3
|
module Action
|
4
4
|
# Raised internally when fail! is called -- triggers failure + rollback handling
|
5
5
|
class Failure < StandardError
|
6
|
-
|
6
|
+
DEFAULT_MESSAGE = "Execution was halted"
|
7
7
|
|
8
|
-
def initialize(
|
9
|
-
|
10
|
-
|
11
|
-
@context = context
|
8
|
+
def initialize(message = nil, **)
|
9
|
+
@message = message
|
10
|
+
super(**)
|
12
11
|
end
|
13
12
|
|
14
|
-
def message
|
13
|
+
def message
|
14
|
+
@message.presence || DEFAULT_MESSAGE
|
15
|
+
end
|
15
16
|
|
16
17
|
def inspect = "#<#{self.class.name} '#{message}'>"
|
17
18
|
end
|
@@ -29,8 +29,10 @@ module Action
|
|
29
29
|
MinimalFailedResult.new(error: nil, exception: e)
|
30
30
|
end
|
31
31
|
|
32
|
-
# This ensures the last line of hoist_errors was an Action call
|
33
|
-
#
|
32
|
+
# This ensures the last line of hoist_errors was an Action call
|
33
|
+
#
|
34
|
+
# CAUTION: if there are multiple calls per block, only the last one will be checked!
|
35
|
+
#
|
34
36
|
unless result.respond_to?(:ok?)
|
35
37
|
raise ArgumentError,
|
36
38
|
"#hoist_errors is expected to wrap an Action call, but it returned a #{result.class.name} instead"
|
data/lib/action/core/logging.rb
CHANGED
@@ -14,8 +14,9 @@ module Action
|
|
14
14
|
end
|
15
15
|
|
16
16
|
module ClassMethods
|
17
|
-
def
|
18
|
-
|
17
|
+
def default_log_level = Action.config.default_log_level
|
18
|
+
|
19
|
+
def log(message, level: default_log_level)
|
19
20
|
msg = [_log_prefix, message].compact_blank.join(" ")
|
20
21
|
|
21
22
|
Action.config.logger.send(level, msg)
|
@@ -29,13 +30,6 @@ module Action
|
|
29
30
|
|
30
31
|
# TODO: this is ugly, we should be able to override in the config class...
|
31
32
|
def _log_prefix = name == "Action::Configuration" ? nil : "[#{name || "Anonymous Class"}]"
|
32
|
-
|
33
|
-
def _targeted_for_debug_logging?
|
34
|
-
return true if Action.config.global_debug_logging?
|
35
|
-
|
36
|
-
target_class_names = (ENV["SA_DEBUG_TARGETS"] || "").split(",").map(&:strip)
|
37
|
-
target_class_names.include?(name)
|
38
|
-
end
|
39
33
|
end
|
40
34
|
end
|
41
35
|
end
|
@@ -15,8 +15,8 @@ module Action
|
|
15
15
|
include InstanceMethods
|
16
16
|
extend ClassMethods
|
17
17
|
|
18
|
-
def
|
19
|
-
|
18
|
+
def run
|
19
|
+
run!
|
20
20
|
rescue StandardError => e
|
21
21
|
# on_error handlers run for both unhandled exceptions and fail!
|
22
22
|
self.class._error_handlers.each do |handler|
|
@@ -25,31 +25,19 @@ module Action
|
|
25
25
|
|
26
26
|
# on_failure handlers run ONLY for fail!
|
27
27
|
if e.is_a?(Action::Failure)
|
28
|
+
@context.instance_variable_set("@error_from_user", e.message) if e.message.present?
|
29
|
+
|
28
30
|
self.class._failure_handlers.each do |handler|
|
29
31
|
handler.execute_if_matches(exception: e, action: self)
|
30
32
|
end
|
33
|
+
else
|
34
|
+
# on_exception handlers run for ONLY for unhandled exceptions. AND NOTE: may be skipped if the exception is rescued via `rescues`.
|
35
|
+
trigger_on_exception(e)
|
31
36
|
|
32
|
-
|
33
|
-
raise e
|
37
|
+
@context.exception = e
|
34
38
|
end
|
35
39
|
|
36
|
-
|
37
|
-
trigger_on_exception(e)
|
38
|
-
|
39
|
-
@context.exception = e
|
40
|
-
|
41
|
-
fail!
|
42
|
-
end
|
43
|
-
|
44
|
-
alias_method :original_run!, :run!
|
45
|
-
alias_method :run!, :run_with_exception_swallowing!
|
46
|
-
|
47
|
-
# Tweaked to check @context.object_id rather than context (since forwarding object_id causes Ruby to complain)
|
48
|
-
# TODO: do we actually need the object_id check? Do we need this override at all?
|
49
|
-
def run
|
50
|
-
run!
|
51
|
-
rescue Action::Failure => e
|
52
|
-
raise if @context.object_id != e.context.object_id
|
40
|
+
@context.instance_variable_set("@failure", true)
|
53
41
|
end
|
54
42
|
|
55
43
|
def trigger_on_exception(exception)
|
@@ -72,17 +60,12 @@ module Action
|
|
72
60
|
end
|
73
61
|
|
74
62
|
class << base
|
75
|
-
def
|
63
|
+
def call!(context = {})
|
76
64
|
result = call(context)
|
77
65
|
return result if result.ok?
|
78
66
|
|
79
|
-
raise result.exception
|
80
|
-
|
81
|
-
raise Action::Failure.new(result.instance_variable_get("@context"), message: result.error)
|
67
|
+
raise result.exception || Action::Failure.new(result.error)
|
82
68
|
end
|
83
|
-
|
84
|
-
alias_method :original_call!, :call!
|
85
|
-
alias_method :call!, :call_bang_with_unswallowed_exceptions
|
86
69
|
end
|
87
70
|
end
|
88
71
|
end
|
@@ -162,8 +145,7 @@ module Action
|
|
162
145
|
@context.instance_variable_set("@failure", true)
|
163
146
|
@context.error_from_user = message if message.present?
|
164
147
|
|
165
|
-
|
166
|
-
raise Action::Failure.new(@context, message:)
|
148
|
+
raise Action::Failure, message
|
167
149
|
end
|
168
150
|
|
169
151
|
def try
|
@@ -6,46 +6,39 @@ module Action
|
|
6
6
|
base.class_eval do
|
7
7
|
around :__top_level_around_hook
|
8
8
|
|
9
|
+
extend AutologgingClassMethods
|
10
|
+
include AutologgingInstanceMethods
|
9
11
|
include InstanceMethods
|
10
12
|
end
|
11
13
|
end
|
12
14
|
|
13
|
-
module
|
14
|
-
def
|
15
|
-
|
16
|
-
_log_before
|
17
|
-
|
18
|
-
_configurable_around_wrapper do
|
19
|
-
(@outcome, @exception) = _call_and_return_outcome(hooked)
|
20
|
-
end
|
21
|
-
|
22
|
-
_log_after(timing_start:, outcome: @outcome)
|
23
|
-
|
24
|
-
raise @exception if @exception
|
25
|
-
end
|
15
|
+
module AutologgingClassMethods
|
16
|
+
def default_autolog_level = Action.config.default_autolog_level
|
17
|
+
end
|
26
18
|
|
19
|
+
module AutologgingInstanceMethods
|
27
20
|
private
|
28
21
|
|
29
|
-
def _configurable_around_wrapper(&)
|
30
|
-
return yield unless Action.config.top_level_around_hook
|
31
|
-
|
32
|
-
Action.config.top_level_around_hook.call(self.class.name || "AnonymousClass", &)
|
33
|
-
end
|
34
|
-
|
35
22
|
def _log_before
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
23
|
+
public_send(
|
24
|
+
self.class.default_autolog_level,
|
25
|
+
[
|
26
|
+
"About to execute",
|
27
|
+
context_for_logging(:inbound).presence&.inspect,
|
28
|
+
].compact.join(" with: "),
|
29
|
+
)
|
40
30
|
end
|
41
31
|
|
42
32
|
def _log_after(outcome:, timing_start:)
|
43
33
|
elapsed_mils = ((Time.now - timing_start) * 1000).round(3)
|
44
34
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
35
|
+
public_send(
|
36
|
+
self.class.default_autolog_level,
|
37
|
+
[
|
38
|
+
"Execution completed (with outcome: #{outcome}) in #{elapsed_mils} milliseconds",
|
39
|
+
_log_after_data_peak,
|
40
|
+
].compact.join(". Set: "),
|
41
|
+
)
|
49
42
|
end
|
50
43
|
|
51
44
|
def _log_after_data_peak
|
@@ -58,6 +51,29 @@ module Action
|
|
58
51
|
return str[0, max_length - suffix.length] + suffix if str.length > max_length
|
59
52
|
end
|
60
53
|
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module InstanceMethods
|
57
|
+
def __top_level_around_hook(hooked)
|
58
|
+
timing_start = Time.now
|
59
|
+
_log_before
|
60
|
+
|
61
|
+
_configurable_around_wrapper do
|
62
|
+
(@outcome, @exception) = _call_and_return_outcome(hooked)
|
63
|
+
end
|
64
|
+
|
65
|
+
_log_after(timing_start:, outcome: @outcome)
|
66
|
+
|
67
|
+
raise @exception if @exception
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def _configurable_around_wrapper(&)
|
73
|
+
return yield unless Action.config.top_level_around_hook
|
74
|
+
|
75
|
+
Action.config.top_level_around_hook.call(self.class.name || "AnonymousClass", &)
|
76
|
+
end
|
61
77
|
|
62
78
|
def _call_and_return_outcome(hooked)
|
63
79
|
hooked.call
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Action
|
4
|
+
module Validation
|
5
|
+
class Fields
|
6
|
+
include ActiveModel::Validations
|
7
|
+
|
8
|
+
# NOTE: defining classes where needed b/c we explicitly register it'll affect ALL the consuming apps' validators as well
|
9
|
+
ModelValidator = Validators::ModelValidator
|
10
|
+
TypeValidator = Validators::TypeValidator
|
11
|
+
ValidateValidator = Validators::ValidateValidator
|
12
|
+
|
13
|
+
def initialize(context)
|
14
|
+
@context = context
|
15
|
+
end
|
16
|
+
|
17
|
+
def read_attribute_for_validation(attr)
|
18
|
+
@context.public_send(attr)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.validate!(validations:, context:, exception_klass:)
|
22
|
+
validator = Class.new(self) do
|
23
|
+
def self.name = "Action::Validation::Fields::OneOff"
|
24
|
+
|
25
|
+
validations.each do |field, field_validations|
|
26
|
+
field_validations.each do |key, value|
|
27
|
+
validates field, key => value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end.new(context)
|
31
|
+
|
32
|
+
return if validator.valid?
|
33
|
+
|
34
|
+
raise exception_klass, validator.errors
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/hash/indifferent_access"
|
4
|
+
|
5
|
+
module Action
|
6
|
+
module Validation
|
7
|
+
class Subfields
|
8
|
+
include ActiveModel::Validations
|
9
|
+
|
10
|
+
# NOTE: defining classes where needed b/c we explicitly register it'll affect ALL the consuming apps' validators as well
|
11
|
+
ModelValidator = Validators::ModelValidator
|
12
|
+
TypeValidator = Validators::TypeValidator
|
13
|
+
ValidateValidator = Validators::ValidateValidator
|
14
|
+
|
15
|
+
def initialize(source)
|
16
|
+
@source = source
|
17
|
+
end
|
18
|
+
|
19
|
+
def read_attribute_for_validation(attr)
|
20
|
+
self.class.extract(attr, @source)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.extract(attr, source)
|
24
|
+
return source.public_send(attr) if source.respond_to?(attr)
|
25
|
+
raise "Unclear how to extract #{attr} from #{source.inspect}" unless source.respond_to?(:dig)
|
26
|
+
|
27
|
+
base = source.respond_to?(:with_indifferent_access) ? source.with_indifferent_access : source
|
28
|
+
base.dig(*attr.to_s.split("."))
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.validate!(field:, validations:, source:, exception_klass:)
|
32
|
+
validator = Class.new(self) do
|
33
|
+
def self.name = "Action::Validation::Subfields::OneOff"
|
34
|
+
|
35
|
+
validates field, **validations
|
36
|
+
end.new(source)
|
37
|
+
|
38
|
+
return if validator.valid?
|
39
|
+
|
40
|
+
raise exception_klass, validator.errors
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model"
|
4
|
+
|
5
|
+
module Action
|
6
|
+
module Validators
|
7
|
+
class ModelValidator < ActiveModel::EachValidator
|
8
|
+
def self.model_for(field:, klass: nil)
|
9
|
+
return klass if defined?(ActiveRecord::Base) && klass.is_a?(ActiveRecord::Base)
|
10
|
+
|
11
|
+
field.to_s.delete_suffix("_id").classify.constantize
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.instance_for(field:, klass:, id:)
|
15
|
+
klass = model_for(field:, klass:)
|
16
|
+
return unless klass.respond_to?(:find_by)
|
17
|
+
|
18
|
+
klass.find_by(id:)
|
19
|
+
end
|
20
|
+
|
21
|
+
def validate_each(record, attribute, id)
|
22
|
+
klass = self.class.model_for(field: attribute, klass: options[:with])
|
23
|
+
instance = self.class.instance_for(field: attribute, klass:, id:)
|
24
|
+
return if instance.present?
|
25
|
+
|
26
|
+
msg = id.blank? ? "not found (given a blank ID)" : "not found for class #{klass.name} and ID #{id}"
|
27
|
+
record.errors.add(attribute, msg)
|
28
|
+
rescue StandardError => e
|
29
|
+
warn("Model validation on field '#{attribute}' raised #{e.class.name}: #{e.message}")
|
30
|
+
|
31
|
+
record.errors.add(attribute, "error raised while trying to find a valid #{klass.name}")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model"
|
4
|
+
|
5
|
+
module Action
|
6
|
+
module Validators
|
7
|
+
class TypeValidator < ActiveModel::EachValidator
|
8
|
+
def validate_each(record, attribute, value)
|
9
|
+
# NOTE: the last one (:value) might be my fault from the make-it-a-hash fallback in #parse_field_configs
|
10
|
+
types = options[:in].presence || Array(options[:with]).presence || Array(options[:value]).presence
|
11
|
+
|
12
|
+
return if value.blank? && !types.include?(:boolean) # Handled with a separate default presence validator
|
13
|
+
|
14
|
+
msg = types.size == 1 ? "is not a #{types.first}" : "is not one of #{types.join(", ")}"
|
15
|
+
record.errors.add attribute, (options[:message] || msg) unless types.any? do |type|
|
16
|
+
if type == :boolean
|
17
|
+
[true, false].include?(value)
|
18
|
+
elsif type == :uuid
|
19
|
+
value.is_a?(String) && value.match?(/\A[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}\z/i)
|
20
|
+
else
|
21
|
+
# NOTE: allow mocks to pass type validation by default (much easier testing ergonomics)
|
22
|
+
next true if Action.config.env.test? && value.class.name.start_with?("RSpec::Mocks::")
|
23
|
+
|
24
|
+
value.is_a?(type)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model"
|
4
|
+
|
5
|
+
module Action
|
6
|
+
module Validators
|
7
|
+
class ValidateValidator < ActiveModel::EachValidator
|
8
|
+
def validate_each(record, attribute, value)
|
9
|
+
msg = begin
|
10
|
+
options[:with].call(value)
|
11
|
+
rescue StandardError => e
|
12
|
+
Action.config.logger.warn("Custom validation on field '#{attribute}' raised #{e.class.name}: #{e.message}")
|
13
|
+
|
14
|
+
"failed validation: #{e.message}"
|
15
|
+
end
|
16
|
+
|
17
|
+
record.errors.add(attribute, msg) if msg.present?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Action
|
4
|
+
module Enqueueable
|
5
|
+
module EnqueueAllInBackground
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def enqueue_all_in_background
|
10
|
+
raise NotImplementedError, "#{name} must implement a .enqueue_all method in order to use .enqueue_all_in_background" unless respond_to?(:enqueue_all)
|
11
|
+
|
12
|
+
::Action::Enqueueable::EnqueueAllWorker.enqueue(klass_name: name)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# NOTE: this is a standalone worker for enqueueing all instances of a class.
|
4
|
+
# Unlike the other files in the folder, it is NOT included in the Action stack.
|
5
|
+
|
6
|
+
# Note it uses Axn-native enqueueing, so will automatically support additional
|
7
|
+
# backends as they are added (initially, just Sidekiq)
|
8
|
+
|
9
|
+
module Action
|
10
|
+
module Enqueueable
|
11
|
+
class EnqueueAllWorker
|
12
|
+
include Action
|
13
|
+
|
14
|
+
expects :klass_name, type: String
|
15
|
+
|
16
|
+
def call
|
17
|
+
klass_name.constantize.enqueue_all
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Action
|
4
|
+
module Enqueueable
|
5
|
+
module ViaSidekiq
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
begin
|
9
|
+
require "sidekiq"
|
10
|
+
include Sidekiq::Job
|
11
|
+
rescue LoadError
|
12
|
+
puts "Sidekiq not available -- skipping Enqueueable"
|
13
|
+
return
|
14
|
+
end
|
15
|
+
|
16
|
+
define_method(:perform) do |*args|
|
17
|
+
context = self.class._params_from_global_id(args.first)
|
18
|
+
bang = args.size > 1 ? args.last : false
|
19
|
+
|
20
|
+
if bang
|
21
|
+
self.class.call!(context)
|
22
|
+
else
|
23
|
+
self.class.call(context)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.enqueue(context = {})
|
28
|
+
perform_async(_process_context_to_sidekiq_args(context))
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.enqueue!(context = {})
|
32
|
+
perform_async(_process_context_to_sidekiq_args(context), true)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.queue_options(opts)
|
36
|
+
opts = opts.transform_keys(&:to_s)
|
37
|
+
self.sidekiq_options_hash = get_sidekiq_options.merge(opts)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def self._process_context_to_sidekiq_args(context)
|
43
|
+
client = Sidekiq::Client.new
|
44
|
+
|
45
|
+
_params_to_global_id(context).tap do |args|
|
46
|
+
if client.send(:json_unsafe?, args).present?
|
47
|
+
raise ArgumentError,
|
48
|
+
"Cannot pass non-JSON-serializable objects to Sidekiq. Make sure all expected arguments are serializable (or respond to to_global_id)."
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self._params_to_global_id(context)
|
54
|
+
context.stringify_keys.each_with_object({}) do |(key, value), hash|
|
55
|
+
if value.respond_to?(:to_global_id)
|
56
|
+
hash["#{key}_as_global_id"] = value.to_global_id.to_s
|
57
|
+
else
|
58
|
+
hash[key] = value
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def self._params_from_global_id(params)
|
64
|
+
params.each_with_object({}) do |(key, value), hash|
|
65
|
+
if key.end_with?("_as_global_id")
|
66
|
+
hash[key.delete_suffix("_as_global_id")] = GlobalID::Locator.locate(value)
|
67
|
+
else
|
68
|
+
hash[key] = value
|
69
|
+
end
|
70
|
+
end.symbolize_keys
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "enqueueable/via_sidekiq"
|
4
|
+
require_relative "enqueueable/enqueue_all_in_background"
|
5
|
+
|
6
|
+
module Action
|
7
|
+
module Enqueueable
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
include ViaSidekiq
|
12
|
+
include EnqueueAllInBackground
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/axn/factory.rb
CHANGED
@@ -34,7 +34,6 @@ module Axn
|
|
34
34
|
end
|
35
35
|
raise ArgumentError, "[Axn::Factory] Cannot convert block to action: block expects a splat of keyword arguments" if args[:keyrest].present?
|
36
36
|
|
37
|
-
# TODO: is there any way to support default arguments? (if so, set allow_blank: true for those)
|
38
37
|
if args[:key].present?
|
39
38
|
raise ArgumentError,
|
40
39
|
"[Axn::Factory] Cannot convert block to action: block expects keyword arguments with defaults (ruby does not allow introspecting)"
|
@@ -66,7 +65,7 @@ module Axn
|
|
66
65
|
retval = instance_exec(**unwrapped_kwargs, &block)
|
67
66
|
expose(expose_return_as => retval) if expose_return_as.present?
|
68
67
|
end
|
69
|
-
end.tap do |axn|
|
68
|
+
end.tap do |axn|
|
70
69
|
expects.each do |field, opts|
|
71
70
|
axn.expects(field, **opts)
|
72
71
|
end
|
data/lib/axn/version.rb
CHANGED
data/lib/axn.rb
CHANGED
@@ -6,18 +6,23 @@ require_relative "axn/version"
|
|
6
6
|
require "interactor"
|
7
7
|
require "active_support"
|
8
8
|
|
9
|
+
require_relative "action/core/validation/validators/model_validator"
|
10
|
+
require_relative "action/core/validation/validators/type_validator"
|
11
|
+
require_relative "action/core/validation/validators/validate_validator"
|
12
|
+
|
9
13
|
require_relative "action/core/exceptions"
|
10
14
|
require_relative "action/core/logging"
|
11
15
|
require_relative "action/core/configuration"
|
12
16
|
require_relative "action/core/top_level_around_hook"
|
13
17
|
require_relative "action/core/contract"
|
18
|
+
require_relative "action/core/contract_for_subfields"
|
14
19
|
require_relative "action/core/swallow_exceptions"
|
15
20
|
require_relative "action/core/hoist_errors"
|
16
|
-
require_relative "action/core/enqueueable"
|
17
21
|
|
18
22
|
require_relative "axn/factory"
|
19
23
|
|
20
24
|
require_relative "action/attachable"
|
25
|
+
require_relative "action/enqueueable"
|
21
26
|
|
22
27
|
def Axn(callable, **) # rubocop:disable Naming/MethodName
|
23
28
|
return callable if callable.is_a?(Class) && callable < Action
|
@@ -37,15 +42,15 @@ module Action
|
|
37
42
|
# can include those hook executions in any traces set from this hook.
|
38
43
|
include TopLevelAroundHook
|
39
44
|
|
40
|
-
include Contract
|
41
45
|
include SwallowExceptions
|
46
|
+
include Contract
|
47
|
+
include ContractForSubfields
|
42
48
|
|
43
49
|
include HoistErrors
|
44
50
|
|
45
|
-
include Enqueueable
|
46
|
-
|
47
51
|
# --- Extensions ---
|
48
52
|
include Attachable
|
53
|
+
include Enqueueable
|
49
54
|
|
50
55
|
# Allow additional automatic includes to be configured
|
51
56
|
Array(Action.config.additional_includes).each { |mod| include mod }
|
@@ -62,3 +67,5 @@ module Action
|
|
62
67
|
end
|
63
68
|
end
|
64
69
|
end
|
70
|
+
|
71
|
+
require "action/enqueueable/enqueue_all_worker"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: axn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.0.pre.alpha.2.
|
4
|
+
version: 0.1.0.pre.alpha.2.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kali Donovan
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-06-
|
11
|
+
date: 2025-06-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -91,14 +91,22 @@ files:
|
|
91
91
|
- lib/action/core/configuration.rb
|
92
92
|
- lib/action/core/context_facade.rb
|
93
93
|
- lib/action/core/contract.rb
|
94
|
-
- lib/action/core/
|
95
|
-
- lib/action/core/enqueueable.rb
|
94
|
+
- lib/action/core/contract_for_subfields.rb
|
96
95
|
- lib/action/core/event_handlers.rb
|
97
96
|
- lib/action/core/exceptions.rb
|
98
97
|
- lib/action/core/hoist_errors.rb
|
99
98
|
- lib/action/core/logging.rb
|
100
99
|
- lib/action/core/swallow_exceptions.rb
|
101
100
|
- lib/action/core/top_level_around_hook.rb
|
101
|
+
- lib/action/core/validation/fields.rb
|
102
|
+
- lib/action/core/validation/subfields.rb
|
103
|
+
- lib/action/core/validation/validators/model_validator.rb
|
104
|
+
- lib/action/core/validation/validators/type_validator.rb
|
105
|
+
- lib/action/core/validation/validators/validate_validator.rb
|
106
|
+
- lib/action/enqueueable.rb
|
107
|
+
- lib/action/enqueueable/enqueue_all_in_background.rb
|
108
|
+
- lib/action/enqueueable/enqueue_all_worker.rb
|
109
|
+
- lib/action/enqueueable/via_sidekiq.rb
|
102
110
|
- lib/axn.rb
|
103
111
|
- lib/axn/factory.rb
|
104
112
|
- lib/axn/version.rb
|