Validators for Interactors
In our previous blog post, we talked about how we started to use the Interactor paradigm for our service objects, with each service designed to have a single purpose.
Here’s an example of an Interactor in action:
Any parameter passed to the Interactor, is made accessible within the Interactor as a method on the context
object.
We can also attach more objects to the context
object if needed.
Here’s an example:
Going back to the AuthenticateUser
interactor, it has one purpose, to authenticate the user, and that seems to be pretty clear.
However, what’s not so easy to determine, is the list of parameters that AuthenticateUser
depends on — email
, password
, token
, session
, key
, etc.
A developer would find it difficult to understand what parameters are needed for an Interactor, and what parameters are being added to the Interactor on runtime.
In a plain old Ruby object (PORO), it is relatively easier to understand the dependencies of the class by inspecting the initialize
method.
Here’s an example of a PORO for the same AuthenticateUser
scenario:
So, the lack of an initialize
equivalent method in the Interactor will eventually turn into a big “code readability” issue when a developer opens an Interactor and has to parse every line to understand it’s dependencies.
How can a developer quickly and easily figure out what parameters the Interactor needs?
a. Documentation
The developer can document the dependencies as comments. This however, isn’t testable and requires the developer who’s writing, and the developer who’s reading the code, to be diligent.
b. Use delegate
As mentioned in our previous post, we used the delegate
method to avoid the code readability issue, but this approach did not allow us to actually validate the parameters going into an interactor instance.
c. Use before
hook along with a method that does validation of the context object.
Interactor allows us to define before, around and after hooks that will be executed before, around and after the call
method.
We can define a before hook to validate the presence of parameters and raise an ArgumentError
if any parameter is missing.
Here’s an example:
This will do the job. The developer who’s going through the code now has one place to look up dependencies, and it’s testable too.
But we can make it better, with the help of some Rails magic.
d. Use ActiveModel::Validations
This module gives us ways to define our validations even better, stronger, that are, out of the box easier to define, and understand.
This requires the activemodel
gem, however, we think the benefits outweigh the dependency.
Here’s an example:
As an added functionality, the requires
method also delegates any variable mentioned in the method to the context
object so that we do not have to explicitly use context.variable
anymore.
This is possible even today with the ActiveSupport’s delegate
method, but since we are mentioning the required parameters in the Interactor anyway, explicit delegation can be avoided.
Example:
Conclusion
We finally decided to go with the ActiveModel::Validations
approach because of the extra flexibility it gives us in terms of custom validations (like validate :email_is_internal
)
Hope you enjoyed reading about Interactors and Validators. We’re always exploring new things at Reflektive and would like to know how you perform validations for your service objects. Let us know in the comments below!