Ruby on Rails

Ruby Interactorの使い方|interactor gem 3.2.0のcontext・Organizer・rollback【Ruby 4.0対応】

Ruby Interactorの使い方|interactor gem 3.2.0のcontext・Organizer・rollback【Ruby 4.0対応】

RubyのInteractorは、アプリケーションが行う1つの業務手続き(ユーザー登録、注文確定、退会など)を1クラスに閉じ込める設計です。Railsではcollectiveideaのinteractor gemという実装があり、本記事はその最新版3.2.0の使い方を、context・fail!・フック・Organizer・テストの順に説明します。単体で動く掲載コードは、検証環境のRuby 4.0.6(x86_64-darwin)とinteractor 3.2.0で実行し、出力をそのまま載せています(2026年11月1日時点の安定版は4.0.9)。コントローラとジェネレータの例はRailsアプリ内で使う書き方の例示です。

まとめ:Interactorを使う条件と3.2.0で押さえる変更点

Interactorが効くのは、1つのリクエストで複数のモデル更新・外部API・メール送信がまとまって起きる手続きです。コントローラはXxx.callの戻り値のsuccess?だけを見ればよくなり、手続きの一覧はapp/interactorsを眺めれば分かります。逆に1モデルを保存するだけのCRUDでは、ファイルとクラスが増える割に得るものが少なくなります。公式READMEも、単純なログイン処理ではInteractorの方がコードが増えると書いたうえで、責務が後から増える前提で使う理由を説明しています。

3.2.0(2025年7月10日公開)は前版3.1.2から5年半ぶりのリリースで、既存コードに影響する変更が2つあります。1つはRuby 4.0でostructがdefault gemからbundled gemへ移ったことへの対応で、3.1.2のままRubyを4.0へ上げるとBundler配下で起動しません。もう1つは、Interactorの中から別のInteractorをcall!で呼んで失敗したとき、3.1.2では失敗が握りつぶされてsuccess?がtrueのまま返っていたのが、3.2.0では呼び出し元へ例外が届くようになった点です。どちらも以降の章で実行結果を示します。

Interactorパターンの意味:業務手続き単位のクラス設計

Interactorという名前は、ユーザーとシステムのやり取り(interaction)を1単位として切り出すことに由来します。READMEは命名について、実装ではなく業務の言葉で名付けるよう勧めており、DestroyUserよりCancelAccountの方が、退会処理に責務が増えたときにも名前が実態からずれにくいと説明しています。

Service Objectとの違い:戻り値と失敗の規約

Service Objectは「モデルやコントローラから処理を切り出したPORO」の総称で、戻り値の形や失敗の伝え方はチームごとに決めます。Interactorはそこに共通の規約を足したものです。入力も出力も1つのcontextに載せ、失敗はcontext.fail!で表し、呼び出し側はsuccess?とfailure?で判定します。規約が揃うので、複数のInteractorを数珠つなぎにするOrganizerや、失敗時のrollbackといった仕組みをgem側が提供できます。モデルの肥大化をどの単位で切り出すか自体の判断はRailsのFat Model対策|1500行model.rbを分割する4手法と判断基準で扱っています。

interactor gemの現状:3.2.0と保守の頻度

RubyGems.orgの記録では、interactorの累計ダウンロードは28,117,864回、GitHubのスターは3,456です(いずれも2026年11月1日時点)。リリースは3.1.0(2014年10月)、3.1.1(2018年5月)、3.1.2(2019年12月)、3.2.0(2025年7月)と間隔が長く、GitHub APIのpushed_atも3.2.0のリリース日(2025-07-10)を指しています(2026年11月1日取得)。主要なAPIは3.0.0と3.1.0(いずれも2014年)で固まっており、新機能を待つgemではありません。機能が増えないことを許容できるかが、採用時の判断材料の1つになります。

interactor gemの導入:Gemfileとapp/interactorsの置き場所

Gemfileに1行足してbundle installします。3.2.0のCHANGELOGにあるとおり、依存にostructが加わっているので、Gemfileへostructを別途書く必要はありません。

gem "interactor", "~> 3.2"

置き場所はREADMEに従ってapp/interactorsにします。Railsは起動時に存在するapp直下のサブディレクトリを、assets・javascript・viewsを除いてautoloadの対象にするため(Railsガイドのautoloadの章)、ディレクトリを作るだけでクラスが読み込まれます。ファイル名とクラス名の対応規則で迷ったらZeitwerkとは?読み方とRailsの自動読み込み、zeitwerk:checkのエラー対処【Rails 8.1】が参考になります。

interactor-railsを入れるかどうかの判断

Rails向けの補助geminteractor-rails(2.3.0、2024年11月18日公開)は、app/interactorsをautoload対象に加えることと、ジェネレータの提供が役割です。依存はrailties >= 7.0で、Ruby 3.1以降・Rails 7.0以降でテストされています。前者はRails 7以降なら本体が済ませるので、実質的にはジェネレータを使うかどうかで決めてかまいません。

rails generate interactor authenticate_user
rails generate interactor:organizer place_order charge_card send_thank_you fulfill_order

1行目はcallだけを持つ雛形、2行目はorganizeに3つのInteractorを並べたOrganizerを作ります。ジェネレータ全般の取り消し方や生成物の確認方法はRailsジェネレータ(rails generate)の使い方|生成ファイル一覧と取り消し・カスタム生成【Rails 8.1.4】にまとめています。

Ruby 4.0でinteractor 3.1.2が起動しない理由

Interactor::ContextはOpenStructのサブクラスです。Ruby 4.0.0のNEWS.mdには、default gemからbundled gemへ移された gem としてostruct 0.6.3が載っています。bundled gemはRubyと一緒にインストールされますが、Bundlerの管理下ではGemfile(またはgemspecの依存)に書かれていないと読み込めません。3.1.2のgemspecにはostructへの依存が無いため、Gemfileにgem "interactor", "3.1.2"だけを書いてbundle execすると、最初のrequireで止まります。

interactor-3.1.2/lib/interactor/context.rb:1: warning: ostruct used to be loaded from the standard library, but is not part of the default gems since Ruby 4.0.0.
You can add ostruct to your Gemfile or gemspec to fix this error.
bundled_gems.rb:60:in 'Kernel.require': cannot load such file -- ostruct (LoadError)

同じ条件で3.2.0にすると、Gemfile.lockにostruct (0.6.3)が依存として入り、そのまま動きます。Ruby 4.0への移行と同時にinteractorを3.2.0へ上げるのが筋で、何らかの事情で3.1.2に留めるならGemfileにgem "ostruct"を足します。ただし後述のネスト時の挙動差があるため、3.1.2に留める理由はほとんど残りません。Ruby本体の版ごとの変更点とサポート期限はRubyのバージョンとサポート期限の解説にあります。

Interactorの書き方:call・context・fail!

クラスにinclude Interactorしてcallを定義すれば、それがInteractorです。呼び出し側はクラスメソッドのcallにハッシュを渡し、戻り値としてcontextを受け取ります。

require "interactor"

class ChargePayment
  include Interactor

  def call
    context.fail!(error: "card_declined") if context.amount > 10_000
    context.charge_id = "ch_#{context.amount}"
  end
end

ok = ChargePayment.call(amount: 500)
ng = ChargePayment.call(amount: 50_000)
p [ok.success?, ok.charge_id]
p [ng.failure?, ng.error, ng.charge_id]
p Interactor::Context.superclass
p ok.usr_id
[true, "ch_500"]
[true, "card_declined", nil]
OpenStruct
nil

fail!にハッシュを渡すと、contextへの値の設定と失敗フラグを同時に行い、その場で処理を打ち切ります。2行目でcharge_idがnilなのは、fail!の次の行が実行されていないためです。

contextの読み書きとパターンマッチ

4行目のnilは、存在しないusr_id(user_idの打ち間違い)を読んだ結果です。contextはOpenStructなので、未定義の名前を読んでも例外にならずnilが返ります。入力の宣言も型の検査も無いため、キー名の誤りは数行あとのNoMethodErrorや、静かに空の値で処理が進む形で表に出ます。後述の「採用すべきでない場面」で扱う弱点はここから来ています。

3.2.0ではcontextがパターンマッチに対応しました(CHANGELOGのAdd support for Ruby 3 pattern matching on the Context)。戻り値をcase/inで分解できます。

case ok
in {charge_id: String => id}
  puts "matched #{id}"
end
# => matched ch_500

context.fail!とcall!の違い:コントローラでの分岐

READMEのDealing with Failureによると、fail!は常にInteractor::Failure例外を送出しており、クラスメソッドのcallがそれを捕まえてcontextを返しています。call!は捕まえずに例外を外へ出す版です。

begin
  ChargePayment.call!(amount: 50_000)
rescue Interactor::Failure => e
  p [e.class, e.context.error]
end
# => [Interactor::Failure, "card_declined"]

コントローラではcallとsuccess?の組み合わせが基本形です。call!は、失敗を例外としてジョブのリトライ機構などに拾わせたい場面で使います。

class PaymentsController < ApplicationController
  def create
    result = ChargePayment.call(amount: params[:amount].to_i)

    if result.success?
      redirect_to payment_path(result.charge_id)
    else
      flash.now[:alert] = t("payments.#{result.error}")
      render :new, status: :unprocessable_content
    end
  end
end

コントローラ経由の分岐をHTTPレベルで確かめるテストの書き方はrequest specとは?Rails 8・RSpec 8でのリクエストスペックの書き方を参照してください。

before・after・aroundフックの実行順

Interactorには前処理・後処理を差し込むフックが3種類あります。READMEのHook Sequenceの規則は、beforeは定義順、afterは定義の逆順、aroundはbefore・afterのさらに外側、というものです。注意点は、fail!が呼ばれるとafterフックとaroundの後半が実行されないことです。

class Hooked
  include Interactor
  around { |i| puts "around before"; i.call; puts "around after" }
  before { puts "before" }
  after  { puts "after" }

  def call
    puts "call"
    context.fail!(error: "stop") if context.stop
  end
end

Hooked.call
puts "---"
Hooked.call(stop: true)
around before
before
call
after
around after
---
around before
before
call

2回目はcallのあとが何も出力されていません。afterに監査ログの書き込みや計測の終了を置くと、失敗した手続きだけ記録が欠けます。成功・失敗の両方で必ず実行したい処理は、呼び出し側で結果を見て行うか、aroundの中でensureを使って書きます。

Organizerで複数のInteractorをつなぐ:rollbackの順序

Organizerは、複数のInteractorに同じcontextを順に渡して実行するInteractorです。途中のどれかがfail!すると後続は呼ばれず、それまでに成功したInteractorのrollbackが逆順に呼ばれます。

class CreateOrder
  include Interactor
  def call
    context.order_id = 101
    puts "CreateOrder#call"
  end
  def rollback
    puts "CreateOrder#rollback (order_id=#{context.order_id})"
  end
end

class ReserveStock
  include Interactor
  def call
    context.reserved = true
    puts "ReserveStock#call"
  end
  def rollback
    puts "ReserveStock#rollback"
  end
end

class ChargeCard
  include Interactor
  def call
    puts "ChargeCard#call"
    context.fail!(error: "card_declined")
  end
  def rollback
    puts "ChargeCard#rollback"
  end
end

class SendReceipt
  include Interactor
  def call
    puts "SendReceipt#call"
  end
end

class PlaceOrder
  include Interactor::Organizer
  organize CreateOrder, ReserveStock, ChargeCard, SendReceipt
end

result = PlaceOrder.call
p [result.success?, result.error, result.order_id, result.reserved]
CreateOrder#call
ReserveStock#call
ChargeCard#call
ReserveStock#rollback
CreateOrder#rollback (order_id=101)
[false, "card_declined", 101, true]

出力から3つのことが読み取れます。SendReceiptは呼ばれていません。失敗したChargeCard自身のrollbackは呼ばれません(READMEも「失敗したInteractorはロールバックされない」と明記しています)。そしてrollback後もcontextのorder_idとreservedは残っています。呼び出し側でresult.order_idがあるから注文が作られた、と判定するとrollback済みの注文を指すことになるので、判定は必ずsuccess?で行います。

rollbackとDBトランザクションの違い

rollbackは、各Interactorが自分で書く取り消し処理を呼ぶだけの仕組みです。gemのソース(Interactor::Context#rollback!)は、成功済みのInteractorを逆順に並べてrollbackメソッドを呼んでいるにすぎず、データベースの変更を自動で巻き戻しません。fail!以外の例外が起きた場合も同じ経路でrollbackが呼ばれ、例外はそのまま呼び出し元へ伝わります。

class First
  include Interactor
  def call; context.a = 1; end
  def rollback; puts "First#rollback"; end
end

class Boom
  include Interactor
  def call; raise ArgumentError, "bug"; end
end

class Flow
  include Interactor::Organizer
  organize First, Boom
end

begin
  Flow.call
rescue => e
  p [e.class, e.message]
end
First#rollback
[ArgumentError, "bug"]

DB更新だけで完結する手続きなら、Organizerのrollbackに頼らずActive Recordのトランザクションで包む方が確実です。ただし、包む中でcallを使うとfail!の例外がcallの内側で捕まり、トランザクションには例外が届かないため、更新はコミットされます。SQLiteのインメモリDBで確かめた結果です。

class CreateAndFail
  include Interactor
  def call
    Order.create!(name: "x")
    context.fail!(error: "declined")
  end
end

ActiveRecord::Base.transaction { CreateAndFail.call }
puts "call   : orders=#{Order.count}"
Order.delete_all
begin
  ActiveRecord::Base.transaction { CreateAndFail.call! }
rescue Interactor::Failure => e
  puts "call!  : orders=#{Order.count} error=#{e.context.error}"
end
call   : orders=1
call!  : orders=0 error=declined

トランザクションの中ではcall!を使い、失敗を例外としてトランザクションの外まで届けてからrescueで受けます。巻き戻せるのは、そのトランザクションと同じDB接続で行った更新だけです。rollbackが役に立つのは、決済APIの取り消しや外部ストレージのファイル削除のように、トランザクションでは戻せない副作用を打ち消すときです。トランザクションの範囲と発行SQLはActive Record(アクティブレコード)とは?仕組みと発行SQL・N+1・検証の落とし穴を実行して解説【Rails 8.1.4】で確認できます。

ネストしたInteractorの失敗:3.1.2と3.2.0で変わった挙動

Organizerは全Interactorが1つのcontextを共有するため、キー名が衝突しやすいという不満がよく挙がります。Organizerを使わず、別のcontextを使う方法として、Interactorの中から別のInteractorをcall!で直接呼ぶ書き方があります。この書き方は版によって結果が違います。

class ValidateEmail
  include Interactor
  def call
    context.fail!(error: "invalid_email") unless context.email.include?("@")
  end
end

class RegisterUser
  include Interactor
  def call
    ValidateEmail.call!(email: context.email)
    context.registered = true
  end
end

begin
  r = RegisterUser.call(email: "foo")
  p [:returned, r.success?, r.registered]
rescue Interactor::Failure => e
  p [:raised, e.class, e.context.error]
end

同じコードを2つの版で実行した結果です。

版 結果 success? registered
3.1.2 contextが返る true nil
3.2.0 Interactor::Failureが届く (返らない) (返らない)

3.1.2では、内側の失敗が外側のcallに捕まり、registeredは設定されていないのにsuccess?がtrueで返ります。登録されていないユーザーを「登録成功」として扱う、気付きにくい不具合です。この問題はissue #170で報告され、PR #192「Don’t rescue Failures from other contexts」(2022年1月マージ)で修正されました。3.2.0のrunは、捕まえたInteractor::Failureが自分のcontextのものでなければ再送出します。

3.2.0で気を付けるのは、修正の結果として例外がコントローラまで届く点です。callは例外を出さないという前提で書いたコントローラは、ネストを含むInteractorに対してはその前提が崩れます。対処は2通りあります。内側の失敗を外側の失敗として扱いたいなら、自分のcontextをそのまま渡します。

class RegisterUserShared
  include Interactor
  def call
    ValidateEmail.call!(context)
    context.registered = true
  end
end

r = RegisterUserShared.call(email: "foo")
p [:shared, r.success?, r.error, r.registered]
# => [:shared, false, "invalid_email", nil]

contextを共有するので、外側は例外を出さずにsuccess?がfalseの結果を返します。別のcontextで呼びたい場合は、内側をrescue Interactor::Failure => eで受けてcontext.fail!(error: e.context.error)で外側の失敗に置き換えます。2021年のRico Sta. Cruz氏のレビュー記事はこのrescueを「失敗が握りつぶされるのを防ぐ回避策」として紹介していますが、3.2.0では目的が変わり、例外をcontextの失敗へ戻すための書き方になっています。3.1.2から上げるときは、call!をネストしている箇所を検索して、どちらの書き方に揃えるかを決めてからにしてください。

RSpecによるInteractorの失敗判定とOrganizerの順序検証

Interactorは入力も出力もcontextなので、単体テストは「ハッシュを渡して、返ったcontextを検証する」形になります。READMEのTesting Interactorsは、モデル側のメソッド(例ではUser.authenticate)をスタブにして、Interactorが担う1つの目的だけを検証するよう勧めています。Organizerは中身を実行せず、Interactor::Organizerが定義するorganizedクラスメソッドで並び順だけを確かめられます。

RSpec.describe ChargePayment do
  it "fails the context when the card is declined" do
    result = described_class.call(amount: 50_000)
    expect(result).to be_a_failure
    expect(result.error).to eq "card_declined"
  end
end

RSpec.describe PlaceOrder do
  it "organizes interactors in order" do
    expect(described_class.organized).to eq [CreateOrder, ReserveStock, ChargeCard, SendReceipt]
  end
end

前章までのChargePaymentとPlaceOrderの定義を読み込んでrspec-core 3.13.6で実行し、2 examples, 0 failuresでした。be_a_failureはRSpecの述語マッチャで、contextのfailure?を呼んでいます。テスト対象のメソッドをcallを経由せずに直接呼ぶと、fail!がInteractor::Failureを送出するので、そのときはexpect { ... }.to raise_error(Interactor::Failure)で受けます。

Interactorを採用すべきでない場面と代替gem

READMEのコントローラ例は、単純なログイン処理ならInteractorを使うとかえってコードが増えると自ら書いています。採用を見送るべき条件は次の3つです。

  • 1モデルの保存とバリデーションで完結するCRUD。モデルのバリデーションとコントローラで足り、ファイルが増えるだけです。
  • 入力の型や必須項目を宣言したい場合。contextはOpenStructなので、打ち間違えたキーもnilとして通ります。宣言が無いまま手続きが増えると、どのInteractorがどのキーを読み書きするのかをコードを読まずに把握できなくなります。
  • 新機能や保守の頻度を重視する場合。前述のとおりリリース間隔は年単位です。

active_interaction・dry-operationとの比較

入力の宣言や失敗の型付けが欲しいなら、目的の近い別のgemがあります。

gem 最新版 公開日 入力の宣言 失敗の表し方
interactor 3.2.0 2025-07-10 なし context.fail!
active_interaction 5.5.0 2025-02-02 フィルタで型宣言 ActiveModelのerrors
dry-operation 1.1.0 2026-02-06 なし(stepで連結) dry-monadsのFailure

active_interactionはfloat :xのように入力をフィルタで宣言し、不正な入力ならexecute自体を実行しません。contextの打ち間違いに悩んでいるチームの乗り換え先として素直です。dry-operationは各stepがSuccessかFailureを返し、Failureが出た時点で以降を飛ばします。Organizerに相当する連結を1クラスのcallの中に直線的に書けるので、contextの共有による衝突が起きません。既存のInteractorが数十本あるなら、入れ替えより、新しい手続きだけを別の方式で書く移行の方が現実的です。業務ロジックが多いRailsアプリの設計や実装を外部に任せる場合は、フルスクラッチでの業務システム開発として相談を受けています。

Interactorと組み合わせるRubyのパターン:FactoryとDecorator

Interactorは手続きの単位を決める設計で、手続きの中で使う部品は別のパターンで整理します。Railsの業務コードで併用が多いのがFactoryとDecoratorです。どちらもgem無しで書けます。

Factory:ハッシュによる生成クラスの選択

Rubyではクラス自体がオブジェクトなので、生成する具象クラスの分岐はcase文より定数ハッシュの表引きの方が、既存クラスを選択肢に加えるときの変更が対応表の1行で済みます。

class BankTransfer
  def label = "銀行振込"
end

class CreditCard
  def label = "クレジットカード"
end

class PaymentFactory
  HANDLERS = {
    "bank_transfer" => BankTransfer,
    "credit_card" => CreditCard
  }.freeze

  def self.build(kind)
    HANDLERS.fetch(kind) { raise ArgumentError, "unknown payment: #{kind}" }.new
  end
end

puts PaymentFactory.build("credit_card").label
PaymentFactory.build("paypay") rescue puts $!.message
クレジットカード
unknown payment: paypay

fetchにブロックを渡しているので、未知のキーはその場でArgumentErrorになります。[]で引くとnilが返り、エラーが出るのは数行あとになります。Factoryパターン一般の構造と他言語での書き方はファクトリパターンとは?Factory Method・Abstract Factoryの違いとJava・Python実装例、実行時に振る舞いを差し替える設計はStrategyパターンとは?if文の分岐を置き換える仕組みとPHP・Java・Python・TypeScriptの実装例で扱っています。

Decorator:SimpleDelegatorと委譲先の型判定の違い

表示専用のメソッドをモデルから外へ出すには、標準添付のdelegateライブラリにあるSimpleDelegatorで足ります。

require "delegate"

User = Struct.new(:family_name, :given_name)

class UserDecorator < SimpleDelegator
  def full_name
    "#{family_name} #{given_name}"
  end
end

user = UserDecorator.new(User.new("山田", "太郎"))
puts user.full_name
puts user.family_name
puts "is_a?(User) => #{user.is_a?(User)}"
puts "__getobj__.is_a?(User) => #{user.__getobj__.is_a?(User)}"
山田 太郎
山田
is_a?(User) => false
__getobj__.is_a?(User) => true

未定義のメソッドは元のオブジェクトへ委譲されますが、is_a?はデコレータ自身が答えるので、is_a?(User)はfalseです。caseとクラスで分岐している既存コードは、デコレータを渡した時点で分岐が外れます。元のオブジェクトが要る箇所では__getobj__で取り出します。デコレータの中でlink_toなどのビューヘルパーを呼びたい場合は、委譲に加えてヘルパーへのアクセスを用意するDraper(4.0.6、2025年11月16日公開)を使います。GoFの23パターン全体の分類はGoFデザインパターン23種類の一覧|3分類と見分け方をJavaコードで解説にあります。

よくある質問

Interactorとはどういう意味ですか?

アプリケーションが行う1つの業務手続きを1クラスにまとめる設計と、その実装であるinteractor gemを指します。入力と出力を1つのcontextに載せ、失敗はcontext.fail!で表します。

interactorとinteractor-railsはどちらを入れればよいですか?

本体はinteractorで、interactor-railsはRails用のジェネレータを足す補助gemです。Rails 7以降はapp/interactorsを本体がautoloadするので、ジェネレータを使わないならinteractorだけで動きます。

callとcall!の違いは何ですか?

どちらも同じ処理を実行します。callはfail!によるInteractor::Failureを捕まえてcontextを返し、call!は例外として外へ出します。ただし3.2.0では、別のcontextで起きた失敗はcallでも捕まえず再送出します。

Organizerの途中で失敗したらどうなりますか?

後続のInteractorは実行されず、成功済みのInteractorのrollbackが逆順に呼ばれます。失敗したInteractor自身のrollbackは呼ばれず、contextに書いた値はrollback後も残ります。

interactor gemはRuby 4.0で使えますか?

3.2.0なら使えます。本記事のコードはRuby 4.0.6とinteractor 3.2.0で実行しました。3.1.2はBundler配下でostructのLoadErrorになるため、Gemfileにgem "ostruct"を足すか、3.2.0へ上げます。

関連記事

お気に入りに入れた記事の一覧

一創の公式 SNS

X では毎朝の新着記事と IT ニュースの速報を、YouTube・TikTok・Instagram・LinkedIn・Facebook では記事の解説動画を配信しています。

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.29 テックブログ ソニー生命の情報漏洩38.5万件|ログインできない理由、再開状況と窓口の電話番号
  2. 2026.11.03 テックブログ クラブツーリズム不正アクセス:約432万件の対象確認と偽メールの見分け方・流出検知の設計
  3. 2026.11.03 テックブログ ノジマ不正アクセスで約700万件漏えい:対象確認・窓口と乗っ取り注文を止める実装
  4. 2026.11.04 テックブログ CVE-2026-119977:WordPress更新中のRCEと安全な更新手順
  5. 2026.11.02 テックブログ アララの不正アクセス:メール約7,700万件の持ち出しと契約企業が行うAPIキー再設定・代替送信・連名報告

RELATED POSTS 関連記事

目次