Do notation and matcher combined for call method not working

Hi there,

Dry-rb libraries beginner here, I was trying to do this:

class Create
  include Dry::Transaction::Operation
  include Dry::Monads::Do.for(:call)

  def call(params)
    result = yield some_other_call(params)

    # some logic with result
  end
end

But I got no block given (yield) error when running the tests.

This discussion LocalJumpError: no block given (yield) in github actions was a hint that it might not work to have do notation for the same :call method, because Dry::Transaction::Operation defines a Matcher for the same :call method with include Dry::Matcher.for(:call, with: Dry::Matcher::ResultMatcher).

I could not find an explanation for this. Why is not possible to use both?

Gems:

  • dry-monads 1.3.5
  • dry-transaction 0.13.2

Thanks!

Welcome! My first question is: why are you combining dry-monads and dry-transaction?

dry-transaction’s future is uncertain. It was deprecated, then un-deprecated, and hasn’t really seen any activity since.

Part of the reason for this is that dry-monads + dry-auto_inject basically provide all the things you get with dry-transaction with far greater flexibility.

If there are legacy reasons why you need dry-transaction, it will probably receive at least maintenance releases for the time being, but if you are starting fresh I would recommend just using do-notation.

That said, here’s the most basic approach to a do-notation Operation:

#!/usr/bin/env ruby
# frozen_string_literal: true

require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'dry-monads'
end

require 'dry/monads'

M = Dry::Monads

class Create
  include Dry::Monads[:result]
  include Dry::Monads::Do.for(:call)

  def call(params)
    result = yield some_other_call(params)

    Success(result)
  end

  private

  def some_other_call(params) = Success(params)
end

create = Create.new

case create.({ foo: "Bar" })
in M::Success(attributes)
  puts attributes.inspect
in M::Failure(err)
  raise err
end

Hi, thanks for your time!

I consider the Create class an operation since I am including Dry::Transaction::Operation, which is later used in a transaction as a step.

The application we have, I would not necessarily say it is legacy. We have top-level business logic in dry-transactions, which are constructed from other transactions and operations.

I was surprised that I could not use the do notation in the same :call method and couldn’t find an explanation anywhere. I really like how transactions look and read, it’s way more natural than having do notation.

I ended up having something like this:

module Api
  class Operation
    def self.inherited(subclass)
      super
      subclass.include Dry::Transaction::Operation
    end
  end
end

class Create < Api::Operation
  include Dry::Monads::Do.for(:create)

  def call(params)
    create(params)
  end

  private

  def create(params)
    result = yield some_other_call(params)

    # some logic with result that returns Success/Failure
  end
end

Moved do notation for the private :create method.