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?