defmodule Needle.Pointers do
@moduledoc """
A context for working with Needle, a sort of global foreign key scheme.
"""
import Ecto.Query
alias Needle.{Pointer, Tables}
@doc """
Returns a Pointer, either the one provided or a synthesised one
pointing to the provided schema object. Does not hit the database or
cause the pointer to be written to the database whatsoever.
"""
def cast!(%Pointer{} = p), do: p
def cast!(%struct{id: id}), do: %Pointer{id: id, table_id: Tables.id!(struct)}
@doc "Looks up the table for a given pointer"
def table(%Pointer{table_id: id}), do: Tables.table!(id)
def schema(%Pointer{table_id: id}), do: Tables.schema!(id)
@doc """
Return the provided pointer when it belongs to table queryable by the given table search term.
"""
def assert_points_to!(%Pointer{table_id: table} = pointer, term) do
if Tables.id!(term) == table, do: pointer, else: raise(ArgumentError)
end
@doc """
Given a list of pointers which may or may have their pointed loaded,
return a plan for preloading, a map of module name to set of loadable IDs.
"""
def plan(pointers) when is_list(pointers),
do: Enum.reduce(pointers, %{}, &plan/2)
defp plan(%Pointer{pointed: p}, acc) when not is_nil(p), do: acc
defp plan(%Pointer{id: id, table_id: table}, acc) do
Map.update(
acc,
Tables.schema!(table),
MapSet.new([id]),
&MapSet.put(&1, id)
)
end
@doc """
Returns a basic query over non-deleted pointable objects in the system,
optionally limited to one or more types.
If the type is set to a Pointable, Virtual or Mixin schema, records
will be selected from that schema directly. It is assumed this
filters deleted records by construction.
Otherwise, will query from Pointer, filtering not is_nil(deleted_at)
"""
def query_base(type \\ nil)
def query_base([]), do: query_base(Pointer)
def query_base(nil), do: query_base(Pointer)
def query_base(:include_deleted), do: from(p in Pointer, as: :main_object)
def query_base(Pointer), do: from(p in Pointer, as: :main_object, where: is_nil(p.deleted_at))
def query_base(schemas) when is_list(schemas) do
table_ids = Enum.map(schemas, &get_table_id!/1)
from(p in query_base(Pointer), where: p.table_id in ^table_ids)
end
def query_base(schema) when is_atom(schema) or is_binary(schema) do
# ensure it's a pointable or virtual or table id
get_table_id!(schema)
from(s in schema, select: s)
end
def get_table_id!(schema) do
if is_binary(schema),
do: schema,
else: with(nil <- Needle.Util.table_id(schema), do: need_pointable(schema))
end
defp need_pointable(got) do
raise RuntimeError,
message:
"Expected a table id or pointable or virtual schema module name, got: #{inspect(got)}"
end
end