Phoenixお気軽API開発⑥：軽量APIの追加・削除REST APIを完成させる

[fukuoka.ex／kokura.ex](https://fukuokaex.connpass.com/)のpiacereです
ご覧いただいて、ありがとうございます :bow:

[前作までの追加・削除REST API実装](https://qiita.com/piacerex/items/94d9275e32ecd0e83da1)は、暫定版で、以下が未完成でしたが、今回、①②を完成させます

①id自動発番（insert時）
②URLからJSONテンプレートパスの特定／実行（create時、delete時）
③data.json.eexと列突合チェック（create時、delete時）

ちなみに③は、更新REST APIの対応後に実装しようと思います

# 本コラムの検証環境

本コラムは、以下環境で検証しています（Windowsで実施していますが、Linuxやmacでも動作する想定です）

- Windows 10
- Elixir 1.10.1 　　　　 ※[<b>最新版のインストール手順はコチラ</b>](https://qiita.com/piacere_ex/items/6714e1440e3f25fb46a1#%E4%BA%8B%E5%89%8D%E6%BA%96%E5%82%99elixir%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)
- Phoenix 1.4.15　　　※[<b>最新版のインストール手順はコチラ</b>](https://qiita.com/piacerex/items/b7787580fce5f148242f#phoenix%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)
- Node.js 12.14.0

あと下記コラムシリーズの続きとして実施するので、未実施であれば、事前に実施しておいてください（近々、ボイラープレート生成のmixコマンドとしてOSS化は考えています）

[<b>PHP的ハックを応用してNode.js Express／Go的な軽量APIをPhoenixで実現してみた</b>](https://qiita.com/piacerex/items/144089aa6f2052f4d1cb)
|> [<b>Express／Go的なPhoenix軽量APIの上に参照系REST APIを実装する</b>](https://qiita.com/piacerex/items/e11ef7b5cdcc81ac69c9)
|> [<b>軽量APIにて実装した一覧REST APIにDBを接続する</b>](https://qiita.com/piacerex/items/437e7b6c26c95dae0efb)
|> [<b>DB取得＋軽量APIで1件参照REST APIを実装する(TDD付)</b>](https://qiita.com/piacerex/items/7c969e42e7cf4fa5ed50)
|> [<b>DB追加／削除＋軽量APIで更新系REST API（暫定版）を実装する(TDD付)</b>](https://qiita.com/piacerex/items/94d9275e32ecd0e83da1)

# 残対応①：id自動発番

DbMnesiaのinsert()の中で、idのmaxを取得し、+1したものをinsertデータとして指定します

また、insert()成功時は、+1したidを返却します

```elixir:lib/util/db_mnesia.ex
defmodule DbMnesia do
	def insert( table_name, _columns, values ) do
		# TODO: 列選択はそのうち
		:mnesia.start
		table_atom = table_name |> String.to_atom
		:mnesia.wait_for_tables( [ table_atom ], 1000 )
		result = :mnesia.transaction( fn -> 
			next_id = max_id( table_atom ) + 1
			insert_spec = values |> values_value |> Tuple.insert_at( 0, next_id ) |> Tuple.insert_at( 0, table_atom )
			writed = :mnesia.write( insert_spec )
			{ writed, next_id }
 		end )
		case result do
			{ :atomic,  { :ok, id } } -> { :ok,    id  }
			{ :aborted, { err, _  } } -> { :error, err }
			{ _,        { err, _  } } -> { :error, err }
			            { err, _  }   -> { :error, err }
		end
 	end

	def max_id( table_atom ) do
		{ :atomic, keys } = :mnesia.transaction( fn -> :mnesia.all_keys( table_atom ) end )
		case keys do
			[] -> -1
			_  -> keys |> Enum.max
		end
	end
…
```

APIコントローラからのinsert時、リクエストJSONのidを渡していたところを削除し、Db.insert()からの戻り値{ :ok, next_id }から、next_idをrender()時のidとして渡すようにします（エラー時はrender()せずエラー返却します）

なお、Dbモジュールは、DbMnesiaからの戻り値をそのまま素通しする造りなので、改修不要です

```elixir:lib/basic_web/controllers/api_controller.ex
defmodule BasicWeb.ApiController do
…
  def create( conn, params ) do
    path = params[ "path_" ]
    prefix = if params[ "path_" ] == nil, do: "", else: Enum.join( path, "/" ) <> "/" 

    # TODO：URLからJSONテンプレートパスを特定し、ハンドラ特定 ＆ data.json.eexと列突合チェック
    result = Db.query( "insert into members values( '#{ data[ "name" ] }', '#{ data[ "age" ] }', '#{ data[ "team" ] }', '#{ data[ "position" ] }' )" )  # <-- modify here

    # v-- add here
    if elem( result, 0 ) == :ok do
      new_params = params |> Map.put( "id", elem( result, 1 ) )
      conn
      |> put_status( :created )
      |> render( "#{ prefix }show.json", params: new_params )
    else
      result
    end
    # ^-- add here
  end
…
```

RESTクライアントで、idを削除した下記のリクエストbodyを設定します

```json
{
    "data": 
    {
        "name": "テストユーザ124", 
        "age": 46, 
        "team": "開発チーム", 
        "position": "実装担当"
    }
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/adf31de1-8840-0b17-d146-912e7f6fb7ee.png)

``「POST http://localhost:4000/api/v1/users」``でデータ追加が行われ、idが、投入済みデータの最大である4の次の5が振られていることが確認できます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/085f3334-6957-0080-df29-915fea9492f7.png)

一覧でもid=5での追加が確認できます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/b86d67dd-183d-8ff5-06df-3e7e80f50a48.png)

``「POST http://localhost:4000/api/v1/users」``で再度データ追加すると、次は、id=6が振られます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/0409e371-6b6b-71ea-c588-5b83ee2ee2b4.png)

# 残対応②：URLからJSONテンプレートパスの特定／実行

## １）create／deleteのAPIコントローラを実装不要に

前回は、APIコントローラのcreate()とdelete()から、直接DBアクセッサを呼び出していた実装を、URLからJSONテンプレート（create.json.eex、delete.json.eex）を特定し、実行する実装に換装します（ついでに、delete()のエラー返却も追加しておくのと、それに合わせてDbMnesiaモジュール側も修正します）

```elixir:lib/basic_web/controllers/api_controller.ex
defmodule BasicWeb.ApiController do
…
  def create( conn, params ) do
    path = params[ "path_" ]
    prefix = if params[ "path_" ] == nil, do: "", else: Enum.join( path, "/" ) <> "/" 

    # TODO：data.json.eexと列突合チェック
    result = execute( "#{ prefix }create.json", params: params )  # <-- replace here

    if elem( result, 0 ) == :ok do
      new_params = params |> Map.put( "id", elem( result, 1 ) )
      conn
      |> put_status( :created )
      |> render( "#{ prefix }show.json", params: new_params )
    else
      result
    end
  end
…
  def delete( conn, params ) do
    id = params[ "path_" ] |> List.last |> Type.to_number
    new_params = params |> Map.put( "id", id )
    path = params[ "path_" ] |> Enum.drop( -1 )
    prefix = if params[ "path_" ] == nil, do: "", else: Enum.join( path, "/" ) <> "/" 

    # TODO：data.json.eexと列突合チェック
    result = execute( "#{ prefix }delete.json", params: new_params )  # <-- replace here

    # v-- add here
    if elem( result, 0 ) == :ok do
      send_resp( conn, :no_content, "" )
    else
      result
    end
    # ^-- add here
  end

  def execute( path, params: params ) do
    File.read!( "lib/basic_web/templates/api/#{ path }.eex" )
    |> Code.eval_string( params: params, data: params[ "data" ] ) 
    |> elem( 0 )
  end
end
…
```

```elixir:lib/util/db_mnesia.ex
defmodule DbMnesia do
…
	def delete( table_name, wheres ) do
		:mnesia.start
		table_atom = table_name |> String.to_atom
		:mnesia.wait_for_tables( [ table_atom ], 1000 )
		where_spec = wheres |> wheres_value |> Tuple.insert_at( 0, table_atom )
		result = :mnesia.transaction( fn -> :mnesia.delete( where_spec ) end )
		case result do
			{ :atomic,  :ok } -> { :ok }
			{ :aborted, err } -> { :error, err }
			{ _,        err } -> { :error, err }
			            err   -> { :error, err }
		end
	end
…
```

## ２）create.json.eex内でDBデータ追加

APIコントローラのcreate()に書いていたDBアクセッサの呼び出しを、create.json.eexというJSONテンプレートを作成し、そこに移します

```json:lib/basic_web/templates/api/v1/users/create.json.eex
data = params[ "data" ]
Db.query( "insert into members values( '#{ data[ "name" ] }', #{ data[ "age" ] }, '#{ data[ "team" ] }', '#{ data[ "position" ] }' )" )
```

## ３）delete.json.eex内でDBデータ削除

APIコントローラのdelete()に書いていたDBアクセッサの呼び出しも、delete.json.eexというJSONテンプレートを作成し、そこに移します

```json:lib/basic_web/templates/api/v1/users/delete.json.eex
Db.query( "delete from members where id = #{ params[ "id" ] }" )
```

# 手順③：動作確認

## １）追加REST APIの動作確認

まずは追加の確認からいきます

RESTクライアントで、idを削除した下記のリクエストbodyを設定します

```json
{
    "data": 
    {
        "name": "テストユーザ125", 
        "age": 48, 
        "team": "障害対応チーム", 
        "position": "バグ改修担当"
    }
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/3eebe3b7-6f47-6a9e-6ef9-8a4ed90a4f29.png)

``「POST http://localhost:4000/api/v1/users」``で追加が行われ、id=7のデータが追加されれば成功です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/d68e68e1-96ad-3248-d24d-426bca38fcdb.png)

``「GET http://localhost:4000/api/v1/users」``で一覧し、id=7の追加を確認します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/528d95f5-a6d8-b4fe-1a68-14d6e1e0669a.png)

## ２）削除REST APIの動作確認

次に、削除の確認です

``「DELETE http://localhost:4000/api/v1/users/7」``で削除を行います
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/ab100163-d7ce-f93a-e207-059d2715b9c6.png)

``「GET http://localhost:4000/api/v1/users」``で一覧し、id=7が削除されたことを確認します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/141bfab0-ba7d-2a04-a7c9-e34f9dcff429.png)

# 終わり

JSONテンプレートによるDB操作で実装された、軽量API版の追加・削除REST APIを実装しました

各種JSONテンプレートを配置するだけで、REST APIが実装されていくことが、だんだん現実味を帯びてきたのでは無いでしょうか？

最終的なコードは、以下にまとめてあります

[<b>Phoenix軽量APIを使ったREST APIのコード（データはMnesia DBに保持）</b>](https://qiita.com/piacerex/items/a41176fb0c7c68523a4b)
https://qiita.com/piacerex/items/a41176fb0c7c68523a4b

今回の内容が上手く理解できなかった方は、上記からコラムを追って、まず手元で動かすところから始めてみてください

[次回は、いよいよREST APIのラスト、更新REST APIを実装](https://qiita.com/piacerex/items/78d3036e556e66981c79)します

# p.s.このコラムが、面白かったり、役に立ったら…

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/67568d54-6584-ad33-77c7-b3b3afe70287.png) や ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/f5b819ef-d655-ef72-294c-b1a9d2c5b563.png) にて、どうぞ応援よろしくお願いします:bow: