Interactorメモ
Interactorレイヤを追加するGem。interactor 3.1.2、interactor-rails 2.2.1 で確認。
サービスレイヤ(ServiceLayer)パターン(PofEAA)でも書いているが個人的には、このGemは使わずにプレーンなRubyクラスでServiceレイヤを構築すれば良いと考えている。
2021年3月現在、このGemは更新が停滞している。2014年以降、二回しかリリースがなされていないし、その間にRubyファイルが変更されたのは6コミットだけである。v4の開発も2017年3月から放置されている。2019年12月29日以降活動はなく簡単なtypo修正のPRも放置されている。
また、1クラス1アプリケーションロジックになるため、無闇にクラス名が長くなりやすい。関連するアプリケーションロジック(例えばCRUD操作など)を1つのクラスにまとめることができない。
インストール
以下のgemを追加する。
gem 'interactor-rails', '~> 2.0'
interactor-railsはRailsにInteractorのgeneratorを追加するgem。generatorが不要ならgem 'interactor', '~> 3.0'でよい。
Generator
bin/rails g interactor
bin/rails g interactor Interactor名でapp/interactors/の中にファイルが作成される。
bin/rails g interactor:organizer
bin/rails g interactor:organizer Interactor名 Organize先のInterractorクラス名1 Organize先のInterractorクラス名2 ...でapp/interactors/の中にorganizeが追加されたファイルが作成される。
organizeについては後述。
Context
Interactorクラスの中ではattr_readerでcontextが宣言されており、このオブジェクトがInteractorクラスの外と中を繋ぐデータコンテナになる。
外からのデータはInteractorClass.call(key: value, ...)のようにcallメソッドの引数に指定しcontext.keyの形でアクセスする。
中からのデータはcontext.key = valueの形でInteractorクラスの中で設定し、callメソッドの戻り値の形でcontextが外に渡される。
基本的にはデータコンテナだが、いくつか特殊なメソッドが用意されている。下記の他(例えばcontext.errorなど)には決まりはない。自分で好き勝手に値の代入と参照ができる。
context.fail!
Interactorが失敗した状態にする。このメソッドを呼び出さなければ成功状態になる。このメソッドを呼び出した場合Interactor::Failure例外が投げられるが、callメソッドを使ってInteractorを呼び出しているのであれば、Interactorの内側でこの例外は処理される。
また、
context.error = '失敗'
context.fail!
と書く代わりにcontext.fail!(error: '失敗')という書き方ができる。
context.success?、context.failure?
Interactorが成功したかどうかを返す。成功していればcontext.success?はtrue、context.failure?はfalseを返す。
context.rollback!
成功したInteractorのrollbackメソッドが呼ばれる。rollbackメソッドはcallメソッドのように自分で定義する必要がある。失敗したInteractorの場合はrollbackメソッドは呼ばれない。
_called、called!
内部的に使われているメソッド。
Hook
before、after、aroundフックがある。
around do |interactor|
# ...
interactor.call
# ...
end
Controllerからの呼び出し
CreateUserというInteractorがある場合、Controllerからの呼び出しは単にCreateUser.call(name: params[:user][:name])のようにすれば良い。あるいは、permitを使ってCreateUser.call(params.require(:user).permit(:name))でもよい。
すでに書いている通り、CreateUser.callの戻り値としてContextが返される。この戻り値を利用してControllerでは処理を行う。
class UsersController < ApplicationController
def create
result = CreateUser.call(user_params)
@user = result.user
if result.success?
redirect_to @user, notice: "User was successfully created."
return
end
render :new, status: :unprocessable_entity
end
end
Interactor
基本的なInteractorは以下のような感じで記述する。
class CreateUser
include Interactor
def call
user = User.new(name: context.name)
context.user = user
context.fail! unless user.valid?
user.save!
rescue ActiveRecord::RecordInvalid
context.fail!
end
end
いくつかのInteractorをまとめたい場合、Organizerという機能を使う。
class Campaign
include Interactor::Organizer
organize CreateUser # , OtherInteractor, ...
end
organizeに指定したInteractorが順々に実行される。失敗したInteractorが現れた場合はそこで実行は打ち切られる。Organizer自身にもcallメソッドを定義できるが、superを呼ばないといけないので注意。