Elixir
Phoenix

Elixir / Phoenix1.3のAPI開発入門 END

はじめに

今回が一連の流れの最後になります。delete機能を実装します。

これまでの回は下記です。
* Elixir/Phoenix1.3のAPI開発入門 その1
* Elixir/Phoenix1.3のAPI開発入門 その2
* Elixir/Phoenix1.3のAPI開発入門 その3

ルーティングはすでに追加済みなので何もしません。

テストプログラムの追加

いつものようにControllerのテストを書いていきます。削除したらステータスコードが204であることを確認するテストです。

test/api_server_web/controllers/user_controller_test.exs
defmodule ApiServerWeb.UserControllerTest do
  use ApiServerWeb.ConnCase
  alias ApiServer.Accounts.User

  @create_attrs %{name: "testuser", age: 20}
  @update_attrs %{name: "testuser2", age: 25}

  describe "index" do
      test "Get list all users", %{conn: conn} do
        conn = get(conn, user_path(conn, :index))
        assert json_response(conn, 200)["data"] == []
      end
  end

  describe "create" do
    test "Create user", %{conn: conn} do
      conn = post(conn, user_path(conn, :create), @create_attrs)
      assert %{"id" => id} = json_response(conn, 201)["data"]
    end
  end

  describe "show" do
    setup [:create_user]

    test "Show user", %{conn: conn, user: %User{id: id}} do
      conn = get(conn, user_path(conn, :show, id))
      assert json_response(conn, 200)["data"] == %{"id"=> id, "name"=> "testuser", "age"=> 20}
    end
  end

  describe "update" do
    setup [:create_user]

    test "Update user", %{conn: conn, user: %User{id: id}} do
      conn = put(conn, user_path(conn, :update, id), user: @update_attrs)
      assert json_response(conn, 200)["data"] == %{"id"=> id, "name"=> "testuser2", "age"=> 25}
    end

  end

  describe "delete" do
    setup [:create_user]

    test "Delete user", %{conn: conn, user: %User{id: id}} do
      conn = delete(conn, user_path(conn, :delete, id))
      assert response(conn, 204)
    end

  end

  defp create_user(_) do
    {:ok, user} = ApiServer.Accounts.create_user(@create_attrs)
    {:ok, user: user}
  end

end

テストを実行してみます。

$ mix test
...warning: variable "id" is unused
  test/api_server_web/controllers/user_controller_test.exs:18

.

  1) test delete Delete user (ApiServerWeb.UserControllerTest)
     test/api_server_web/controllers/user_controller_test.exs:44
     ** (UndefinedFunctionError) function ApiServerWeb.UserController.delete/2 is undefined or private
     code: conn = delete(conn, user_path(conn, :delete, id))
     stacktrace:
       (api_server) ApiServerWeb.UserController.delete(%Plug.Conn{adapter: {Plug.Adapters.Test.Conn, :...}, assigns: %{}, before_send: [#Function<1.77730059/1 in Plug.Logger.call/2>], body_params: %{}, cookies: %Plug.Conn.Unfetched{aspect: :cookies}, halted: false, host: "www.example.com", method: "DELETE", owner: #PID<0.321.0>, params: %{"id" => "127"}, path_info: ["api", "users", "127"], path_params: %{"id" => "127"}, peer: {{127, 0, 0, 1}, 111317}, port: 80, private: %{ApiServerWeb.Router => {[], %{}}, :phoenix_action => :delete, :phoenix_controller => ApiServerWeb.UserController, :phoenix_endpoint => ApiServerWeb.Endpoint, :phoenix_format => "json", :phoenix_layout => {ApiServerWeb.LayoutView, :app}, :phoenix_pipelines => [:api], :phoenix_recycled => true, :phoenix_router => ApiServerWeb.Router, :phoenix_view => ApiServerWeb.UserView, :plug_session_fetch => #Function<1.112984571/1 in Plug.Session.fetch_session/1>, :plug_skip_csrf_protection=> true}, query_params: %{}, query_string: "", remote_ip: {127, 0, 0, 1}, req_cookies: %Plug.Conn.Unfetched{aspect: :cookies}, req_headers: [], request_path: "/api/users/127", resp_body: nil, resp_cookies: %{}, resp_headers: [{"cache-control", "max-age=0, private, must-revalidate"}, {"x-request-id", "0v3u9hrtuolb4qddi0ojtu54s8jv9io9"}], scheme: :http, script_name: [], secret_key_base: "VtrFwAGAAoDixeHbmOxwHXDunjBsEXn/e3Dq+5+ByRMecPEATVGvEz5vK4dSGmnw", state: :unset, status: nil}, %{"id" => "127"})
       (api_server) lib/api_server_web/controllers/user_controller.ex:1: ApiServerWeb.UserController.action/2
       (api_server) lib/api_server_web/controllers/user_controller.ex:1: ApiServerWeb.UserController.phoenix_controller_pipeline/2
       (api_server) lib/api_server_web/endpoint.ex:1: ApiServerWeb.Endpoint.instrument/4
       (phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
       (api_server) lib/api_server_web/endpoint.ex:1: ApiServerWeb.Endpoint.plug_builder_call/2
       (api_server) lib/api_server_web/endpoint.ex:1: ApiServerWeb.Endpoint.call/2
       (phoenix) lib/phoenix/test/conn_test.ex:224: Phoenix.ConnTest.dispatch/5
       test/api_server_web/controllers/user_controller_test.exs:45: (test)

...

Finished in 0.2 seconds
8 tests, 1 failure

Randomized with seed 981956

もう慣れてきましたね、Controllerの作成をやっていきます。

Controllerの追加

delete関数を定義します。削除処理したら204で返却するだけなので、send_respという関数を使って実装します。
詳しくは下記の公式ドキュメントをご覧ください。
https://hexdocs.pm/plug/Plug.Conn.html#send_resp/1

lib/api_server_web/controllers/user_controller.ex
defmodule ApiServerWeb.UserController do
  use ApiServerWeb, :controller
  alias ApiServer.Accounts
  alias ApiServer.Accounts.User

  def index(conn, params) do
    users = Accounts.list_users
    render(conn, "index.json", users: users )
  end

  def create(conn, params) do
    {:ok, %User{} = user} = Accounts.create_user(params)
    conn
    |> put_status(:created)
    |> render("show.json", user: user)
  end

  def show(conn, %{"id" => id}) do
    user = Accounts.get_user!(id)
    render(conn, "show.json", user: user)
  end

  def update(conn, %{"id"=>id, "user"=> user_param}) do
    {:ok, user} = Accounts.update_user(id, user_param)
    render(conn, "show.json", user: user)
  end

  def delete(conn, %{"id"=>id}) do
    #TODO 削除処理
    send_resp(conn, :no_content, "")
  end

end

テストを実行します。

$ mix test
Compiling 2 files (.ex)
warning: variable "params" is unused
  lib/api_server_web/controllers/user_controller.ex:6

warning: variable "id" is unused
  lib/api_server_web/controllers/user_controller.ex:28

...warning: variable "id" is unused
  test/api_server_web/controllers/user_controller_test.exs:18

.....

Finished in 0.2 seconds
8 tests, 0 failures

Randomized with seed 995507

ここで通るんですよ、いつもなら怒られるんですけどw
今回はこの時点でグリーンになりました。あとは直接ユーザ情報を削除する機能を実装するだけです。ということはviewを実装する必要がなさそうですね。

Viewの追加

こちらはありません。

リファクタリング

では削除処理ができるようにデータベース側にdelete_user関数を追加していきましょう。

データベースでデータ削除

ユーザ情報を取得して、それをRepoのdelete関数に渡すだけです。

lib/api_server/accounts/accounts.ex
defmodule ApiServer.Accounts do
  @moduledoc """
  The Accounts context 
  """
  import Ecto.Query, warn: false
  alias ApiServer.Repo
  alias ApiServer.Accounts.User

  def list_users do
    Repo.all(User)
  end

  def create_user(attrs \\ %{}) do
    %User{}
    |> User.changeset(attrs)
    |> Repo.insert()
  end

  def get_user!(id) do
    Repo.get!(User, id)
  end

  def update_user(id, attrs \\ %{}) do
    get_user!(id)
    |> User.changeset(attrs)
    |> Repo.update()
  end

  def delete_user(id) do
    get_user!(id)
    |> Repo.delete()
  end

end

Controllerの改修

Controller側に先ほど作成したdelete_user関数を実装します。

lib/api_server_web/controllers/user_controller.ex
defmodule ApiServerWeb.UserController do
  use ApiServerWeb, :controller
  alias ApiServer.Accounts
  alias ApiServer.Accounts.User

  def index(conn, params) do
    users = Accounts.list_users
    render(conn, "index.json", users: users )
  end

  def create(conn, params) do
    {:ok, %User{} = user} = Accounts.create_user(params)
    conn
    |> put_status(:created)
    |> render("show.json", user: user)
  end

  def show(conn, %{"id" => id}) do
    user = Accounts.get_user!(id)
    render(conn, "show.json", user: user)
  end

  def update(conn, %{"id"=>id, "user"=> user_param}) do
    {:ok, user} = Accounts.update_user(id, user_param)
    render(conn, "show.json", user: user)
  end

  def delete(conn, %{"id"=>id}) do
    res = Accounts.delete_user(id)
    send_resp(conn, :no_content, "")
  end

end

テストを実行します。

$ mix test
Compiling 1 file (.ex)
warning: variable "params" is unused
  lib/api_server_web/controllers/user_controller.ex:6

warning: variable "res" is unused
  lib/api_server_web/controllers/user_controller.ex:29

...warning: variable "id" is unused
  test/api_server_web/controllers/user_controller_test.exs:18

.....

Finished in 0.2 seconds
8 tests, 0 failures

Randomized with seed 161780

Viewの改修

こちらはありません。

実際に試してみる

それでは実際に試してみます。
まずサーバーを起動します。

$ mix phx.server

まずは現在どんなユーザーが登録されているか確認してみます。(1件も出ない方はcreateしてください)
スクリーンショット 2017-12-27 14.25.37.png

id=1のユーザを削除したいと思います。実行してみると何も返ってはきませんが、HTTPステータスコードが204であることは確認できます。
スクリーンショット 2017-12-27 14.27.56.png

もう一度ユーザ一覧を取得してみます。ID=1のユーザが消えていますね!

スクリーンショット 2017-12-27 14.28.53.png

まとめ

今回全部で4つの記事で書いていきました。この記事の目的は、作り方の流れを掴んでいくというのを一番の目的としているので、その流れが掴めたら書いた甲斐があったかなぁと思います。(とりあえず社内メンバーにしてもらおうw)
実際には準正常系などの処理を入れる必要があると思います。それに合わせたテストなども書く必要があると思います。

ソースコード

https://github.com/yujikawa/phoenix_sample_api