defmodule SchemaAssertions.Schema do @moduledoc "Ecto schema introspection" @doc "Returns true if the given module has a belongs_to relationship" @spec belongs_to?(module(), atom(), module()) :: :ok | {:error, String.t()} def belongs_to?(module, assoc_name, assoc_module) do case module.__schema__(:association, assoc_name) do %Ecto.Association.BelongsTo{queryable: ^assoc_module} -> :ok %Ecto.Association.BelongsTo{queryable: queryable} -> {:error, "Found module: #{queryable}"} _other -> {:error, "Association not found"} end end @doc "Returns true if the given module is an Ecto schema" @spec ecto_schema?(module()) :: boolean() def ecto_schema?(module) do function_exported?(module, :__schema__, 1) end @doc "Returns true if the schema has a has_many relationship" @spec has_many?(module(), atom(), module() | Keyword.t()) :: :ok | {:error, atom(), String.t()} def has_many?(module, assoc_name, assoc_module_or_options) do case assoc_module_or_options do through: through -> has_many_through?(module, assoc_name, through) _ -> case module.__schema__(:association, assoc_name) do %Ecto.Association.Has{queryable: ^assoc_module_or_options} -> :ok %Ecto.Association.Has{queryable: queryable} -> {:error, :has_many, "Found module: #{queryable}"} _other -> {:error, :has_many, "Association not found"} end end end @doc "Returns true if the given module exists" @spec module_exists?(module()) :: boolean() def module_exists?(module) do Code.ensure_loaded?(module) end @doc "Returns the database table name for the given module" @spec table_name(module()) :: binary() def table_name(module) do module.__schema__(:source) end # # # defp has_many_through?(module, assoc_name, through) do case module.__schema__(:association, assoc_name) do %Ecto.Association.HasThrough{through: ^through} -> :ok %Ecto.Association.HasThrough{through: actual} -> {:error, :has_many_through, "Found: #{inspect(actual)}"} _other -> {:error, :has_many_through, "Association not found"} end end end