SkillAgentSearch skills...

Interactor

Interactor provides a common interface for performing complex user interactions.

Install / Use

/learn @collectiveidea/Interactor
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Interactor

Gem Version Build Status Maintainability Test Coverage Ruby Style Guide

Getting Started

Add Interactor to your Gemfile and bundle install.

gem "interactor", "~> 3.0"

What is an Interactor?

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.

Context

An interactor is given a context. The context contains everything the interactor needs to do its work.

When an interactor does its single purpose, it affects its given context.

Adding to the Context

As an interactor runs it can add information to the context.

context.user = user

Failing the Context

When something goes wrong in your interactor, you can flag the context as failed.

context.fail!

When given a hash argument, the fail! method can also update the context. The following are equivalent:

context.error = "Boom!"
context.fail!
context.fail!(error: "Boom!")

You can ask a context if it's a failure:

context.failure? # => false
context.fail!
context.failure? # => true

or if it's a success.

context.success? # => true
context.fail!
context.success? # => false

Dealing with Failure

context.fail! always throws an exception of type Interactor::Failure.

Normally, however, these exceptions are not seen. In the recommended usage, the controller invokes the interactor using the class method call, then checks the success? method of the context.

This works because the call class method swallows Interactor::Failure exceptions. When unit testing an interactor, if calling custom business logic methods directly and bypassing call, be aware that fail! will generate such exceptions.

See Interactors in the Controller, below, for the recommended usage of call and success?.

Hooks

Before Hooks

Sometimes an interactor needs to prepare its context before the interactor is even run. This can be done with before hooks on the interactor.

before do
  context.emails_sent = 0
end

A symbol argument can also be given, rather than a block.

before :zero_emails_sent

def zero_emails_sent
  context.emails_sent = 0
end

After Hooks

Interactors can also perform teardown operations after the interactor instance is run.

after do
  context.user.reload
end

NB: After hooks are only run on success. If the fail! method is called, the interactor's after hooks are not run.

Around Hooks

You can also define around hooks in the same way as before or after hooks, using either a block or a symbol method name. The difference is that an around block or method accepts a single argument. Invoking the call method on that argument will continue invocation of the interactor. For example, with a block:

around do |interactor|
  context.start_time = Time.now
  interactor.call
  context.finish_time = Time.now
end

With a method:

around :time_execution

def time_execution(interactor)
  context.start_time = Time.now
  interactor.call
  context.finish_time = Time.now
end

NB: If the fail! method is called, all of the interactor's around hooks cease execution, and no code after interactor.call will be run.

Hook Sequence

Before hooks are invoked in the order in which they were defined while after hooks are invoked in the opposite order. Around hooks are invoked outside of any defined before and after hooks. For example:

around do |interactor|
  puts "around before 1"
  interactor.call
  puts "around after 1"
end

around do |interactor|
  puts "around before 2"
  interactor.call
  puts "around after 2"
end

before do
  puts "before 1"
end

before do
  puts "before 2"
end

after do
  puts "after 1"
end

after do
  puts "after 2"
end

will output:

around before 1
around before 2
before 1
before 2
after 2
after 1
around after 2
around after 1

Interactor Concerns

An interactor can define multiple before/after hooks, allowing common hooks to be extracted into interactor concerns.

module InteractorTimer
  extend ActiveSupport::Concern

  included do
    around do |interactor|
      context.start_time = Time.now
      interactor.call
      context.finish_time = Time.now
    end
  end
end

An Example Interactor

Your application could use an interactor to authenticate a user.

class AuthenticateUser
  include Interactor

  def call
    if user = User.authenticate(context.email, context.password)
      context.user = user
      context.token = user.secret_token
    else
      context.fail!(message: "authenticate_user.failure")
    end
  end
end

To define an interactor, simply create a class that includes the Interactor module and give it a call instance method. The interactor can access its context from within call.

Interactors in the Controller

Most of the time, your application will use its interactors from its controllers. The following controller:

class SessionsController < ApplicationController
  def create
    if user = User.authenticate(session_params[:email], session_params[:password])
      session[:user_token] = user.secret_token
      redirect_to user
    else
      flash.now[:message] = "Please try again."
      render :new, status: :unprocessable_entity
    end
  end

  private

  def session_params
    params.require(:session).permit(:email, :password)
  end
end

can be refactored to:

class SessionsController < ApplicationController
  def create
    result = AuthenticateUser.call(session_params)

    if result.success?
      session[:user_token] = result.token
      redirect_to result.user
    else
      flash.now[:message] = t(result.message)
      render :new, status: :unprocessable_entity
    end
  end

  private

  def session_params
    params.require(:session).permit(:email, :password)
  end
end

The call class method is the proper way to invoke an interactor. The hash argument is converted to the interactor instance's context. The call instance method is invoked along with any hooks that the interactor might define. Finally, the context (along with any changes made to it) is returned.

When to Use an Interactor

Given the user authentication example, your controller may look like:

class SessionsController < ApplicationController
  def create
    result = AuthenticateUser.call(session_params)

    if result.success?
      session[:user_token] = result.token
      redirect_to result.user
    else
      flash.now[:message] = t(result.message)
      render :new
    end
  end

  private

  def session_params
    params.require(:session).permit(:email, :password)
  end
end

For such a simple use case, using an interactor can actually require more code. So why use an interactor?

Clarity

We often use interactors right off the bat for all of our destructive actions (POST, PUT and DELETE requests) and since we put our interactors in app/interactors, a glance at that directory gives any developer a quick understanding of everything the application does.

▾ app/
  ▸ controllers/
  ▸ helpers/
  ▾ interactors/
      authenticate_user.rb
      cancel_account.rb
      publish_post.rb
      register_user.rb
      remove_post.rb
  ▸ mailers/
  ▸ models/
  ▸ views/

TIP: Name your interactors after your business logic, not your implementation. CancelAccount will serve you better than DestroyUser as the account cancellation interaction takes on more responsibility in the future.

The Future™

SPOILER ALERT: Your use case won't stay so simple.

In our experience, a simple task like authenticating a user will eventually take on multiple responsibilities:

  • Welcoming back a user who hadn't logged in for a while
  • Prompting a user to update his or her password
  • Locking out a user in the case of too many failed attempts
  • Sending the lock-out email notification

The list goes on, and as that list grows, so does your controller. This is how fat controllers are born.

If instead you use an interactor right away, as responsibilities are added, your controller (and its tests) change very little or not at all. Choosing the right kind of interactor can also prevent simply shifting those added responsibilities to the interactor.

Kinds of Interactors

There are two kinds of interactors built into the Interactor library: basic interactors and organizers.

Interactors

A basic interactor is a class that includes Interactor and defines call.

class AuthenticateUser
  include Interactor

  def call
    if user = User.authenticate(context.email, context.password)
      context.user = user
      context.token = user.secret_token
    else
      context.fail!(message: "authenticate_user.failure")
    end
  end
end

Basic interactors are the building blocks. They are your application's single-purpose units of work.

Organizers

An organizer is an important variation on the basic interactor. Its single purpose is to run other interactors.

class PlaceOrder
  include Interactor::Organizer

  organize CreateOr

Related Skills

View on GitHub
GitHub Stars3.5k
CategoryDevelopment
Updated3d ago
Forks215

Languages

Ruby

Security Score

95/100

Audited on Apr 2, 2026

No findings