The Ruby and Rails communities often use a gem (package) called interactor which aims to make logic encapsulated into self-contained pieces. After writing a few interactors myself, I came across a few problems that I thought I’d share.
The messy contexts problem
Interactors are often used to wrap code that will receive an input, and return an output. Here’s an example that takes some input (email, name) and returns output (user):
Working with contexts
The way interactors handle this is by using a context object that holds both inputs and outputs. I’ve found that having one object hold both inputs and outputs gets messy very quickly.
The shared contexts problem
Interactors provide an Organizer class which allows breaking apart interactors into smaller, reuseble interactors. However, doing so often means the concept of “inputs” and “outputs” are a bit blurred.
Making sense of it
I’ve found that the more organisers are used, the more context becomes harder to manage. One way to keep track of this is making some graph that keeps track of these things. (These tables can get quite difficult to manage very quickly.)
The reusability problem
Interactors are often touted for being useful for managing reuseable pieces of logic. Here’s one example of business logic that might be useful in many places.
Not easily reuseable
While this logic is nicely self-contained, it’s not easily reuseable in an organizer that might take in a different input shape.
The error handling problem
Nested interactors break error handling. Because Interactor Organisers enforce the use of the same context fields, it’s often better to call interactors directly from other interactors.
Errors are swallowed by default
Unfortunately, the code above wouldn’t work, because errors are swallowed by default even when using call!. The fix here is to intentionally rescue Interactor errors and re-raise them using context.fail!.
Problem: Interactor hasn’t been maintained
The last release of the interactor and interactor-rails gems are in 2019. There has been plans for a v4 release but those have been put on hold as of Dec 2021. There are still some open issues, such as with the use of nested interactors mentioned above.
Problem: type checking is difficult
Ruby 3.0 comes with static type checking. However, with context being shared as both input and output, type definitions can become ambiguous. Interactors don’t support static typing yet, but consider this hypothetical example below that shows how difficult it would be to add types.
Here are a few ideas I had on how to work around these limitations with interactors.
Organisers impose strict restrictions on how code is supposed to be structured, and I feel that the restrictions don’t necessarily make for better code. It’s not worth the extra effort in my opinion, and nested interactors are a more reasonable alternative.
Consider validating inputs
Gems like dry.rb allows writing runtime validation for types. Static compile-time type checking is most ideal in my opinion (eg, Ruby type signatures or Sorbet), but runtime validation is the closest alternative.
Catch nested interator errors, or avoid fail!
Many devs I talked to who uses interactors have written some code to fix the shortcomings of fail! errors being swallowed. This snippet might be good to extract into somewhere easy to reuse:
Document inputs and outputs
Since it’s easy to get lost in what parts of a context is input or output, I found that it helps to document what each interactor’s inputs and outputs are.
Use plain Ruby instead of interactors
Many scenarios involving interactors can be done using plain Ruby modules. Consider if the addition of a gem like interactor is worth it over writing service objects in a different way.
Thanks for reading! I'm Rico Sta Cruz, I write about web development and more. Subscribe to my newsletter!