LoginSignup
8
7

More than 5 years have passed since last update.

HanamiのInteractorでValidationを使いやすくする

Last updated at Posted at 2018-02-10

経緯

HanamiのControllerから責務を分けるために、ビジネスロジックをInteractorに移していきたい。
同時にパラメータ検証(バリデーション)もInteractor側でやりたいが、Controllerはパラメータ管理が拡張されていて定義書くだけでできるのに対して、Interactorは普通にやるとやぼったい処理を書くことになる。

Hanami::Actionを読み込む方法案も考えたが、exposeなどで挙動の違いがあるほか、Interactorでやりたいことに閉じた拡張がしづらそうなので、ひとまず現状やりたいパラメータ検証に特化した汎用化を探ってみる。

InteractorでとりあえずValidationする場合

InteractorとValidationを組み合わせた手法をとると、例えば以下のようなController、Interactorが書ける。
参考: HanamiはRubyの救世主(メシア)となるか、愚かな星と散るのか

apps/web/controllers/items/create.rb
module Web::Controllers::Items
  class Create
    include Web::Action

    expose :error
    expose :item

    def call(params)
      interactor = ItemInteractor::Create.new(params).call

      if interactor.successful?
        redirect_to routes.item_path(interactor.item.id)
      else
        self.status = 422
        @error = interactor.error
        @item = interactor.item
      end
    end
  end
end
lib/interactors/items/create.rb
require 'hanami/interactor'
require 'hanami/validations'

module ItemInteractor
  class Create
    include Hanami::Interactor

    class Validation
      include Hanami::Validations

      validations do
        required(:name).filled(:str?, size?: 1..200)
        optional(:description) { str? & max_size?(1000) }
      end
    end

    expose :item

    def initialize(params)
      @params = params
    end

    def call
      @item = ItemRepository.new.create(
        name: @params[:name],
        description: @params[:description]
      )
    end

    private

    def valid?
      validation = Validation.new(@params).validate
      error(validation.messages) if validation.failure?

      validation.success?
    end
  end
end

これをベースに拡張していけば、Entityごとのロジックは書いていけそうですが、#call以外はDRYじゃなくなりそうなのが気になる。

Interactorの汎用部分をmoduleに切り出してみる

Interactorでバリデーションを使う場合に共通化できそうな部分を切り出すと以下のような感じ。

だいぶすっきり。
Validation.class_eval のところはもっとスマートなやり方あれば知りたいところ。

lib/interactors/items/create.rb
require '../../modules/sample/interactor'

module ItemInteractor
  class Create
    include Sample::Interactor

    Validation.class_eval do
      validations do
        required(:name).filled(:str?, size?: 1..200)
        optional(:description) { str? & max_size?(1000) }
      end
    end

    expose :item

    def call
      @item = ItemRepository.new.create(
        name: @params[:name],
        description: @params[:description]
      )
    end
  end
end
lib/sample/modules/interactor.rb
require 'hanami/interactor'
require 'hanami/validations'

module Sample::Interactor
  class Validation
    include Hanami::Validations
  end

  def self.included(klass)
    klass.class_eval do
      include Hanami::Interactor

      def initialize(params)
        @params = params
      end
    end
  end

  private

  def valid?
    validation = Validation.new(@params).validate
    error(validation.messages) if validation.failure?

    validation.success?
  end
end

さらにValidationまわりを切り出してみる

Interactorで毎回Validationするとも限らないので、Validationまわりを切り出して着脱可能にしておく。

lib/interactors/items/create.rb
require '../../modules/sample/interactor'
require '../../modules/sample/action/validatable'

module ItemInteractor
  class Create
    include Sample::Interactor
    include Sample::Action::Validatable

    Validation.class_eval do
      validations do
        required(:name).filled(:str?, size?: 1..200)
        optional(:description) { str? & max_size?(1000) }
      end
    end

    expose :item

    def call
      @item = ItemRepository.new.create(
        name: @params[:name],
        description: @params[:description]
      )
    end
  end
end
lib/sample/modules/interactor.rb
require 'hanami/interactor'

module Sample::Interactor
  def self.included(klass)
    klass.class_eval do
      include Hanami::Interactor

      def initialize(params)
        @params = params
      end
    end
  end
end
lib/sample/modules/action/validatable.rb
require 'hanami/validations'

module Sample
  module Action
    module Validatable
      class Validation
        include Hanami::Validations
      end

      private

      def valid?
        validation = Validation.new(@params).validate
        error(validation.messages) if validation.failure?

        validation.success?
      end
    end
  end
end

I18n対応なども組み込みやすくなりそう。

8
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
7