Ever struggled with where where to stick your Rails app logic? or had controllers and models getting big? When I first started with rails the MVC seemed like it covered it all; but as my application logic started to get complex, and it was less clear where specific pieces of logic should live. Does a ‘process payment’ method live on a controller or a model? It really felt like there should be another standard rails layer between the controller and the model to put my code. The Interactor gem provides a simple pattern to organize your application logic, and keep your app from feeling bloated.
The interactor gem provides a pattern for creating single-purpose objects to contain your business logic.
I like the put them in
app/interactors and it even feels just like rails.
Interactors have a single public method
call that takes a hash as an argument.
This hash gets converted into a
context, should have all the information the interactor needs to complete this piece of logic.
The interactor can add information to the context, which can be useful for returning values back to what called the interactor.
The most useful part about the context is that it responds to
context.success? and you can
Which will return from the interactor.
A basic interactor for purchasing tickets could look something like this:
class PurchaseTicket include Interactor def call if context.user? && context.concert? ticket = purchase_ticket(context.user, context.concert) context.ticket = ticket else context.fail!(message: "Need a user and concert to purchase a ticket!") end end private def purchase_ticket(user, concert) # create ticket logic end end
It is an object that includes
Interactor and defines a method
The context has the information required to purchase a ticket,
and the interactor has all the logic require to handle that action.
What does this mean for your rails app? You can keep your business logic out of your controllers and models. In the case above, the controller method could look something like this:
class TicketsController < ApplicationController def create result = PurchaseTicket.call(ticket_params) if result.success? render :show, result.ticket else flash[:error] = result.message render :new end end end
This controller method does not need to know how to purchase a ticket. Instead it needs to know if a ticket was purchased successfully, and what views to render. This also helps keep the Ticket model clean:
class Ticket < ActiveRecord::Base belongs_to: user belongs_to: concert validates: price, presence: true end
This model never has to know how tickets are purchased, or who does the purchasing. Its responsibility can stick to just describing out applications data.
The Interactor gem is simple enough that you could pick it up and improve your app right now. Once you get started, figuring out how to keep controllers and models skinny will not longer be a question. There are a bunch of other hooks and patterns to the gem I recommend you check out (like chaining interactors)!