A place for your business logic in Rails
It’s a well spread knowledge that Controller shouldn’t have too much business logic, actually it’s better if you keep any kind of business logic away from your controller.
That’s great, but where to put all that business logic? The easy answer is in the models, but as your application grows in complexity so grows your models and faster than you think you now have bunch of Fat Models.
There are a lot of ways to fix this, primarily you should remove some of the logic of your models to other classes, but if you look into your Rails project for example you won’t see anything other than: Controller, Mailers, Views, Models and Assets.
So if it won’t go inside the Controller nor the Models where should we put all those logics?
There is this really well written blog post from code climate with 7 Patterns to Refactor Fat Models which is great, but still… I wanted something more than just moving some of my model methods around.
That’s when I heard of the Interactor gem
An interactor is a simple, single-purpose object.
Interactors are used to encapsulate your application’s business logic. Each interactor represents one thing that your application does.
We’ve been using interactors at Worldpackers for more than a year now and I can say it really helps :).
The trick is… each interactor should do ONE THING
A lot of the things we do nowadays do more than one thing per request…
At Worldpackers when someones sends a message you need to do the following:
- Create the new message
- Notify the receiver by email that there is a new message
For some time that was okay… our controller was like.
Not great… but was Okay.
But then our platform got more complex, instead of just sending an email to notify we since we developed apps for Android and iOS we need to also send push notifications, for both systems.
That’s getting ugly, and if you think about the SRP (Single Responsability Principle) it surely doesn’t have a single responsability:
- Permiting params and handling requests
- Creating the message
- Send Email
- Send iOS push notification
- Send Android push notification
Keeping classes with only one responsability is hard but hey! You don’t need to give up, and put everything in the controller right?
So technically it’s possible to move this code to the model but, models that have the ability to send emails and push notifications give me the chills.
That’s why I think the interactors are great.
Making an Interactor
You can extract the send notification code into an Interactor which is basically a common ruby class also know as PORO (Plain Old Ruby Object).
You surely can say this class that sends an email and two different pushes doesn't have a single purpose. Well I won't argue about that, it's up to you and how you want to split your code.
But you need to need to add
include Interactor and define a method named
To call the interactor you need to
And that’s it! Well almost… our interactor still needs the
destination and the
Passing parameters to an interactor
We can send params to an interactor using a
All the params that you send this way to an Interactor are accessible inside a variable called
Interactors reuse is nice and easy :)
Another great thing that we found about interactors is that, since they have no state and should do one task it makes it easy to reuse the interactors.
When we started our mobile apps development we needed to create another set of controllers to handle the same things that we already have in the web as an RESTful API.
So we just needed to use the same interactor in our new controller
Creating another interactor
So as you can see we still have some code duplication
Those two lines above exists in the
Api::MessagesController and in the
So we can extract this call to another interactor.
Now, you need to know if the
conversation.messages.create was created successfully.
But if you call
Your return will be an
Interactor::Context and not a
User , the same way you pass a params to the interactor internal
context you can add things to your context that will be returned to the caller.
context = CreateConversationMessage.call(params: create_params)
And to access the result you need to:
Here I prefer to use
result as the name of the variable that receives the interactor result.
Did it fail?
You may be wondering why you need a gem for this? After all everything we created so far are just simple PORO classes.
Well the thing is this gem has also a way to control if your interactor
call execution was a failure or not.
To define your Interactor execution as a failure you need:
This way you mark the Interactor response as a fail and also pass the exception as a part of the response
This way you can:
Another concept that is available in the Interactor gem is the
Organizer, which in a very short explantion is a way to chain interactor calls when you need to do multiple things, there are also hooks to execute code before, after and around you Interactor.
Interactors are now my to go pattern for writing business logic. I'm also using it in our Android App and it works pretty well too.
I think this post is long enought so I’m finishing here, but there are more things that we can do with Interactors. If you found this helpful and want me to written about the organizers and the around hooks please leave a comment telling me that :).
Special thanks to Leandro TK and Felipe Besson for reviewing this post :)