defmodule ExTeal.Resource.Delete do
@moduledoc """
Defines a behaviour for deleting a resource and the function to execute it.
It relies on (and uses):
* ExTeal.Repo
* ExTeal.Record
When used ExTeal.Resource.Delete defines the `delete/2` action suitable for
handling teal delete requests.
To customize the behaviour of the update action the following callbacks can
be implemented:
* handle_delete/2
* ExTeal.Record.record/2
* ExTeal.Repo.repo/0
"""
import Ecto.Query, only: [where: 3]
import Plug.Conn
alias ExTeal.Resource.{Delete, Index, Serializer}
alias Plug.Conn
@doc """
Returns an unpersisted changeset or persisted model representing the newly updated model.
Receives the conn and the record as found by `record/2`.
Default implementation returns the results of calling `Repo.delete(record)`.
Example custom implementation:
def handle_delete(conn, record) do
case conn.assigns[:user] do
%{is_admin: true} -> super(conn, record)
_ -> send_resp(conn, 401, "nope")
end
end
"""
@callback handle_delete(Ecto.Query.t(), Conn.t()) :: {integer(), nil | [term()]}
defmacro __using__(_) do
quote do
use ExTeal.Resource.Repo
use ExTeal.Resource.Record
@behaviour ExTeal.Resource.Delete
def handle_delete(query, _conn) do
__MODULE__.repo().delete_all(query)
end
defoverridable handle_delete: 2
end
end
@doc """
Execute the delete action on a given module implementing Delete behaviour and conn.
"""
def call(resource, conn) do
resource
|> find_deleteable(conn)
|> resource.handle_delete(conn)
|> Delete.respond(conn)
end
@doc false
def respond(nil, conn), do: not_found(conn)
def respond(%Plug.Conn{} = conn, _old_conn), do: conn
def respond({0, nil}, conn), do: not_found(conn)
def respond({int, nil}, conn) when is_integer(int), do: deleted(conn)
def respond({:ok, _model}, conn), do: deleted(conn)
def respond({:errors, errors}, conn), do: invalid(conn, errors)
def respond({:error, errors}, conn), do: invalid(conn, errors)
def respond(_model, conn), do: deleted(conn)
defp not_found(conn) do
conn
|> send_resp(:not_found, "")
end
defp deleted(conn) do
conn
|> send_resp(:no_content, "")
end
defp invalid(conn, errors) do
conn
|> put_status(:unprocessable_entity)
|> Serializer.render_errors(errors)
end
defp find_deleteable(resource, %Conn{params: %{"resources" => "all"} = params} = conn) do
resource.model()
|> Index.filter_via_relationships(params)
|> Index.field_filters(conn, resource)
|> Index.search(params, resource)
end
defp find_deleteable(resource, %Conn{params: %{"resources" => ids}}) do
ids = ids |> String.split(",") |> Enum.map(&String.to_integer/1)
resource.model()
|> where([r], r.id in ^ids)
end
end