dry-transaction-extra 0.1.1 → 0.1.2
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/lib/dry/transaction/extra/active_record_rescues.rb +26 -0
- data/lib/dry/transaction/extra/perform_later.rb +58 -0
- data/lib/dry/transaction/extra/steps/async.rb +55 -0
- data/lib/dry/transaction/extra/steps/maybe.rb +14 -15
- data/lib/dry/transaction/extra/steps/use.rb +3 -3
- data/lib/dry/transaction/extra/validation_dsl.rb +41 -2
- data/lib/dry/transaction/extra/version.rb +1 -1
- data/lib/dry/transaction/extra.rb +13 -1
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3519bcb78e2b13c959698975e01d44ff031c16abc4d650096054264d14cdf580
|
4
|
+
data.tar.gz: dbd636da4a27423d023889ee8ea6863ac192d10930aa9a107309708218efcc7e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b6dfb9945b340e4c944e3feeadc54edf3b2e150e1f9725c5764667cd9e80a573e613a72ba2654bfc87280d2cb749fc5cdc06ec93b4830d7e4c4b639b0c4631f1
|
7
|
+
data.tar.gz: d77f0cef4644bf1dfa8b96dbad0f89031910857a0f79577aec47d37202a2c8a6dc6de81779fce873d524a387321286c5000d1c2be9fb0c2843c6bdc83591436c
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Transaction
|
5
|
+
module Extra
|
6
|
+
module ActiveRecordRescues
|
7
|
+
RESCUE_ERRORS = [
|
8
|
+
ActiveRecord::RecordInvalid,
|
9
|
+
ActiveRecord::RecordNotFound,
|
10
|
+
ActiveRecord::RecordNotUnique
|
11
|
+
].freeze
|
12
|
+
|
13
|
+
def with_broadcast(args)
|
14
|
+
super
|
15
|
+
rescue *RESCUE_ERRORS => e
|
16
|
+
error = adapter.options[:message] || e
|
17
|
+
Failure(
|
18
|
+
Dry::Transaction::StepFailure.call(self, error) do
|
19
|
+
publish(:step_failed, step_name: name, args: args, value: error)
|
20
|
+
end
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Transaction
|
5
|
+
module Extra
|
6
|
+
module PerformLater
|
7
|
+
def self.extended(klass)
|
8
|
+
klass.extend Dry::Core::ClassAttributes
|
9
|
+
|
10
|
+
klass.defines :transaction_job
|
11
|
+
end
|
12
|
+
|
13
|
+
include Dry::Monads::Result::Mixin
|
14
|
+
|
15
|
+
def set(options = {})
|
16
|
+
ConfiguredJob.new(transaction_job, self, options)
|
17
|
+
end
|
18
|
+
|
19
|
+
def perform_later(*args)
|
20
|
+
if validator
|
21
|
+
result = validator.new.call(*args).to_monad
|
22
|
+
return result unless result.success?
|
23
|
+
|
24
|
+
args = [result.value!.to_h]
|
25
|
+
end
|
26
|
+
|
27
|
+
Dry::Monads::Success(transaction_job.new(transaction_class_name: name, args:).enqueue)
|
28
|
+
end
|
29
|
+
|
30
|
+
class ConfiguredJob
|
31
|
+
def initialize(job_class, transaction, options = {})
|
32
|
+
@job_class = job_class
|
33
|
+
@transaction = transaction
|
34
|
+
@options = options
|
35
|
+
end
|
36
|
+
|
37
|
+
def perform_later(*args)
|
38
|
+
if validator
|
39
|
+
result = validator.new.call(*args).to_monad
|
40
|
+
return result unless result.success?
|
41
|
+
|
42
|
+
args = [result.value!.to_h]
|
43
|
+
end
|
44
|
+
|
45
|
+
Dry::Monads::Success(
|
46
|
+
transaction_job.new(transaction_class_name: @transaction.name, args:)
|
47
|
+
.enqueue(@options)
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
def validator
|
52
|
+
@transaction.validator
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Transaction
|
5
|
+
module Extra
|
6
|
+
module Steps
|
7
|
+
# Executes the step in a background job. Argument is an ActiveJob or anything that
|
8
|
+
# implements `#perform_later`. This can include other Transactions when using the
|
9
|
+
# :perform_later extension.
|
10
|
+
#
|
11
|
+
# If the provided transaction implements a `validate` step, then that validator will be
|
12
|
+
# called on the input before the job is enqueued. This prevents us from enqueuing jobs with
|
13
|
+
# garbage arguemnts that can never be run, and limits the params passed through the message
|
14
|
+
# body into only those relevant to the job.
|
15
|
+
#
|
16
|
+
# Additionally, ActiveJob only allows for the serialization of a few types of values into
|
17
|
+
# the message, Strings, Numbers and ActiveRecord::Model instances (via globalid). Anything
|
18
|
+
# else will raise an ActiveJob::SerializationError. Calling the validator beforehand helps
|
19
|
+
# strip those out as well.
|
20
|
+
#
|
21
|
+
# Accepts an optional argument of delay:, to allow for jobs to be performed later, instead
|
22
|
+
# of as soon as possible.
|
23
|
+
#
|
24
|
+
# Usage:
|
25
|
+
#
|
26
|
+
# async GuestsCleanupJob # A job
|
27
|
+
# async Guests::CleanStale # A transaction
|
28
|
+
# async DoThisLater, delay: 5.minutes # Optional delay for the job
|
29
|
+
#
|
30
|
+
module Async
|
31
|
+
module DSL
|
32
|
+
def async(job, delay: nil)
|
33
|
+
method_name = job.name.underscore.intern
|
34
|
+
step method_name
|
35
|
+
define_method method_name do |input = {}|
|
36
|
+
job = job.set(wait: delay) if delay
|
37
|
+
|
38
|
+
if (validator = job&.validator)
|
39
|
+
result = validator.new.call(input)
|
40
|
+
# If the validator failed, don't enqueue the job, but don't
|
41
|
+
# also fail the step
|
42
|
+
job.perform_later(**result.to_h) if result.success?
|
43
|
+
else
|
44
|
+
job.perform_later(**input)
|
45
|
+
end
|
46
|
+
|
47
|
+
Success(input)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -8,42 +8,41 @@ module Dry
|
|
8
8
|
module DSL
|
9
9
|
class NoValidatorError < ArgumentError
|
10
10
|
def initialize(txn)
|
11
|
-
super
|
11
|
+
super("The object provided to the step (#{txn}) does not implement a `validator` method.")
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
# Just like the `use` step, this invokes another transaction.
|
16
|
-
#
|
17
|
-
#
|
18
|
-
# extension), and if so, will call it before invoking the
|
19
|
-
# transaction.
|
15
|
+
# Just like the `use` step, this invokes another transaction. However, it first checks
|
16
|
+
# to see if the transaction implements a `validator` method (usually provided by the
|
17
|
+
# validation_dsl extension), and if so, will call it before invoking the transaction.
|
20
18
|
#
|
21
|
-
# If the validation succeeds, then it invokes the transaction as
|
22
|
-
#
|
23
|
-
# the original input through.
|
19
|
+
# If the validation succeeds, then it invokes the transaction as normal. If it fails, it
|
20
|
+
# continues on with the next step, passing the original input through.
|
24
21
|
#
|
25
22
|
# @example
|
26
23
|
#
|
27
24
|
# step :create_user
|
28
25
|
# maybe VerifyEmail
|
29
26
|
#
|
30
|
-
def maybe(txn_or_container, key = nil, as: nil)
|
27
|
+
def maybe(txn_or_container, key = nil, as: nil, **)
|
31
28
|
if key
|
32
29
|
container = txn_or_container
|
33
|
-
method_name = as || "#{container.name}.#{key}"
|
30
|
+
method_name = as || :"#{container.name}.#{key}"
|
34
31
|
else
|
35
32
|
txn = txn_or_container
|
36
33
|
method_name = as || txn.name.to_sym
|
37
34
|
end
|
38
35
|
|
39
|
-
merge(method_name, as
|
36
|
+
merge(method_name, as:, **)
|
40
37
|
define_method method_name do |*args|
|
41
38
|
txn = container[key] if key
|
42
|
-
|
39
|
+
txn_class = txn.is_a?(Class) ? txn : txn.class
|
40
|
+
raise NoValidatorError, txn unless txn_class.respond_to? :validator
|
43
41
|
|
44
|
-
result =
|
42
|
+
result = txn_class.validator.new.call(*args)
|
45
43
|
if result.failure?
|
46
|
-
#
|
44
|
+
# publish(:maybe_failed, step_name: method_name, args:, value: result)
|
45
|
+
Rails.logger.debug "Skipping #{txn} because of errors: #{result.errors.to_h}" if defined?(Rails)
|
47
46
|
return Success(*args)
|
48
47
|
end
|
49
48
|
|
@@ -38,16 +38,16 @@ module Dry
|
|
38
38
|
#
|
39
39
|
# # => { user: #<User: id=1> }
|
40
40
|
#
|
41
|
-
def use(txn_or_container, key = nil, as: nil)
|
41
|
+
def use(txn_or_container, key = nil, as: nil, **)
|
42
42
|
if key
|
43
43
|
container = txn_or_container
|
44
|
-
method_name = as || "#{container.name}.#{key}"
|
44
|
+
method_name = as || :"#{container.name}.#{key}"
|
45
45
|
else
|
46
46
|
txn = txn_or_container
|
47
47
|
method_name = as || txn.name.to_sym
|
48
48
|
end
|
49
49
|
|
50
|
-
merge(method_name, as
|
50
|
+
merge(method_name, as:, **)
|
51
51
|
define_method method_name do |*args|
|
52
52
|
txn = container[key] if key
|
53
53
|
txn.call(*args)
|
@@ -26,6 +26,45 @@ module Dry
|
|
26
26
|
# # => #<Dry::Validation::Result{name: "Jane"} errors={}>
|
27
27
|
klass.defines :validator
|
28
28
|
|
29
|
+
# Allows overriding the default validation contract class. This is useful if you want to
|
30
|
+
# use a different Contract class with a different configuration.
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
#
|
34
|
+
# module MyApp
|
35
|
+
# module Types
|
36
|
+
# include Dry.Types()
|
37
|
+
#
|
38
|
+
# Container = Dry::Schema::TypeContainer.new
|
39
|
+
# Container.register("params.email", String.constrained(format: /@/))
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# class Contract < Dry::Validation::Contract
|
43
|
+
# config.types = Types::Container
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# module ApplicationTransaction
|
48
|
+
# include Dry::Transaction
|
49
|
+
# include Dry::Transaction::Extra
|
50
|
+
#
|
51
|
+
# load_extensions :validation
|
52
|
+
#
|
53
|
+
# validation_contract_class MyApp::Contract
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# class MyTransaction
|
57
|
+
# include ApplicationTransaction
|
58
|
+
#
|
59
|
+
# validate do
|
60
|
+
# params do
|
61
|
+
# # Now the custom `:email` type is available in this schema
|
62
|
+
# required(:email).filled(:email)
|
63
|
+
# end
|
64
|
+
# end
|
65
|
+
# end
|
66
|
+
klass.defines :validation_contract_class
|
67
|
+
|
29
68
|
require "dry/validation"
|
30
69
|
Dry::Validation.load_extensions(:monads)
|
31
70
|
end
|
@@ -65,8 +104,8 @@ module Dry
|
|
65
104
|
# validate NewUserContract
|
66
105
|
# end
|
67
106
|
#
|
68
|
-
def validate(contract = nil, &
|
69
|
-
validator(contract || Class.new(Dry::Validation::Contract, &
|
107
|
+
def validate(contract = nil, &)
|
108
|
+
validator(contract || Class.new(validation_contract_class || Dry::Validation::Contract, &))
|
70
109
|
|
71
110
|
valid(validator.new, name: "validate")
|
72
111
|
end
|
@@ -5,19 +5,23 @@ require_relative "extra/version"
|
|
5
5
|
require "dry/monads"
|
6
6
|
require "dry/transaction"
|
7
7
|
|
8
|
-
require_relative "extra/steps/
|
8
|
+
require_relative "extra/steps/async"
|
9
9
|
require_relative "extra/steps/maybe"
|
10
10
|
require_relative "extra/steps/merge"
|
11
|
+
require_relative "extra/steps/tap"
|
11
12
|
require_relative "extra/steps/use"
|
12
13
|
require_relative "extra/steps/valid"
|
13
14
|
|
15
|
+
require_relative "extra/active_record_rescues"
|
14
16
|
require_relative "extra/class_callable"
|
17
|
+
require_relative "extra/perform_later"
|
15
18
|
require_relative "extra/validation_dsl"
|
16
19
|
|
17
20
|
module Dry
|
18
21
|
module Transaction
|
19
22
|
module Extra
|
20
23
|
def self.included(klass)
|
24
|
+
klass.extend Extra::Steps::Async::DSL
|
21
25
|
klass.extend Extra::Steps::Maybe::DSL
|
22
26
|
klass.extend Extra::Steps::Use::DSL
|
23
27
|
klass.extend Extra::Steps::Valid::DSL
|
@@ -31,6 +35,14 @@ module Dry
|
|
31
35
|
klass.register_extension :class_callable do
|
32
36
|
klass.extend ClassCallable
|
33
37
|
end
|
38
|
+
|
39
|
+
klass.register_extension :perform_later do
|
40
|
+
klass.extend PerformLater
|
41
|
+
end
|
42
|
+
|
43
|
+
klass.register_extension :active_record_rescues do
|
44
|
+
Dry::Transaction::Step.prepend(ActiveRecordRescues)
|
45
|
+
end
|
34
46
|
end
|
35
47
|
|
36
48
|
adapters = Dry::Transaction::StepAdapters
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dry-transaction-extra
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul Sadauskas
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-04-11 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: dry-monads
|
@@ -98,7 +97,10 @@ files:
|
|
98
97
|
- Rakefile
|
99
98
|
- dry-transaction-extra.gemspec
|
100
99
|
- lib/dry/transaction/extra.rb
|
100
|
+
- lib/dry/transaction/extra/active_record_rescues.rb
|
101
101
|
- lib/dry/transaction/extra/class_callable.rb
|
102
|
+
- lib/dry/transaction/extra/perform_later.rb
|
103
|
+
- lib/dry/transaction/extra/steps/async.rb
|
102
104
|
- lib/dry/transaction/extra/steps/maybe.rb
|
103
105
|
- lib/dry/transaction/extra/steps/merge.rb
|
104
106
|
- lib/dry/transaction/extra/steps/tap.rb
|
@@ -113,7 +115,6 @@ licenses:
|
|
113
115
|
metadata:
|
114
116
|
homepage_uri: https://github.com/paul/dry-transaction-extra
|
115
117
|
source_code_uri: https://github.com/paul/dry-transaction-extra
|
116
|
-
post_install_message:
|
117
118
|
rdoc_options: []
|
118
119
|
require_paths:
|
119
120
|
- lib
|
@@ -128,8 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
128
129
|
- !ruby/object:Gem::Version
|
129
130
|
version: '0'
|
130
131
|
requirements: []
|
131
|
-
rubygems_version: 3.
|
132
|
-
signing_key:
|
132
|
+
rubygems_version: 3.6.2
|
133
133
|
specification_version: 4
|
134
134
|
summary: Extra steps and functionality for Dry::Transaction
|
135
135
|
test_files: []
|