Current section
Files
Jump to
Current section
Files
lib/repo/queryable.ex
defmodule ExAudit.Queryable do
@version_schema Application.get_env(:ex_audit, :version_schema)
require Logger
def update_all(module, adapter, queryable, updates, opts) do
Ecto.Repo.Queryable.update_all(module, adapter, queryable, updates, opts)
end
def delete_all(module, adapter, queryable, opts) do
Ecto.Repo.Queryable.delete_all(module, adapter, queryable, opts)
end
def history(module, adapter, queryable, opts) do
import Ecto.Query
query = from v in @version_schema,
order_by: v.recorded_at
# TODO what do when we get a query
query = case queryable do
# %Ecto.Query{from: struct} ->
# from v in query,
# where: v.entity_id == subquery(from q in queryable, select: q.id),
# where: v.entity_schema == ^struct
%{__struct__: struct, id: id} when nil not in [struct, id] ->
from v in query,
where: v.entity_id == ^id,
where: v.entity_schema == ^struct
end
Ecto.Repo.Queryable.all(module, adapter, query, opts)
end
@drop_fields [:__meta__, :__struct__]
def revert(module, _adapter, version, opts) do
import Ecto.Query
# get the history of the entity after this version
query = from v in @version_schema,
where: v.entity_id == ^version.entity_id,
where: v.entity_schema == ^version.entity_schema,
where: v.recorded_at >= ^version.recorded_at,
order_by: [desc: v.recorded_at]
versions = module.all(query)
# get the referenced struct as it exists now
struct = module.one(from s in version.entity_schema, where: s.id == ^version.entity_id)
result = Enum.reduce(versions, struct, &_revert/2)
result = if result |> Map.keys() |> length() == 0 do
nil
else
result
end
schema = version.entity_schema
{action, changeset} = case {struct, result} do
{nil, %{}} -> {:insert, schema.changeset(struct(schema, %{}), Map.drop(result, @drop_fields))}
{%{}, nil} -> {:delete, struct}
{nil, nil} -> {nil, nil}
_ -> {:update, schema.changeset(struct, Map.drop(result, @drop_fields))}
end
opts = Keyword.update(opts, :ex_audit_custom, [rollback: true], fn custom -> [{:rollback, true} | custom] end)
if action do
res = apply(module, action, [changeset, opts])
case action do
:delete -> {:ok, nil}
_ -> res
end
else
Logger.warn(["Can't revert ", inspect(version), " because the entity would still be deleted"])
{:ok, nil}
end
end
defp _revert(version, struct) do
apply_change(reverse_action(version.action), ExAudit.Diff.reverse(version.patch), struct)
end
defp apply_change(:updated, patch, to) do
ExAudit.Patch.patch(to, patch)
end
defp apply_change(:deleted, _patch, _to) do
%{}
end
defp apply_change(:created, patch, _to) do
ExAudit.Patch.patch(%{}, patch)
end
defp reverse_action(:updated), do: :updated
defp reverse_action(:created), do: :deleted
defp reverse_action(:deleted), do: :created
end