Often when working with Rails we hear about fatty controllers and skinny models. We also come across complex user interactions where we need to perform tasks sequentially on particular user action.
There are various ways to solve the above problems using service objects, form objects, policy objects, etc.
One such way to refactor code is to use Interactor gem.
We need to add the interactor gem in our Gemfile and execute
bundle install
Interactors
As seen in the above example
-
We created a small class
AddNumbers
and includedInteractor
in the class. -
We passed two arguments to
call
methoda
andb
. They can be accessed inside the interactor using thecontext
object ascontext.a
andcontext.b
. -
We also set the
sum
inside thecontext
usingcontext.sum = context.a + context.b
. -
Basically
context
contains everything the interactor needs to do its work. -
AddNumbers.call(a: 1, b: 2)
returns acontext
object and we can check if the above code executed successfully or not by checkingresult.failure?
-
We can check the sum by executing
result.sum
-
If something goes wrong in the interactor we can flag the context as failed by
context.fail!
and also set the error message ascontext.fail!(error: "Error message")
. Above example can be modified as
The interactor
also provides before
, around
and after
hooks. We can think
of them to be similar to before_action
, around_action
and after_action
used
in controllers.
To avoid rescue
in the above example we can add a before
hook to check if the
numbers are integer or not and fail the context accordingly.
We can initialize sum
to 0 as shown below.
The above interactor is a single-purpose unit interactor. A complex system might involve multiple interactors which need to be called in a sequence.
Organizers
To execute a sequence of interactors we can use organizers provided by this gem. Imagine a situation where we need to pull data of users from different social media accounts and import it into our database.
The basic steps we might follow to import the users will be:
- Fetch data from the social media account.
- Convert data into standard format which can be imported to our system.
- Import data into our system.
Each of the above steps can be seen as a single unit and we can create interactors for each step. The above steps can be grouped in an organizer as below:
We have created three interactors inside ImportSocialMediaUsers
organizers.
The three interactors will be called one after another in a sequence.
If any interactor fails then next interactors are not called and the respective error is returned.
So to check if the flow was executed successfully we can use #success?
or #failure?
method as below
As per the above example, we observe Facebook.new
is passed as a client to the
organizer ImportSocialMediaUsers
.
So our Facebook
class will look like this
If we decide to integrate Twitter
, and import users from Twitter
we just need
to implement the Twitter
class and pass its instance to the ImportSocialMediaUsers
organizer as below:
The interactor and organizers help to refactor the code into smaller single units making them easily testable and reusable.