Active InteractionをMPAでハンドリングする方法

「Active InteractionをMPAでハンドリングする方法」のアイキャッチ画像

普段はAPI開発で利用しているので、Active recordのerrorsに入れるというよりもMapやList形式でエラーを返しているのですが、createが失敗した時のnewをレンダリングする際にちょこっとはまったので記事にしました

まずは最終的なコード

# interaction
module Resources
  module InvoiceInformations
    class Create < ActiveInteraction::Base
      hash :params do
        string :name
        string :zip
        string :prefecture
        string :city
        string :street
        string :building, default: nil
        string :memo, default: nil
      end

      object :current_user, class: User
      validates :current_user, presence: true

      def execute
        invoice_information = current_user.build_invoice_information(params)
        invoice_information.save!
      rescue ActiveRecord::RecordInvalid => e
        # ここ
        e.record.errors.errors.each do |error|
          errors.add(error.attribute, error.message)
        end
        # 公式では下記になってる
        # errors.merge!(e.record.errors.errors)
      end
    end
  end
end

# controller
def create
    outcome = Resources::InvoiceInformations::Create.run(
      params: params_create,
      current_user: current_user
    )
    if outcome.valid?
      @invoice_information = outcome.result
      redirect_to customers_path,
                  notice: t('action_messages.created', model_name: InvoiceInformation.model_name.human)
    else
      @invoice_information = current_user.build_invoice_information(outcome.params)
      # 正直もっと良い方法ありそうだけど、時間の関係上こちらで
      outcome.errors.errors.each do |e|
        @invoice_information.errors.add(e.attribute, e.message)
      end
      render :new
    end
  end

問題点

attributeにbaseが入れられてしまう、が問題です

errors.merge!(e.record.errors.errors) でも当然エラーを返すことができるのですが、errors.merge!に公式の通りのコードを入れると、エラーの対象が特定のカラムではなく全てbaseで表現されてしまいます

煩雑になってしまいますが、丁寧に一つずつエラーをaddすることで目的の挙動を実現できます

※rescue内部でデバッグ実行してclassを確認

公式のコード

<ActiveModel::Error attribute=base, type=blank, options={}>

上記のコードの場合

<ActiveModel::Error attribute=zip, type=blank, options={}>

まとめ

Active Interactionを利用することでusecase的な実装を行うことができるようになり、コードは増えますがより変更に耐えうる実装ができたかと思います

正直Railsを利用するのは否定的ですが、usecaseとして切り出せるのであればまだまだ可能性を感じるので、もうちょい研究とかしていきたいです