defmodule Ecto.SoftDelete.Query do @moduledoc """ functions for querying data that is (or is not) soft deleted """ import Ecto.Query @doc """ Returns a query that searches only for undeleted items query = from(u in User, select: u) |> with_undeleted results = Repo.all(query) """ @spec with_undeleted(Ecto.Queryable.t()) :: Ecto.Queryable.t() def with_undeleted(query) do if soft_deletable?(query) do query |> where([t], is_nil(t.deleted_at)) else query end end @doc """ Returns `true` if the query is soft deletable, `false` otherwise. query = from(u in User, select: u) |> soft_deletable? """ @spec soft_deletable?(Ecto.Queryable.t()) :: boolean() def soft_deletable?(query) do schema_module = get_schema_module(query) fields = if schema_module, do: schema_module.__schema__(:fields), else: [] Enum.member?(fields, :deleted_at) end @doc """ Returns `true` if the schema is not flagged to skip auto-filtering """ @spec auto_include_deleted_at_clause?(Ecto.Queriable.t()) :: boolean() def auto_include_deleted_at_clause?(query) do schema_module = get_schema_module(query) !Kernel.function_exported?(schema_module, :skip_soft_delete_prepare_query?, 0) || !schema_module.skip_soft_delete_prepare_query?() end defp get_schema_module({_raw_schema, module}) when not is_nil(module), do: module defp get_schema_module(%Ecto.Query{from: %{source: source}}), do: get_schema_module(source) defp get_schema_module(%Ecto.SubQuery{query: query}), do: get_schema_module(query) defp get_schema_module(_), do: nil end