On a recent project we made extensive use of the ActiveInteraction gem.
Its a Ruby implementation of the command pattern that works well with Rails for validating inputs, performing isolated pieces of work, and cleanly returning results or errors.
The project was a JSON API which interacted with a number of external services.
We were tasked with getting the project started, establishing some conventions and patterns, and then handing it back to an internal team to finish development.
At the end of the engagement, our team was happy with the code and the client team was able to hit the ground running.
ActiveInteraction
ActiveInteraction is “an implementation of the command pattern in Ruby”. This post which announced the gem does a nice job of explaining the motivation behind the gem. It came from a rails project which was running into a fairly common problem: bloated controllers. The developers wanted to leave their models concerned with persistence and keep their controllers concerned with request logic. But where to put the business logic that the application was actually supposed to implement? Enter the interactor pattern, which houses application specific business rules in interactor objects, and isolates details like which database the application uses, or whether it renders the data as html views or json payloads. The interactor is responsible for validating what input the application accepts, performing some work, and returning whether the work was successful or not.
ActiveInteraction takes this concept and implements it for use in a Rails environment. ActiveInteraction objects respond to the same validations and errors API that ActiveModel does, so they can be seamlessly integrated into Rails forms. One of our favorite features was composition, which lets you call an interactor from within another interactor. If the called interactor fails, it will terminate and add its errors to the caller. This lets you nest interactors several layers deep without the top level needing to handle lower level failures or know anything about the interactor that caused it.
Our uses
We found the command pattern really handy, and used it in several different ways: for controller actions, to handle state transitions, to wrap external services, and for use as miscellaneous service objects. I’ll give examples of each, in a fictional domain, and provide some assessment as to whether it ended up being a good use case for the pattern or not.
1) Controller Actions:
Every public controller method was backed by a corresponding interaction defined in app/controllers/controller_actions/MODEL_NAME/ACTION_NAME
. A controller action takes inputs from the controller and returns a serialized object.
Our controllers actions looked like:
def create
inputs = create_params
outcome = ControllerActions::Animals::Create.run(inputs)
render_outcome(outcome, success_status: 201)
end
Which was backed by an interaction like:
module ControllerActions
module Animals
# Interaction for AnimalsController#create
class Create < ::ControllerActions::Base
date_time :birthday, default: nil
string :name
string :favorite_food
def execute
serialize(animal, with: AnimalSerializer)
end
private
def animal
@animal ||= compose(
::Animals::Create,
name: name,
favorite_food: favorite_food,
birthday: birthday
)
end
end
end
end