Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@ config :logger, level: :info

config :ex_audit,
ecto_repos: [ExAudit.Test.Repo],
version_schema: ExAudit.Test.Version,
tracked_schemas: [
ExAudit.Test.User,
ExAudit.Test.BlogPost,
ExAudit.Test.BlogPost.Section,
ExAudit.Test.Comment
],
ecto_repos_schemas: %{
ExAudit.Test.Repo => %{
version_schema: ExAudit.Test.Version,
tracked_schemas: [
ExAudit.Test.User,
ExAudit.Test.BlogPost,
ExAudit.Test.BlogPost.Section,
ExAudit.Test.Comment
]
}
},
primitive_structs: [
Date
]
16 changes: 7 additions & 9 deletions lib/repo/queryable.ex
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
defmodule ExAudit.Queryable do
require Logger

defp version_schema() do
Application.get_env(:ex_audit, :version_schema)
end

@compile {:inline, version_schema: 0}

def update_all(module, queryable, updates, opts) do
Ecto.Repo.Queryable.update_all(module, queryable, updates, opts)
end
Expand All @@ -20,7 +14,7 @@ defmodule ExAudit.Queryable do

query =
from(
v in version_schema(),
v in version_schema(module),
order_by: [desc: :recorded_at]
)

Expand Down Expand Up @@ -64,7 +58,7 @@ defmodule ExAudit.Queryable do

versions ++
[
struct(version_schema(), %{
struct(version_schema(module), %{
id: oldest_id
})
|> Map.put(:original, empty_map_to_nil(oldest_struct))
Expand All @@ -83,7 +77,7 @@ defmodule ExAudit.Queryable do

query =
from(
v in version_schema(),
v in version_schema(module),
where: v.entity_id == ^version.entity_id,
where: v.entity_schema == ^version.entity_schema,
where: v.recorded_at >= ^version.recorded_at,
Expand Down Expand Up @@ -176,4 +170,8 @@ defmodule ExAudit.Queryable do
defp reverse_action(:updated), do: :updated
defp reverse_action(:created), do: :deleted
defp reverse_action(:deleted), do: :created

defp version_schema(repo_module) do
Application.get_env(:ex_audit, :ecto_repos_schemas) |> get_in([repo_module, :version_schema])
end
end
24 changes: 12 additions & 12 deletions lib/repo/repo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ defmodule ExAudit.Repo do
delete!: 2
)

defp tracked?(struct_or_changeset) do
tracked_schemas = Application.get_env(:ex_audit, :tracked_schemas, [])
@compile {:inline, tracked?: 2}

defp tracked?(repo_module, struct_or_changeset) do
tracked_schemas = ExAudit.Tracking.tracked_schemas(repo_module)

schema =
case struct_or_changeset do
Expand All @@ -72,10 +74,8 @@ defmodule ExAudit.Repo do
schema in tracked_schemas
end

@compile {:inline, tracked?: 1}

def insert(struct, opts) do
if tracked?(struct) do
if tracked?(__MODULE__, struct) do
ExAudit.Schema.insert(
__MODULE__,
get_dynamic_repo(),
Expand All @@ -88,7 +88,7 @@ defmodule ExAudit.Repo do
end

def update(struct, opts) do
if tracked?(struct) do
if tracked?(__MODULE__, struct) do
ExAudit.Schema.update(
__MODULE__,
get_dynamic_repo(),
Expand All @@ -101,7 +101,7 @@ defmodule ExAudit.Repo do
end

def insert_or_update(changeset, opts) do
if tracked?(changeset) do
if tracked?(__MODULE__, changeset) do
ExAudit.Schema.insert_or_update(
__MODULE__,
get_dynamic_repo(),
Expand All @@ -114,7 +114,7 @@ defmodule ExAudit.Repo do
end

def delete(struct, opts) do
if tracked?(struct) do
if tracked?(__MODULE__, struct) do
ExAudit.Schema.delete(
__MODULE__,
get_dynamic_repo(),
Expand All @@ -127,7 +127,7 @@ defmodule ExAudit.Repo do
end

def insert!(struct, opts) do
if tracked?(struct) do
if tracked?(__MODULE__, struct) do
ExAudit.Schema.insert!(
__MODULE__,
get_dynamic_repo(),
Expand All @@ -140,7 +140,7 @@ defmodule ExAudit.Repo do
end

def update!(struct, opts) do
if tracked?(struct) do
if tracked?(__MODULE__, struct) do
ExAudit.Schema.update!(
__MODULE__,
get_dynamic_repo(),
Expand All @@ -153,7 +153,7 @@ defmodule ExAudit.Repo do
end

def insert_or_update!(changeset, opts) do
if tracked?(changeset) do
if tracked?(__MODULE__, changeset) do
ExAudit.Schema.insert_or_update!(
__MODULE__,
get_dynamic_repo(),
Expand All @@ -166,7 +166,7 @@ defmodule ExAudit.Repo do
end

def delete!(struct, opts) do
if tracked?(struct) do
if tracked?(__MODULE__, struct) do
ExAudit.Schema.delete!(
__MODULE__,
get_dynamic_repo(),
Expand Down
43 changes: 33 additions & 10 deletions lib/repo/schema.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
defmodule ExAudit.Schema do
require Logger

def insert_all(module, name, schema_or_source, entries, opts) do
# TODO!
opts = augment_opts(opts)
Expand All @@ -13,7 +15,7 @@ defmodule ExAudit.Schema do

case result do
{:ok, resulting_struct} ->
ExAudit.Tracking.track_change(module, :created, struct, resulting_struct, opts)
track_change(module, :created, struct, resulting_struct, opts)

_ ->
:ok
Expand All @@ -31,7 +33,7 @@ defmodule ExAudit.Schema do

case result do
{:ok, resulting_struct} ->
ExAudit.Tracking.track_change(module, :updated, struct, resulting_struct, opts)
track_change(module, :updated, struct, resulting_struct, opts)

_ ->
:ok
Expand All @@ -50,7 +52,7 @@ defmodule ExAudit.Schema do
case result do
{:ok, resulting_struct} ->
state = if changeset.data.__meta__.state == :loaded, do: :updated, else: :created
ExAudit.Tracking.track_change(module, state, changeset, resulting_struct, opts)
track_change(module, state, changeset, resulting_struct, opts)

_ ->
:ok
Expand All @@ -64,12 +66,12 @@ defmodule ExAudit.Schema do
opts = augment_opts(opts)

augment_transaction(module, fn ->
ExAudit.Tracking.track_assoc_deletion(module, struct, opts)
track_assoc_deletion(module, struct, opts)
result = Ecto.Repo.Schema.delete(module, name, struct, opts)

case result do
{:ok, resulting_struct} ->
ExAudit.Tracking.track_change(module, :deleted, struct, resulting_struct, opts)
track_change(module, :deleted, struct, resulting_struct, opts)

_ ->
:ok
Expand All @@ -86,7 +88,7 @@ defmodule ExAudit.Schema do
module,
fn ->
result = Ecto.Repo.Schema.insert!(module, name, struct, opts)
ExAudit.Tracking.track_change(module, :created, struct, result, opts)
track_change(module, :created, struct, result, opts)
result
end,
true
Expand All @@ -100,7 +102,7 @@ defmodule ExAudit.Schema do
module,
fn ->
result = Ecto.Repo.Schema.update!(module, name, struct, opts)
ExAudit.Tracking.track_change(module, :updated, struct, result, opts)
track_change(module, :updated, struct, result, opts)
result
end,
true
Expand All @@ -115,7 +117,7 @@ defmodule ExAudit.Schema do
fn ->
result = Ecto.Repo.Schema.insert_or_update!(module, name, changeset, opts)
state = if changeset.data.__meta__.state == :loaded, do: :updated, else: :created
ExAudit.Tracking.track_change(module, state, changeset, result, opts)
track_change(module, state, changeset, result, opts)
result
end,
true
Expand All @@ -128,9 +130,9 @@ defmodule ExAudit.Schema do
augment_transaction(
module,
fn ->
ExAudit.Tracking.track_assoc_deletion(module, struct, opts)
track_assoc_deletion(module, struct, opts)
result = Ecto.Repo.Schema.delete!(module, name, struct, opts)
ExAudit.Tracking.track_change(module, :deleted, struct, result, opts)
track_change(module, :deleted, struct, result, opts)
result
end,
true
Expand Down Expand Up @@ -174,4 +176,25 @@ defmodule ExAudit.Schema do
end ++ custom_fields
end)
end

# It wraps Tracking.track_change in a try rescue block because we don't want to crash the caller process when there is exception in track_change
# It's opinionated here that tracking is something that's not mission critical and thus an exception caused by bug should
# TODO make this try rescue behaviour configurable
defp track_change(module, action, changeset, resulting_struct, opts) do
ExAudit.Tracking.track_change(module, action, changeset, resulting_struct, opts)
rescue
e ->
:error
|> Exception.format(e, __STACKTRACE__)
|> Logger.error(crash_reason: {e, __STACKTRACE__})
end

def track_assoc_deletion(module, struct, opts) do
ExAudit.Tracking.track_assoc_deletion(module, struct, opts)
rescue
e ->
:error
|> Exception.format(e, __STACKTRACE__)
|> Logger.error(crash_reason: {e, __STACKTRACE__})
end
end
6 changes: 4 additions & 2 deletions lib/repo/schema_type.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ defmodule ExAudit.Type.Schema do

def type, do: :string

defp schemas do
Application.get_env(:ex_audit, :tracked_schemas, [])
defp schemas() do
Application.get_env(:ex_audit, :ecto_repos_schemas)
|> Map.values()
|> Enum.flat_map(& &1.tracked_schemas)
end
end
22 changes: 11 additions & 11 deletions lib/tracking/tracking.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule ExAudit.Tracking do
def find_changes(action, struct_or_changeset, resulting_struct) do
def find_changes(module, action, struct_or_changeset, resulting_struct) do
old =
case {action, struct_or_changeset} do
{:created, _} -> %{}
Expand All @@ -17,13 +17,13 @@ defmodule ExAudit.Tracking do
%{}
end

compare_versions(action, old, new)
compare_versions(module, action, old, new)
end

def compare_versions(action, old, new) do
def compare_versions(module, action, old, new) do
schema = Map.get(old, :__struct__, Map.get(new, :__struct__))

if schema in tracked_schemas() do
if schema in tracked_schemas(module) do
assocs = schema.__schema__(:associations)

patch =
Expand Down Expand Up @@ -53,7 +53,7 @@ defmodule ExAudit.Tracking do

def track_change(module, action, changeset, resulting_struct, opts) do
if not Keyword.get(opts, :ignore_audit, false) do
changes = find_changes(action, changeset, resulting_struct)
changes = find_changes(module, action, changeset, resulting_struct)

insert_versions(module, changes, opts)
end
Expand All @@ -78,7 +78,7 @@ defmodule ExAudit.Tracking do

_ ->
opts = Keyword.drop(opts, [:on_conflict, :conflict_target])
module.insert_all(version_schema(), changes, opts)
module.insert_all(version_schema(module), changes, opts)
end
end

Expand All @@ -102,7 +102,7 @@ defmodule ExAudit.Tracking do
root ++ Enum.map(root, &find_assoc_deletion(module, &1, repo_opts))
end)
|> List.flatten()
|> Enum.flat_map(&compare_versions(:deleted, &1, %{}))
|> Enum.flat_map(&compare_versions(module, :deleted, &1, %{}))
end

def track_assoc_deletion(module, struct, opts) do
Expand All @@ -111,11 +111,11 @@ defmodule ExAudit.Tracking do
insert_versions(module, deleted_structs, opts)
end

defp tracked_schemas do
Application.get_env(:ex_audit, :tracked_schemas, [])
def tracked_schemas(repo_module) do
Application.get_env(:ex_audit, :ecto_repos_schemas) |> get_in([repo_module, :tracked_schemas])
end

defp version_schema do
Application.get_env(:ex_audit, :version_schema)
defp version_schema(repo_module) do
Application.get_env(:ex_audit, :ecto_repos_schemas) |> get_in([repo_module, :version_schema])
end
end
17 changes: 16 additions & 1 deletion test/ex_audit_test.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule ExAuditTest do
use ExUnit.Case
use ExUnit.Case, async: false
doctest ExAudit

import Ecto.Query
Expand Down Expand Up @@ -245,4 +245,19 @@ defmodule ExAuditTest do

assert 2 = Repo.aggregate(query, :count, :id)
end

test "will not crash the caller process if the tracking " do
original = Application.get_env(:ex_audit, :ecto_repos_schemas)
Application.put_env(:ex_audit, :ecto_repos, :crash)

ExUnit.Callbacks.on_exit(fn ->
Application.put_env(:ex_audit, :ecto_repos, original)
end)

user = Util.create_user()

changeset = User.changeset(user, %{transient_field: 3})

assert {:ok, user} = Repo.update(changeset)
end
end
1 change: 0 additions & 1 deletion test/test_helper.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
ExAudit.Test.Repo.start_link()
Ecto.Adapters.SQL.Sandbox.mode(ExAudit.Test.Repo, :auto)


migrations_path = Path.join([:code.priv_dir(:ex_audit), "repo", "migrations"])
Ecto.Migrator.run(ExAudit.Test.Repo, migrations_path, :up, all: true)

Expand Down