Saying stuff about stuff.

Operatic 0.7

Operatic defines a minimal standard interface to encapsulate your Ruby operations. The job of Operatic is to receive input and make it available to the operation, and to gather output and return it via a result object. This leaves you a well-defined space to write the actual code by implementing the #call method – and with so much of the ceremony taken care of it feels like writing a function but in a Ruby wrapping.

Version 0.7.0 introduces concrete Success/Failure result classes which brings a slight change to the API and may therefore impose some tweaks to your code.

In earlier versions an operation gathered data on its result object and marked the result as a success or failure. Now the operation gathers data on a separate Operatic::Data object and it’s the operation that controls the success/failure status by choosing the appropriate Success/Failure result class and initialising it with its data.

Operatic::Data could have perhaps been a plain Hash but I wanted to retain the ability to define a per-operation subclass with custom accessors (which remain accessible via the result thanks to the magic of #method_missing). I couldn’t use the Data class introduced in Ruby 3.2 because it’s frozen at the point of creation whereas in an operation data is gathered throughout execution and frozen on completion.

Having a concrete success/failure result class also makes pattern matching clearer by replacing an anonymous boolean ([true, { message: }]) with the self-documenting result class itself:

case SayHello.call(name: 'Dave')
in [Operatic::Success, { message: }]
  # Result is a success, do something with the `message` variable.
in [Operatic::Failure, _]
  # Result is a failure, ignore any data and do something else.
end

Altogether I’m pleased with the overall refactor, I think it’s resulted in a better API by more clearly defining the roles and responsibilities of each of the component parts.