Current section
Files
Jump to
Current section
Files
lib/extools/ecto.ex
defmodule Extools.Ecto do
@moduledoc false
@doc """
Auto-detect the Ecto Repo module used by the host application.
Strategy (in order):
1. Explicit config: `config :extools, repo: MyApp.Repo`
2. `config :extools, otp_app: :my_app` then scan that app's modules
3. Scan `Application.loaded_applications()` for a module whose
behaviour list includes `Ecto.Repo`
Returns `{:ok, module}` or `{:error, reason}`.
"""
@spec detect_repo() :: {:ok, module} | {:error, term()}
def detect_repo do
with {:error, _} <- from_config(:repo),
{:error, _} <- from_otp_app_config(),
{:error, _} <- scan_all_apps() do
{:error,
"No Ecto.Repo module found. Set `config :extools, repo: MyApp.Repo` or `config :extools, otp_app: :my_app`."}
end
end
defp from_config(:repo) do
case Application.get_env(:extools, :repo) do
nil -> {:error, :not_configured}
repo when is_atom(repo) -> {:ok, repo}
repo when is_binary(repo) -> {:ok, String.to_atom("Elixir." <> repo)}
end
end
defp from_otp_app_config do
case Application.get_env(:extools, :otp_app) do
nil -> {:error, :not_configured}
app -> scan_app(app)
end
end
defp scan_all_apps do
loaded =
Application.loaded_applications()
|> Enum.map(fn {app, _, _} -> app end)
Enum.find_value(loaded, {:error, :not_found}, fn app ->
case scan_app(app) do
{:ok, repo} -> {:ok, repo}
{:error, _} -> nil
end
end)
end
defp scan_app(app) do
modules = Application.spec(app, :modules) || []
Enum.find_value(modules, {:error, :not_found}, fn mod ->
if repo_behaviour?(mod), do: {:ok, mod}, else: nil
end)
end
defp repo_behaviour?(mod) do
Code.ensure_loaded?(mod) and function_exported?(mod, :__adapter__, 0) and
is_ecto_repo?(mod)
rescue
_ -> false
catch
_, _ -> false
end
defp is_ecto_repo?(mod) do
behaviours =
mod.module_info(:attributes)
|> Keyword.get_values(:behaviour)
|> List.flatten()
Ecto.Repo in behaviours
end
end