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

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

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

## InteractorでとりあえずValidationする場合
InteractorとValidationを組み合わせた手法をとると、例えば以下のようなController、Interactorが書ける。
参考： [HanamiはRubyの救世主(メシア)となるか、愚かな星と散るのか](http://magazine.rubyist.net/?0056-hanami#l17)

```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
```

```ruby: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` のところはもっとスマートなやり方あれば知りたいところ。


```ruby: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
```

```ruby: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まわりを切り出して着脱可能にしておく。

```ruby: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
```

```ruby: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
```

```ruby: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対応なども組み込みやすくなりそう。