dry-rb / dry-transaction

Business transaction DSL
http://dry-rb.org/gems/dry-transaction
MIT License
468 stars 55 forks source link

around steps and Dry::Transaction::Operation don't get along #114

Open dmaze opened 5 years ago

dmaze commented 5 years ago

I've gotten into the habit of writing dry-transaction operations as individual classes that include Dry::Transaction::Operation. (It avoids the scary 5-letter "m" word.) If you use this in an operation class that's intended to be an around step, like so:

class First
  include Dry::Transaction::Operation
  def call(input)
    yield Success(input + 1)
  end
end

You will get a backtrace when you run it. https://gist.github.com/dmaze/ea78510a8870089be510a55d8fb38a8e is a more complete reproduction script. Running that prints out:

first 1
done with first
LocalJumpError: yield called out of block
              call at ./dry-transaction-operation.rb:20
     block in call at .../gems/dry-matcher-0.7.0/lib/dry/matcher.rb:35
              call at org/jruby/RubyMethod.java:129
              call at .../gems/dry-transaction-0.13.0/lib/dry/transaction/callable.rb:33
              call at .../gems/dry-transaction-0.13.0/lib/dry/transaction/step_adapters/around.rb:12
              call at org/jruby/RubyMethod.java:129
              call at .../gems/dry-transaction-0.13.0/lib/dry/transaction/step_adapter.rb:41
     block in call at .../gems/dry-transaction-0.13.0/lib/dry/transaction/step.rb:52
    with_broadcast at .../gems/dry-transaction-0.13.0/lib/dry/transaction/step.rb:61
              call at .../gems/dry-transaction-0.13.0/lib/dry/transaction/step.rb:52
  block in compile at .../gems/dry-transaction-0.13.0/lib/dry/transaction/stack.rb:19
              bind at .../gems/dry-monads-0.4.0/lib/dry/monads/right_biased.rb:48
  block in compile at .../gems/dry-transaction-0.13.0/lib/dry/transaction/stack.rb:19
              call at .../gems/dry-transaction-0.13.0/lib/dry/transaction/stack.rb:12
              call at .../gems/dry-transaction-0.13.0/lib/dry/transaction/instance_methods.rb:28
            <main> at ./dry-transaction-operation.rb:46

More specifically, it looks like the Operation mixin wraps #call in a dry-matcher, and the generated method doesn't pass the block up to the user code (it looks like it might repurpose it for something else).

An easy enough workaround is to include Dry::Monads::Result::Mixin instead.

I'm running this with jruby-9.2.0.0 and the gem versions shown in the backtrace, should that turn out to matter.

TastyPi commented 1 year ago

I found a workaround - dry-transaction doesn't actually care that the object includes Dry::Transaction::Operation specifically, it just uses the call method. So you can instead replace it with Dry::Monads[:result] directly:

class First
  include Dry::Monads[:result]
  def call(input)
    yield Success(input + 1)
  end
end