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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b418dfe409a43af2be1442c9816531076a476c16aa459b5203b49fb32d840836
4
- data.tar.gz: be20845e12107f4e7b7b8b086dbead6b7920a1baf1bce41bd847922807c3e9d9
3
+ metadata.gz: 3519bcb78e2b13c959698975e01d44ff031c16abc4d650096054264d14cdf580
4
+ data.tar.gz: dbd636da4a27423d023889ee8ea6863ac192d10930aa9a107309708218efcc7e
5
5
  SHA512:
6
- metadata.gz: d48878a62c1a65a8202021d7ccbb4a4e7c63cc4bc75e825a9f115c6060b75fefdd53abb2a2412acd869edaf9cb5f659f8a93804cd9d7d1a9fbf98f887cc464a9
7
- data.tar.gz: c5fca1261a2e76d32a2b45ca33c0ab0f51cd911c61f8904306ff8437c6896a183d1108e6d6fbc8d26ec0fdb208d947e52707aa401efd6738e4eb1ad153d1d401
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 "The object provided to the step (#{txn}) does not implement a `validator` method."
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
- # However, it first checks to see if the transaction implements a
17
- # `validator` method (usually provided by the validation_dsl
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
- # normal. If it fails, it continues on with the next step, passing
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}".to_sym
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
- raise NoValidatorError, txn unless txn.respond_to? :validator
39
+ txn_class = txn.is_a?(Class) ? txn : txn.class
40
+ raise NoValidatorError, txn unless txn_class.respond_to? :validator
43
41
 
44
- result = txn.validator.new.call(*args)
42
+ result = txn_class.validator.new.call(*args)
45
43
  if result.failure?
46
- # Rails.logger.debug "Skipping #{txn} because of errors: #{result.errors.to_h}"
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}".to_sym
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, &block)
69
- validator(contract || Class.new(Dry::Validation::Contract, &block))
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
@@ -3,7 +3,7 @@
3
3
  module Dry
4
4
  module Transaction
5
5
  module Extra
6
- VERSION = "0.1.1"
6
+ VERSION = "0.1.2"
7
7
  end
8
8
  end
9
9
  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/tap"
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.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: 2023-07-18 00:00:00.000000000 Z
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.4.10
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: []