fukuoka.ex/kokura.exのpiacereです
ご覧いただいて、ありがとうございます
前作の軽量APIでの参照REST API実装を元に、DB接続を実装してみます
DBは、ローカルPostgreSQLやCloud SQLとかでは無く、Elixir標準のインメモリ&ローカルファイルDB「Mnesia」でサクっと済ませます(これ以降のどこかで、外部APIやGraphQLでサクっとやってもいいかも知れない)
本コラムの検証環境
本コラムは、以下環境で検証しています(Windowsで実施していますが、Linuxやmacでも動作する想定です)
- Windows 10
- Elixir 1.10.1 ※最新版のインストール手順はコチラ
- Phoenix 1.4.15 ※最新版のインストール手順はコチラ
- Node.js 12.14.0
あと下記コラムシリーズの続きとして実施するので、未実施であれば、事前に実施しておいてください(近々、ボイラープレート生成のmixコマンドとしてOSS化は考えています)
PHP的ハックを応用してNode.js Express/Go的な軽量APIをPhoenixで実現してみた
|> Express/Go的なPhoenix軽量APIの上に参照系REST APIを実装する
手順①:Mnediaテーブル作成、データ投入
まず、Mnesiaを起動し、テーブル作成します
このテーブルは、ファイル化して永続化してあるので、インメモリ時のような、Phoenixを落とすとDBが消えてしまうといったことがありません
iex> :mnesia.create_schema( [ node() ] )
:ok
iex> :mnesia.start
:ok
iex> :mnesia.create_table( :members, [ attributes: [ :id, :name, :age, :team, :position ], disc_copies: [ node() ] ] )
{:atomic, :ok}
次に、データ投入用コードを用意します
defmodule Basic do
def inserts() do
:mnesia.transaction( fn -> :mnesia.write( { :members, 1, "enぺだーし", 49, "有限会社デライトシステムズ", "代表取締役、性能探求者" } ) end )
:mnesia.transaction( fn -> :mnesia.write( { :members, 2, "ざっきー", 45, "公立大学法人 北九州市立大学", "准教授、カーネルハッカー" } ) end )
:mnesia.transaction( fn -> :mnesia.write( { :members, 3, "つちろー", 34, "カラビナテクノロジー株式会社", "リードエンジニア、アプリマイスター" } ) end )
:mnesia.transaction( fn -> :mnesia.write( { :members, 4, "piacere", 43, "カラビナテクノロジー株式会社", "CTO、福岡Elixirプログラマ、重力プログラマ、技術顧問" } ) end )
end
end
データ投入します
iex> recompile
Compiling 1 file (.ex)
:ok
iex> Basic.inserts
{:atomic, :ok}
手順②:DBアクセッサモジュールを作り、select処理を追加
PJフォルダ内のlibフォルダ配下にutilフォルダを掘り、下記コラムと同一のDBアクセスモジュールを作ります
Excelから関数型言語マスター3回目:WebにDBデータ表示【Mnesia編】
https://qiita.com/piacerex/items/a7558adc6856e3577dc6
defmodule DbMnesia do
def select( table_name ) do
:mnesia.start
table_atom = table_name |> String.to_atom
:mnesia.wait_for_tables( [ table_atom ], 1000 )
columns = :mnesia.table_info( table_atom, :attributes )
|> Enum.reduce( [], fn( item, acc ) -> acc ++ [ Atom.to_string( item ) ] end )
columns_spec = 1..Enum.count( columns )
|> Enum.reduce( { table_atom }, fn( x, acc ) -> Tuple.append( acc, :"$#{ x }" ) end )
rows = :mnesia.transaction( fn ->
:mnesia.select( table_atom, [ { columns_spec, [], [ :"$$" ] } ] ) end ) |> elem( 1 )
%{
columns: columns,
command: :select,
connection_id: 0,
num_rows: Enum.count( rows ),
rows: rows
}
end
end
defmodule Db do
def query( sql ) when sql != "" do
Regex.named_captures( ~r/select( *)(?<columns>.*)( *)from( *)(?<tables>.*)/, sql )[ "tables" ]
|> DbMnesia.select
end
def columns_rows( result ) do
result
|> rows
|> Enum.map( fn row -> Enum.into( List.zip( [ columns( result ), row ] ), %{} ) end )
end
def rows( %{ rows: rows } = _result ), do: rows
def columns( %{ columns: columns } = _result ), do: columns
end
手順③:index.json.eex内でDBデータ取得
上記で投入したDBデータを取得し、JSON化していきます
まず、1件JSON返却を、DBデータ列から作るようにします
%{
name: data[ "name" ],
age: data[ "age" ],
team: data[ "team" ],
position: data[ "position" ]
}
次に、DBデータ取得し、for式の中で、データ1件ずつをdata指定してテンプレート展開を繰り返します
json = File.read!( "lib/basic_web/templates/api/v1/users/data.json.eex" )
datas = Db.query( "select * from members" ) |> Db.columns_rows
for data <- datas do
json |> Code.eval_string( [ params: params, data: data ] ) |> elem( 0 )
end
手順④:動作確認
これで準備完了です
RESTクライアントで「GET http://localhost:4000/api/v1/users」
のアクセスを行うと、以下のように、DBからデータ取得した行が、一覧としてJSON返却されます
終わり
JSONテンプレートでDBデータ取得を行い、一覧REST APIにてJSON返却できるようにしました
次回は、DBモジュールをwhere対応しつつ、1件ずつのDB値取得に対応します