HanamiのアプリケーションをREST APIとして実装する 

Hanamiは複数のアプリケーションを[モノリシックに構築することができます。](https://qiita.com/isuke/items/134d82246203026857c6)

複数のアプリケーションを実装するにあたり、REST APIとして実装するアプリケーションを作成する機会がありましたが、3年以上の前の [Build a Web API with Hanami](https://speakerdeck.com/toch/build-a-web-api-with-hanami) ぐらいしか情報がなく苦労したので、備忘録として残しておきます。

# API化するHanamiのアプリケーションの準備
今回は[公式のチュートリアル](https://guides.hanamirb.org/introduction/getting-started/)が終わった段階の `web`アプリケーションに対してPOSTメソッド利用できるAPI化を施そうと思います。

# APIに不要なView関連ディリクトリの削除

assets, views, template の各ディリクトリは不要なので削除しちゃってください。

```diff
   web
-    ├── assets           
-    │   ├── images
-    │   ├── javascripts
-    │   └── stylesheets
     ├── config
     ├── controllers
-    ├── templates       
-    └── views    
```
このままでは、Hanamiのサーバが起動するときに読み込みエラーになるので、 `apps/web/application.rb` の各クラスの読み込み箇所を修正します。
※assetsの設定は起動時に読み込まれないので、削除してもしなくても大丈夫です。

```diff:apps/web/application.rb
      # Relative load paths where this application will recursively load the
      # code.
      #
      # When you add new directories, remember to add them here.
      #
-      load_paths << [
-        'controllers',
-        'views'  #削除
+     load_paths << ['controllers']
```

```diff:apps/web/application.rb
      # The relative path to templates
      #
-      templates 'templates'
+     # templates 'templates'

```
また、今回はメーラーも使用しないのでこのタイミングで削除します。

```diff:bookshelf/config/environment.rb
-  mailer do
-    root 'lib/bookshelf/mailers'
-
-    # See https://guides.hanamirb.org/mailers/delivery
-    delivery :test
-  end
+  # mailer do
+  #   root 'lib/bookshelf/mailers'
+  # 
+  #   # See https://guides.hanamirb.org/mailers/delivery
+  #   delivery :test
+  # end
```

# テストの作成
HanamiはTDDを推奨の設計手法としてしているのでテストを先に作成しましょう。
今回はPOSTのAPIを実装するので `spec/web/controllers/books/create_spec.rb` を作成してテストを書いていきます。

```ruby:spec/web/controllers/books/create_spec.rb
RSpec.describe Web::Controllers::Books::Create, type: :action do
  include Rack::Test::Methods
  let(:app) { Hanami.app }

  describe "create books" do

    let(:request_body) do
      {
        bools: {
          title: 'hogehoge',
          author: 'foo'
        }
      }
    end

    let(:do_request) { post '/api/books', params: request_body, header: { 'Content-Type' => 'application/json' } }

    it 'is http status 201' do
      do_request
      expect(last_response.status).to eq 201
      expect(last_response.body).to eq request_body
    end

    it 'is expect response body' do
      do_request
      expect(last_response.body).to eq request_body
    end

    it 'is created created books' do
      expect { do_request }.to change {
        BookRepository.new.first.nil?
      }.from(true).to(false)
    end
  end
end
```
アクションのテストとの違いは直接エンドポイントを叩いているところです。
エンドポイントを叩くためには下記のモジュールをincludeして `Hanami.app` を `app` に格納する必要があります

```ruby
  include Rack::Test::Methods
  let(:app) { Hanami.app }
```

# ルーティング
今回は `/api/books` というエンドポイントを作成します。
まず `config/environment.rb` において、下記のように設定することでホスト名の次に来るパスに `api` を自動で追加できます。

```ruby
mount Web::Application, at: '/api'
```

そして、Hanami には rails と同じような RESTful Resource(s) な記法で エンドポイントをルーティングできるのでapi以下のルーティングを下記のように設定します

```ruby:web/config/route.rb
resources 'books', only: [:create]
```

これでルーティングの設定は完了です。

今回は `:create` なのでhttpメソッドは POST になりますが、 `:update` の場合、httpメソッドは PATCH になります。PUT は存在しないので注意してください。
おそらく厳密に :create と :update を分けたいために PUTは廃止されたのかと思われます。
また、Hanami側から指定してくれているので PUT か PATCh で惑わずに済んで助かります。

# Action(Controller）

エラー周りに改善の余地がありますが、バリデーションと保存処理はアクションから独立させています。
最終的に `self.body` に入れた値がレスポンスボディとして返されるので、作成したリソースからJSON化したものをここに格納すればJSONのレスポンスボディとして返されます。 `self.status` も同様です。


```ruby:apps/web/controllers/books/create.rb
module Web
  module Controllers
    module Books
      include Api::Action
      accept :json

      def call(params)
        validate_result = Form::BooksValidator.new(params).validate
        raise StandardError unless validate_result.success?

        BooksInteractor.new.create(attribute)
        self.body = response_body
        self.status = 201
      rescue => exception
        self.status = 400
        @error = exception
      end

      def response_body
        JSON.dump(BooksRepository.new.last.to_hash)
      end
    end
  end
end
```

Hanamiのバリデーションは コンポーネント指向な `dry-validation` を模して実装されています。
今回やっていることは rails で言うところの StrongParamater の処理と同じですが、ガッツリとバリデーションも記述できるので、アクション側からそのあたりの責務を一通り引き取ることができます。

```ruby:lib/bookshelf/validators/form/books_validator.rb
require 'hanami/validations'

module Form
  class BooksValidator
    include Hanami::Validations

    validations do
      required(:books).schema do
        required(:title) { filled? }
        required(:author) { filled? }
      end
    end
  end
end

```

保存処理をIteratorにまとめたものです。こちらも保存処理関連の責務をアクションから剥がすことができます。

```ruby:lib/bookshelf/interactors/books_interactor/create.rb
require 'hanami/interactor'

module BooksInteractor
  class Create
    include Hanami::Interactor

    def initialize(params)
      @attributes = {
          title: params[:books][:title],
          author: params[:books][:author]
      }
    end

    def call
      BooksRepository.new.create(@attributes)
    end
  end
end
```



これらの実装により、`Web` アプリケーションをPOSTメソッドを持ったAPIにすることができます。

# 終わりに
Hanami を触っていて思ったことは  Entity と Repository の元となっている  [ROM(Ruby Object Mapper)](https://rom-rb.org/) の理解や Itarator の使い所をわかっていないと、どうしても rails 感が抜けずに劣化 rails になりがちな点です。まだプロダクトレベルのサンプルも少なく手探りなところもありますが、自分の中で消化できたらそのあたりの記事も書けたらと思います。