Created
December 25, 2018 04:05
-
-
Save paul/b29b366d87880f91d8bb881dedddfcdb to your computer and use it in GitHub Desktop.
Implementations of useful step adapters for dry-transaction
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
module Tesseract | |
module Transaction | |
module Steps | |
# Executes the step in a background job. Argument is either an ActiveJob | |
# or another Transaction (or anything that implements `#perform_later`. | |
# | |
# If the provided transaction implements a `validate` step, then that | |
# validator will be called on the input before the job is enqueued. This | |
# prevents us from enqueuing jobs with garbage arguemnts that can never | |
# be run, and limits the params passed through the message body into only | |
# those relevant to the job. | |
# | |
# Additionally, ActiveJob only allows for the serialization of a few | |
# types of values into the message, Strings, Numbers and | |
# ActiveRecord::Model instances (via globalid). Anything else will raise | |
# an ActiveJob::SerializationError. Calling the validator beforehand | |
# helps strip those out as well. | |
# | |
# Usage: | |
# | |
# async DeliverMessageJob # A job | |
# async Conversations::Open # A transaction | |
# | |
module Async | |
extend ActiveSupport::Concern | |
module ClassMethods | |
def async(job) | |
method_name = job.name.underscore.intern | |
step method_name | |
define_method method_name do |input| | |
if validator = job&.validator | |
result = validator.call(input) | |
return result unless result.success? | |
job.perform_async(result.output) | |
else | |
job.perform_async(input) | |
end | |
Success(input) | |
end | |
end | |
def perform_later(*args) | |
TransactionJob.perform_later(transaction_class_name: name, args: args) | |
end | |
end | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
module Tesseract | |
module Transaction | |
module Steps | |
# Adds a "merge" step that expects the input to the step to be a hash, | |
# and then merges the successful result of the step into the input hash. | |
# If the step results in a Failure then that Failure is returned instead. | |
# | |
# If the return value of the step is not a hash, then the return value is | |
# merged into the input hash using the step name as the key. | |
# | |
# Usage: | |
# | |
# merge :user | |
# merge :lookup_metadata | |
# | |
# # input: { user_id: 42, name: "Chuck" } | |
# def user(user_id:, **) | |
# User.find(user_id) | |
# end | |
# # output: { user_id: 42, name: "Chuck", user: #<User id:42> } | |
# | |
# def lookup_metadata(user:,.**) | |
# resp = APIClient.get_email(user.client_id) | |
# { email: resp["user_email"], fists: resp["fists"]["items"].size } | |
# end | |
# | |
# # output: { user_id: 42, name: "Chuck", user: #<User id:42>, email: "chuck@example", fists: 2 } | |
# | |
module Merge | |
class MergeStepAdapter < Dry::Transaction::StepAdapters | |
include Dry::Monads::Result::Mixin | |
def call(operation, _options, input) | |
result = operation.call(input[0]) | |
return result if result.try(:failure?) | |
value = result.try(:value!) || result || {} | |
value = { operation.operation.name => value } unless value.is_a?(Hash) | |
Success(input[0].merge(value)) | |
end | |
end | |
Dry::Transaction::StepAdapters.register(:merge, MergeStepAdapter.new) unless Dry::Transaction::StepAdapters.key?(:merge) | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
module Tesseract | |
module Transaction | |
module Steps | |
# Add a "tap" step that works similarly to the built-in "tee" step in | |
# that if the step is successful it ignores the output and returns the | |
# input. However, if the step returns a Failure, then that failure is | |
# not ignored as it is with "tee", but is instead returned directly. | |
# | |
# Usage: | |
# | |
# tap :update_metadata | |
# | |
# def update_metadata(user:, **) | |
# resp = APIClient.update_data(name: user.name) | |
# return Failure(resp) if resp.code != 200 | |
# # on success this will implicitly return `nil`, but subsequent steps will | |
# # still have access to `user:` and other kwargs | |
# end | |
# | |
module Tap | |
class TapStepAdapter < Dry::Transaction::StepAdapters | |
include Dry::Monads::Result::Mixin | |
def call(operation, _options, input) | |
result = operation.call(input[0]) | |
return result if result.try(:failure?) | |
Success(input[0]) | |
end | |
end | |
Dry::Transaction::StepAdapters.register(:tap, TapStepAdapter.new) unless Dry::Transaction::StepAdapters.key?(:tap) | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
module Tesseract | |
module Transaction | |
module Steps | |
# Adds a "use" step that will call other transactions. If that | |
# transaction returns a Hash, it will be merged with the input hash and | |
# returned, otherwise the step returns the result of the transaction. If | |
# the transaction results in a Failure, that failure is returned. | |
# | |
# Usage: | |
# | |
# use MyTransaction | |
# | |
module Use | |
extend ActiveSupport::Concern | |
module ClassMethods | |
def use(transaction, **kwargs) | |
method_name = (transaction.is_a?(Class) ? transaction : transaction.class).name.intern | |
step method_name | |
define_method method_name do |params| | |
params.merge!(kwargs) | |
result = transaction.call(params) | |
result.fmap { |value| value.is_a?(Hash) ? params.merge(value) : params } | |
end | |
end | |
end | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
module Tesseract | |
module Transaction | |
module Steps | |
# Adds a "validation" step that expects the method to return a | |
# Dry::Validation validator. It runs the validator on the input | |
# arguments, and returns Success on the validation output when the | |
# validator passes, or Failure with the result containing the validation | |
# errors. | |
# | |
# Also adds a DSL method `validate` that allows you do define the | |
# validator inline and will then run it as the step. | |
# | |
# Usage with an explicit validation step: | |
# | |
# valid :my_validation | |
# step :next_thing | |
# | |
# def my_validation(params) | |
# Dry::Validation.Params do | |
# require(:name).filled | |
# optional(:age).maybe(:int?) | |
# end | |
# end | |
# | |
# def next_thing(name:, age:) | |
# end | |
# | |
# Usage with an implicit validator: | |
# | |
# validate do | |
# require(:name).filled | |
# optional(:age).maybe(:int?) | |
# end | |
# | |
# step :next_thing | |
# | |
# def next_thing(name:, age:) | |
# end | |
# | |
module Validation | |
extend ActiveSupport::Concern | |
included do |base| | |
base.send :extend, Dry::Core::ClassAttributes | |
base.defines :validator | |
end | |
module ClassMethods | |
def validate(&block) | |
validator(Dry::Validation.Params(&block)) | |
valid :validate | |
define_method :validate do |params| | |
self.class.validator.call(params) | |
end | |
end | |
end | |
class ValidationStepAdapter < Dry::Transaction::StepAdapters | |
include Dry::Monads::Result::Mixin | |
def call(operation, _options, input) | |
result = operation.call(input[0]) | |
if result.success? | |
Success(result.output) | |
else | |
Failure(result) | |
end | |
end | |
end | |
Dry::Transaction::StepAdapters.register(:valid, ValidationStepAdapter.new) unless Dry::Transaction::StepAdapters.key?(:valid) | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment