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