Current section

Files

Jump to
ex_audit lib tracking tracking.ex
Raw

lib/tracking/tracking.ex

defmodule ExAudit.Tracking do
@version_schema Application.get_env(:ex_audit, :version_schema)
@tracked_schemas Application.get_env(:ex_audit, :tracked_schemas)
@ignored_fields [:__meta__, :__struct__]
import Ecto.Query
def find_changes(action, struct_or_changeset, resulting_struct) do
old = case {action, struct_or_changeset} do
{:created, _} -> %{}
{_, %Ecto.Changeset{data: struct}} -> struct
{_, %{} = struct} -> struct
{_, nil} -> %{}
end
new = case action do
x when x in [:updated, :created] ->
resulting_struct
:deleted -> %{}
end
compare_versions(action, old, new)
end
def compare_versions(action, old, new) do
schema = Map.get(old, :__struct__, Map.get(new, :__struct__))
if schema in @tracked_schemas do
assocs = schema.__schema__(:associations)
ignored_fields = @ignored_fields ++ assocs
patch = ExAudit.Diff.diff(
Map.drop(old, ignored_fields),
Map.drop(new, ignored_fields)
)
case patch do
:not_changed -> []
patch ->
params = %{
entity_id: Map.get(old, :id) || Map.get(new, :id),
entity_schema: schema,
patch: patch,
action: action
}
[params]
end
else
[]
end
end
def track_change(module, adapter, action, changeset, resulting_struct, opts) do
if not Keyword.get(opts, :ignore_audit, false) do
changes = find_changes(action, changeset, resulting_struct)
insert_versions(module, adapter, changes, opts)
end
end
def insert_versions(module, adapter, changes, opts) do
now = DateTime.utc_now
custom_fields =
Keyword.get(opts, :ex_audit_custom, [])
|> Enum.into(%{})
changes = Enum.map(changes, fn change ->
change = Map.put(change, :recorded_at, now)
Map.merge(change, custom_fields)
end)
case changes do
[] -> :ok
_ ->
Ecto.Repo.Schema.insert_all(module, adapter, @version_schema, changes, opts)
end
end
def find_assoc_deletion(module, adapter, struct, repo_opts) do
struct = case struct do
%Ecto.Changeset{} -> Ecto.Changeset.apply_changes(struct)
_ -> struct
end
id = struct.id
schema = struct.__struct__
assocs =
schema.__schema__(:associations)
|> Enum.map(fn field -> {field, schema.__schema__(:association, field)} end)
|> Enum.filter(fn {_, opts} -> Map.get(opts, :on_delete) == :delete_all end)
assocs
|> Enum.flat_map(fn {field, _opts} ->
root = module.all(Ecto.assoc(struct, field))
root ++ Enum.map(root, &find_assoc_deletion(module, adapter, &1, repo_opts))
end)
|> List.flatten()
|> Enum.flat_map(&compare_versions(:deleted, &1, %{}))
end
def track_assoc_deletion(module, adapter, struct, opts) do
deleted_structs = find_assoc_deletion(module, adapter, struct, opts)
insert_versions(module, adapter, deleted_structs, opts)
end
end