defmodule Archeometer.Collect.Ecto do
@moduledoc """
Module for collecting information about Ecto schemas and their associations between them.
"""
alias Archeometer.Repo
alias Exqlite.Sqlite3, as: DB
def ecto_schemas(extra_deps \\ []) do
Archeometer.Collect.Project.local_modules(extra_deps)
|> Enum.reduce([], fn module, acc ->
if is_ecto_schema?(module) do
embedded_schemas = get_embedded_schemas(module)
embedded_schemas ++ [module | acc]
else
acc
end
end)
end
def get_associations(modules) do
Enum.reduce(modules, [], fn module, acc ->
Enum.map(
module.__schema__(:associations),
&%{
caller: module,
callee: get_related_module(module, &1),
type: "ecto_#{get_assoc_type(module, &1)}",
line: nil
}
) ++ acc
end)
end
defp get_related_module(module, association) do
assoc_data = module.__schema__(:association, association)
case assoc_data.__struct__ do
Ecto.Association.HasThrough ->
process_has_through(module, assoc_data.through)
_ ->
assoc_data.related
end
end
defp process_has_through(module, [assoc]) do
reflection = module.__schema__(:association, assoc)
if Map.has_key?(reflection, :related) do
reflection.related
else
process_has_through(module, reflection.through)
end
end
defp process_has_through(module, [assoc | rest]) do
reflection = module.__schema__(:association, assoc)
if Map.has_key?(reflection, :related) do
process_has_through(reflection.related, rest)
else
process_has_through(module, reflection.through)
end
end
defp get_assoc_type(module, association) do
assoc = module.__schema__(:association, association)
relation =
assoc.__struct__
|> Module.split()
|> List.last()
|> Macro.underscore()
case relation do
"belongs_to" ->
relation
"many_to_many" ->
relation
_ ->
cardinality = Atom.to_string(assoc.cardinality)
relation <> "_" <> cardinality
end
end
defp is_ecto_schema?(nil), do: false
defp is_ecto_schema?(module) do
if Keyword.has_key?(module.__info__(:functions), :__struct__) do
case Map.get(module.__struct__(), :__meta__, :no_meta) do
%{__struct__: Ecto.Schema.Metadata} -> true
_ -> false
end
end
end
def clear_references(db_name \\ Repo.default_db_name()) do
{:ok, conn} = DB.open(db_name)
:ok = DB.execute(conn, "DELETE FROM xrefs WHERE type LIKE 'ecto%';")
DB.close(conn)
end
defp get_embedded_schemas(module) do
module.__schema__(:embeds)
|> Enum.map(&module.__schema__(:embed, &1))
|> Enum.map(&Map.get(&1, :related))
end
end