Allow #step to be given a name, surfaced to #on_failure#42
Conversation
03b9a2c to
eeeb902
Compare
eeeb902 to
224b204
Compare
| # hook, supporting several signatures: | ||
| # | ||
| # def on_failure(failure) | ||
| # def on_failure(failure, method_name) |
There was a problem hiding this comment.
I wonder if we could find a more descriptive name instead of method_name. I thought about operation_name but it'd also be confusing given we define the class as a Dry::Operation 🤔
| # attrs = step validate(input) | ||
| # user = step persist(attrs) | ||
| # step notify(user) | ||
| # attrs = step :validate, validate(input) |
There was a problem hiding this comment.
Do you think keeping the value as the first positional parameter would read better?
I’m thinking about something like:
step validate(input), as: :validateNot a strong opinion, but it could also make refactoring easier when we want to provide a name in a second thought, since appending something to a line is always easier than inserting something in the middle for most IDEs 🙂
There was a problem hiding this comment.
This reads more naturally. Is it possible to use reflection to infer the method's name and omit the symbol altogether, making it optional?
step validate(input) # on_failure gets :validate as step_name
step persist(input), as: :save # on_failure gets :save as step_name
I have been using my own helper method for this called either and I've landed on three useful forms: functional mapping, because sometimes I want to transform the errors somehow. For instance, a Dry::Types error result returns multiple values and I want to splat them into the Failure tuple. attrs = either validate(input), ->(*err) { Failure[:validation, *err] }Sometime's I'm doing just a sanity check, and I don't need to lazy-process the error result, so being able to eagerly pass a Failure is useful: either T::Callable.try(discriminator), Failure[:type, discriminator]Finally, the vast majority of cases I just want to wrap a Failure in a tuple and assign a name. I do it this way: attrs = either validate(input), error: :validation
# produces: Failure[:validation, Dry::Schema::Result]This is most commonly done to mark HTTP errors as Doing this inline, I've never felt the need for something like |
Allow steps to be given an optional name, so that the
on_failurehook can differentiate between steps.Now you can define steps like this:
With this done, you can use a new form on
on_failure, which now acceptsstep_name:andmethod_name:as optional keyword arguments:Knowing the step name inside
on_failuremakes it much easier to add step-specific failure handling, like we see above. You can supply one or both ofmethod_name:andstep_name:to this method — we do params introspection behind the scenes to figure out what to pass.Existing
on_failureparams signatures work exactly as before:I expect this may be just the first step (🥁) in use of step names. Down the track we might want to consider their usage in other areas. For example, a nicer DSL for per-step failure handlers, standard failure structures "tagged" with the step name prepended, per-step "recovery" flows, instrumentation hooks, etc.
For starters, though, we keep this simple and focused on improving the facilities we already have.
To this end, while we're here, enhance the validation plugins so that it provides a
:validationstep name when validation fails.Implementation notes
on_failurevia thethrowpayload, which now becomes aStepFailuredata class, holding both the failure and the step name.stepsblock API (in part an internal concern, but also a piece of lower-level public API) now invokeson_failurewhen a failure is encountered. This closes a gap in its earlier functionality, and allows us to continue using it from withinClassContext::StepsMethodPrepender.on_failurehas now become more complex with our params introspection, so that logic is now moved to a separateClassContext::FailureHookDispatchermodule.