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