2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-04-04

fukuoka.ex/kokura.exのpiacereです
ご覧いただいて、ありがとうございます :bow:

前作までの追加・削除REST API実装は、暫定版で、以下が未完成でしたが、今回、①②を完成させます

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

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

本コラムの検証環境

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

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

PHP的ハックを応用してNode.js Express/Go的な軽量APIをPhoenixで実現してみた
|> Express/Go的なPhoenix軽量APIの上に参照系REST APIを実装する
|> 軽量APIにて実装した一覧REST APIにDBを接続する
|> DB取得+軽量APIで1件参照REST APIを実装する(TDD付)
|> DB追加/削除+軽量APIで更新系REST API(暫定版)を実装する(TDD付)

残対応①:id自動発番

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

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

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からの戻り値をそのまま素通しする造りなので、改修不要です

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を設定します

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

image.png

「POST http://localhost:4000/api/v1/users」でデータ追加が行われ、idが、投入済みデータの最大である4の次の5が振られていることが確認できます
image.png

一覧でもid=5での追加が確認できます
image.png

「POST http://localhost:4000/api/v1/users」で再度データ追加すると、次は、id=6が振られます
image.png

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

1)create/deleteのAPIコントローラを実装不要に

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

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

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

2)create.json.eex内でDBデータ追加

APIコントローラのcreate()に書いていたDBアクセッサの呼び出しを、create.json.eexという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" ] }' )" )

3)delete.json.eex内でDBデータ削除

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

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

手順③:動作確認

1)追加REST APIの動作確認

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

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

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

image.png

「POST http://localhost:4000/api/v1/users」で追加が行われ、id=7のデータが追加されれば成功です
image.png

「GET http://localhost:4000/api/v1/users」で一覧し、id=7の追加を確認します
image.png

2)削除REST APIの動作確認

次に、削除の確認です

「DELETE http://localhost:4000/api/v1/users/7」で削除を行います
image.png

「GET http://localhost:4000/api/v1/users」で一覧し、id=7が削除されたことを確認します
image.png

終わり

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

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

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

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

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

次回は、いよいよREST APIのラスト、更新REST APIを実装します

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

image.pngimage.png にて、どうぞ応援よろしくお願いします:bow:

2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?