LoginSignup
11
12

More than 5 years have passed since last update.

PhoenixでDB CRUD以外のJSONを返すAPIを作る

Last updated at Posted at 2018-09-29

fukuoka.ex代表のpiacereです
今回もご覧いただいて、ありがとうございます:bow:

Phoenixのmix phx.gen.jsonで作成したAPIは、DB(テーブル)と1対1のCRUD APIを自動生成してくれて便利なので、mix phx.gen.jsonした結果を改造して、DB CURD以外のJSONを返すAPIを作る手順をまとめてみます

なお、mix phx.gen.json自体の使い方については、Elixir入門「第3回:Phoenix 1.3で高速webアプリ & REST APIアプリをサクッと書いてみる」のP22以降をご覧ください
image.png

mix phx.gen.jsonが生成するcontroller/viewの構造

上記スライドに挙げている例の通り、mix phx.gen.jsonでAPIを作成します

mix phx.gen.json Tools Post posts title:string body:text

すると、controllersフォルダ配下に、以下のようなモジュールが作成されます

lib/api_web/controllers/post_controller.ex
defmodule ApiWeb.PostController do
  
  def index(conn, _params) do
    api = Tools.list_posts()
    render(conn, "index.json", api: api)
  end

この自動生成されたコントローラは、index()であれば、テーブルデータ全件のマップリストを、render()に渡し、JSONデータとして返却します

なお、index()以外も、だいたい同じ構造となっています

テーブルデータ全件を取得するRepo.all()は、以下のような定義が自動生成されます(index()以外が使うCRUD関数も、このモジュール中に定義されています)

lib/api/tools/tools.ex
defmodule Api.Tools do
  
  def list_posts do
    Repo.all(Post)
  end
  

その後、呼ばれるrender()は、ビューとして自動生成され、3つの関数で構成されます

lib/api_web/views/post_view.ex
defmodule ApiWeb.PostView do
  use SampleAnalyticsWeb, :view
  alias SampleAnalyticsWeb.PostView

  def render("index.json", %{posts: posts}) do
    %{data: render_many(posts, PostView, "post.json")}
  end

  def render("show.json", %{post: post}) do
    %{data: render_one(post, PostView, "post.json")}
  end

  def render("post.json", %{post: post}) do
    %{id: post.id,
      title: post.title,
      body: post.body}
  end

index()が使うのは、1つ目の関数と、3つ目の関数です

1つ目の関数(index)で使われるrender_many()は、複数件のマップリストとして返却します

2つ目の関数(show)で使われるrender_one()は、単品のマップとして返却します

3つ目の関数は、render_many()とrender_one()のどちらからも呼ばれる、単品マップを返却するためのコールバック的な関数で、ここでDB列を表す構造体を、返却するJSONに相当するマップへと変換しています(render_many()は、この関数を、各行毎に呼び出しています)

DB CURD以外のJSONを返却するAPIを作る

DB CURD以外のJSONを返却するAPIを作るためには、コントローラにDB CRUD以外の任意の処理をさせ、ビューのrender()では、DB列の構造体で無いものをマップとして返せば良い、ということです

まず、コントローラからrender()に渡すデータを変更します

下記の例では、元々あったテーブルデータ全件を返却する代わりに、Excelionを使って、Excelデータを読み込み、そのマップリストを返却しています(なお、Excelは、1行目に列ヘッダーがあるデータを想定しており、1行目は読み飛ばしています)

lib/api_web/controllers/post_controller.ex
defmodule ApiWeb.PostController do
  
  def index(conn, _params) do
    api = "sample.xlsx"
        |> Excelion.parse!( 0, 1 )
        |> Enum.drop( 1 )
        |> MapList.zip_atom( &( Enum.zip( [ columns, &1 ] ) ) )
        |> Enum.map( &( &1 |> Enum.reduce( %{}, fn { key, value }, acc -> Map.put( acc, key |> String.to_atom, value ) end ) ) )
    render(conn, "index.json", api: api)
  end

ビューのrender()の方は、controllerから渡されたマップリストをそのまま返却すればOKなので、DB列の構造体に置き換えしていた部分を、引数そのままで返却するように変えます

lib/api_web/views/post_view.ex
defmodule ApiWeb.PostView do
  
  def render("post.json", %{post: post}) do
    post
  end
end

こんな感じで、DB CURD以外のJSONを返すAPIが作れます

マップリスト/マップを返せばJSON APIが作られる世界へ

上記のやったことをまとめると、以下の通りです

① コントローラで、任意のマップリスト/マップを作る
② ビューのrender()は引数をそのまま返す

ここには、他言語だと当たり前な、JSONをチマチマ組み立てる処理は、一切、出てきません

にも関わらず、JSON APIがちゃんと定義できてしまう…という、ちょっとした魔法のような出来事です

Elixirの強力なリスト処理をうまく応用した、Phoenixの造りに脱帽です:laughing:

なお、APIのURLを変えたり、モジュール名を変えるコツ、ビューのrender()を使わずに処理する方法などもありますが、これはまた別のコラムにて

p.s.「いいね」よろしくお願いします

よろしければ、ページ左上の image.pngimage.png のクリックをお願いしますー:bow:

11
12
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
11
12