defmodule TrackIndices do @moduledoc """ Provides index documentation for schema modules. ## Usage Add `use TrackIndices` to your schema module and define @indices: defmodule MyApp.MySchema do use Ecto.Schema use TrackIndices @indices [ %{columns: [:user_id], unique: false}, %{columns: [:email], unique: true} ] schema "my_table" do # ... end end This will generate a `__indices__/0` function that returns the documented indices. ## Index Format Each index is a map with the following keys: * `:columns` - List of column names (atoms) * `:unique` - Boolean indicating if the index is unique * `:where` - (Optional) Partial index condition as a string ## Example @indices [ %{columns: [:email], unique: true}, %{columns: [:event_id, :email], unique: true, where: "email IS NOT NULL"} ] """ defmacro __using__(_opts) do quote do Module.register_attribute(__MODULE__, :track_indices_table, persist: true) @before_compile TrackIndices end end defmacro __before_compile__(env) do indices = Module.get_attribute(env.module, :indices, []) quote do @doc """ Returns the documented indices for this schema. """ def __indices__ do unquote(Macro.escape(indices)) end @doc """ Returns the table name for this schema. """ def __table_name__ do __schema__(:source) end end end @doc """ Returns all modules that use TrackIndices along with their table names. """ def all_schemas do # Get all loaded applications and their modules :application.loaded_applications() |> Enum.flat_map(fn {app, _, _} -> case :application.get_key(app, :modules) do {:ok, modules} -> modules _ -> [] end end) |> Enum.filter(fn module -> Code.ensure_loaded?(module) and function_exported?(module, :__table_name__, 0) end) |> Enum.map(fn module -> {module, module.__table_name__()} end) |> Enum.reject(fn {_module, table} -> is_nil(table) end) end @doc """ Gets the Ecto repo from application config. """ def get_repo do case Application.get_env(:track_indices, :repo) do nil -> raise_no_config_error() repo when is_atom(repo) -> repo other -> raise_invalid_repo_error(other) end end defp raise_no_config_error do raise """ No repo configured for TrackIndices. Please add to your config/config.exs: config :track_indices, repo: YourApp.Repo """ end defp raise_invalid_repo_error(other) do raise "Expected :repo to be a module, got: #{inspect(other)}" end @doc """ Retrieves documented indices from all schema modules. Returns a map of table_name => [documented_index] """ def get_documented_indices do all_schemas() |> Enum.reduce(%{}, fn {module, table_name}, acc -> indices = module.__indices__() Map.put(acc, table_name, indices) end) end end