Rails Interactor Pattern - Part 2
In part 1 of this series we've looked on the basics of the interactor pattern, created an interactor dealing with the Mailgun API and refactored it to use a
Result class to signalize success and failure.
In this part, we want to add some method sugar to our solution, so that we don't have to deal with the setup process too much.
Let's start with the interactor we created last time:
class CreateMailgunDomain include HTTParty base_uri 'api.mailgun.com' pattr_initialize :domain def call response = post('/domains', name: domain) if response.code == 200 Result.new(true, response) else Result.new(false, response) end end private # Other parts omitted end
Instead of calls to
Result#newI would rather like to use something closer to natural language, like
errored. Let's try that: We'll refactor
CreateMailgunDomain#call to sound nicer:
def call response = post('/domains', name: domain) if response.code == 200 response else fail!(response) end end
That looks better, in my opinion. If the call to the
/domains endpoint is successful (meaning if it returns a
200 OK), we will return the response. If it weren't, we would call
fail!. Please note that in Ruby,
fail is a reserved word and used as a synonym to
If we run that code now, it would fail because we haven't implemented our
fail! method yet. Let's do that:
def fail!(object = nil) fail Failure, object end class Failure < StandardError attr_reader :object def initialize(object = nil, message = nil) super(message) @object = object end end
We will just call the ordinary
fail method with a new
Failure takes and optional object (our response) and an optional message, which it inherits from
Now, how do we get this code into our
CreateMailgunDomain interactor? There are various options for that: we could create an
Interactor class and let our custom interactor inherit from it. Or we could create a
module which we can
include into our interactor. For this article, I'll go with solution #2, the module:
module Interactor def self.included(base) base.extend(ClassMethods) end def fail!(object = nil) self.class.fail!(object) end module ClassMethods def call(*args) object = new(*args).call Result.new(true, object) rescue Failure => failure Result.new(false, failure.object) end def fail!(object = nil) fail Failure, object end end end
call method does is, it creates a new instance of the class it was included in and calls
call on the instance. If the instance returns a value, it wraps that one in a new instance of the
Result class, sets the
success attribute to
true (via the constructor) and returns it.
If during the processing a
Failure error is raised, it will also create a new instance of the
Result class, but set its
success attribute to
false and return it along with the object from the failure.
We also added a new instance method onto the class, called
fail!. That's the once which is called by our
CreateMailgunDomain interactor above. It just passes the arguments it receives on to the same-name class method.
If you're unsure what's happening with the internal
ClassMethods module, please check out this article here: Include vs Extend in Ruby.
Now we can finally include this module in our interactor and use it:
class CreateMailgunDomain include HTTParty include Interactor base_uri 'api.mailgun.com' pattr_initialize :domain def call response = post('/domains', name: domain) if response.code == 200 response else fail!(response) end end private # Other parts omitted end # Use it like so: CreateMailgunDomain.call('example.com')
If you are interested in the complete code, I've created a Gist with all the parts put together.
Alright, that's it for today! I hope you enjoyed this two-part article about interactors. As always, I'm happy for feedback and new ideas - just leave a comment!