We’re using Dry::Transaction extensively within our Rails application, specifially for controller actions and background jobs. Most of our controller actions invoke a Transaction, and then render a response/status based on the Result of the Transaction. The bulk of Transactions invoked from the controller action have a similar set of steps (eg :authorize, :validate, etc…). To dry up the error handling code, we extracted the common failure handler to a separate method:
# Controller action
def update
  txn.call(params) do |on|
    on.success           { ... }
    on.failure(:special) { ... }
    on.failure           { handle_common_failures(on) }
  end
end
# Shared helper
def handle_common_failures(on)
  on.failure(:authorize) { render status: :not_found}
  on.failure(:validate)  { |errors| render error: errors, status: :unprocessable_entity }
  on.failure do |err|
    status = case err
             when ActiveRecord::RecordInvalid, Dry::Schema::Result
               :unprocessable_entity
             when ActiveRecord::RecordNotFound
               :not_found
             else
               :forbidden
             end
    render error: err, status: status
  end
end
However, this doesn’t work out-of-the box with Dry::Matchers, because the @output of each branch gets checked if its defined? (dry-matcher/evaluator.rb at master · dry-rb/dry-matcher · GitHub). What’s happening is that in the controller action the on.failure gets invoked, which defines @output (to nil) then yields to our helper. Then when we try to invoke a different set of matchers in the helper, @output is already defined, so none of those matcher branches are attempted.
We’ve monkey-patched Dry::Matcher::Evaluator#method_missing to separately check if we’ve “completed” a matcher branch (with the @matched ivar), rather than if it has been started:
class Dry::Matcher::Evaluator 
  def method_missing(name, *args, &block) 
    kase = @cases.fetch(name) { return super }
    @unhandled_cases.delete name
    return @output if @matched
    kase.(@result, args) do |result|
      @output = yield(result)
      @matched = true
      @output
    end
  end
end
We’ve been running with this monkey-patch for years (June 2018 according to the git history). I’m wondering if I opened a PR with the implementation in our monkey-patch, is it something that would be accepted upstream?