Skip to content

Instantly share code, notes, and snippets.

@solnic
Last active October 3, 2022 10:15
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save solnic/66d663790a956fc811ee376249ec0447 to your computer and use it in GitHub Desktop.
Save solnic/66d663790a956fc811ee376249ec0447 to your computer and use it in GitHub Desktop.
require 'logger'
require 'byebug'
require 'dry/monads'
require 'dry/monads/do'
require 'dry/matcher/result_matcher'
module Dry
module Transaction
module Steps
class Abstract
include Dry::Monads[:result]
attr_reader :name
attr_reader :object
def initialize(name, object = nil)
@name = name
@object = object
end
def call(input)
object.public_send(name, input)
end
def bind(object)
self.class.new(name, object)
end
end
class Step < Abstract
end
class Map < Abstract
end
class Tee < Abstract
def call(input)
super
Success(input)
end
end
end
module DSL
def step(name)
__steps__ << Steps::Step.new(name)
end
def map(name)
__steps__ << Steps::Map.new(name)
end
def tee(name)
__steps__ << Steps::Tee.new(name)
end
def __steps__
@__steps__ ||= []
end
end
def self.included(klass)
super
klass.class_eval do
extend DSL
include Dry::Monads[:result, :try]
include Dry::Monads::Do.for(:call)
include Dry::Matcher.for(:call, with: Dry::Matcher::ResultMatcher)
end
end
attr_reader :steps
def initialize(*)
@steps = self.class.__steps__.map { |step| step.bind(self) }
end
def call(input)
steps.reduce(input) do |value, curr_step|
case curr_step
when Steps::Tee
curr_step.(value)
else
yield(curr_step.(value))
end
end
end
end
end
RSpec.describe Dry::Transaction do
subject(:transaction) do
Class.new do
include Dry::Transaction
step :validate
map :transform
tee :log
attr_reader :logs
def initialize
super
@logs = []
end
def validate(params)
if params.key?('email')
Success(params)
else
Failure('no email')
end
end
def transform(params)
Success(email: params.fetch('email'))
end
def log(values)
logs << values
end
def call(input)
res = yield super
res = yield extract(res)
Success(res)
end
private
def extract(params)
Success(params[:email])
end
end.new
end
it 'works with a success matcher' do
result = nil
transaction.('email' => 'jane@doe.org') do |m|
m.success do |value|
result = value
end
m.failure do |_|
end
end
expect(transaction.logs).to eql('email' => 'jane@doe.org')
expect(result).to eql('jane@doe.org')
end
it 'works with a failure matcher' do
result = nil
transaction.('foo': 'jane@doe.org') do |m|
m.success do |value|
result = value
end
m.failure do |error|
result = error
end
end
expect(transaction.logs).to eql('email' => 'jane@doe.org')
expect(result).to eql('no email')
end
end
@jslucas-root
Copy link

Can't wait to see this in a release

@solnic
Copy link
Author

solnic commented Oct 3, 2022

@jslucas-root what a funny coincident that you wrote here because I started working on something last week 🤞

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment