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
を呼ばないといけないので注意。