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:

  1. Create the new message
  2. 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:

  1. Permiting params and handling requests
  2. Creating the message
  3. Send Email
  4. Send iOS push notification
  5. 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 namedcall .

To call the interactor you need to

And that’s it! Well almost… our interactor still needs the destination and the message

Passing parameters to an interactor

We can send params to an interactor using a hash

All the params that you send this way to an Interactor are accessible inside a variable called context

https://gist.github.com/diegoy/ccd88f8146ac656d427741bd200f802e

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 MessagesController

So we can extract this call to another interactor.

https://gist.github.com/diegoy/b80972a7dd3e8b27cd85cb42c6f9ee01

Handling returns

Now, you need to know if the conversation.messages.create was created successfully.

But if you call

CreateConversationMessage.call(params: create_params)

Your return will be an Interactor::Context and not aUser , 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:

context.message

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 interactorcall 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 theOrganizer, 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.

Wrapping up

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 :)