Packages
paper_trail
0.14.2
1.1.2
1.1.1
1.1.0
1.0.0
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.0
0.12.4
0.12.3
0.12.2
0.12.1
0.12.0
0.11.3
0.11.2
0.11.1
0.11.0
0.10.1
0.10.0
0.9.0
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.3
0.5.2
0.5.0
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
0.0.9
0.0.8
0.0.7
0.0.6
0.0.5
0.0.4
0.0.3
0.0.2
0.0.1
Track and record all the changes in your database. Revert back to anytime in history.
Current section
Files
Jump to
Current section
Files
lib/paper_trail/multi.ex
defmodule PaperTrail.Multi do
import Ecto.Changeset
alias PaperTrail
alias PaperTrail.Version
alias PaperTrail.RepoClient
alias PaperTrail.Serializer
defdelegate new(), to: Ecto.Multi
defdelegate append(lhs, rhs), to: Ecto.Multi
defdelegate error(multi, name, value), to: Ecto.Multi
defdelegate merge(multi, merge), to: Ecto.Multi
defdelegate merge(multi, mod, fun, args), to: Ecto.Multi
defdelegate prepend(lhs, rhs), to: Ecto.Multi
defdelegate run(multi, name, run), to: Ecto.Multi
defdelegate run(multi, name, mod, fun, args), to: Ecto.Multi
defdelegate to_list(multi), to: Ecto.Multi
defdelegate make_version_struct(version, model, options), to: Serializer
defdelegate serialize(data), to: Serializer
defdelegate get_sequence_id(table_name), to: Serializer
defdelegate add_prefix(schema, prefix), to: Serializer
defdelegate get_item_type(data), to: Serializer
defdelegate get_model_id(model), to: Serializer
@default_transaction_options [
origin: nil,
meta: nil,
originator: nil,
prefix: nil,
model_key: :model,
version_key: :version,
initial_version_key: :initial_version,
ecto_options: []
]
def insert(%Ecto.Multi{} = multi, changeset, options \\ @default_transaction_options) do
model_key = options[:model_key] || :model
version_key = options[:version_key] || :version
initial_version_key = options[:initial_version_key] || :initial_version
ecto_options = options[:ecto_options] || []
case RepoClient.strict_mode() do
true ->
multi
|> Ecto.Multi.run(initial_version_key, fn repo, %{} ->
version_id = get_sequence_id("versions") + 1
changeset_data =
Map.get(changeset, :data, changeset)
|> Map.merge(%{
id: get_sequence_id(changeset) + 1,
first_version_id: version_id,
current_version_id: version_id
})
initial_version = make_version_struct(%{event: "insert"}, changeset_data, options)
repo.insert(initial_version)
end)
|> Ecto.Multi.run(model_key, fn repo, %{^initial_version_key => initial_version} ->
updated_changeset =
changeset
|> change(%{
first_version_id: initial_version.id,
current_version_id: initial_version.id
})
repo.insert(updated_changeset, ecto_options)
end)
|> Ecto.Multi.run(version_key, fn repo,
%{
^initial_version_key => initial_version,
^model_key => model
} ->
target_version = make_version_struct(%{event: "insert"}, model, options) |> serialize()
Version.changeset(initial_version, target_version) |> repo.update
end)
_ ->
multi
|> Ecto.Multi.insert(model_key, changeset, ecto_options)
|> Ecto.Multi.run(version_key, fn repo, %{^model_key => model} ->
version = make_version_struct(%{event: "insert"}, model, options)
repo.insert(version)
end)
end
end
def update(%Ecto.Multi{} = multi, changeset, options \\ @default_transaction_options) do
model_key = options[:model_key] || :model
version_key = options[:version_key] || :version
initial_version_key = options[:initial_version_key] || :initial_version
ecto_options = options[:ecto_options] || []
case RepoClient.strict_mode() do
true ->
multi
|> Ecto.Multi.run(initial_version_key, fn repo, %{} ->
version_data =
changeset.data
|> Map.merge(%{
current_version_id: get_sequence_id("versions")
})
target_changeset = changeset |> Map.merge(%{data: version_data})
target_version = make_version_struct(%{event: "update"}, target_changeset, options)
repo.insert(target_version)
end)
|> Ecto.Multi.run(model_key, fn repo, %{^initial_version_key => initial_version} ->
updated_changeset = changeset |> change(%{current_version_id: initial_version.id})
repo.update(updated_changeset, Keyword.take(options, [:returning]))
end)
|> Ecto.Multi.run(version_key, fn repo, %{^initial_version_key => initial_version} ->
new_item_changes =
initial_version.item_changes
|> Map.merge(%{
current_version_id: initial_version.id
})
initial_version |> change(%{item_changes: new_item_changes}) |> repo.update
end)
_ ->
multi
|> Ecto.Multi.update(
model_key,
changeset,
ecto_options ++ Keyword.take(options, [:returning])
)
|> Ecto.Multi.run(version_key, fn repo, %{^model_key => _model} ->
version = make_version_struct(%{event: "update"}, changeset, options)
repo.insert(version)
end)
end
end
def insert_or_update(%Ecto.Multi{} = multi, changeset, options \\ @default_transaction_options) do
case get_state(changeset) do
:built ->
insert(multi, changeset, options)
:loaded ->
update(multi, changeset, options)
state ->
raise ArgumentError,
"the changeset has an invalid state " <>
"for PaperTrail.insert_or_update/2 or PaperTrail.insert_or_update!/2: #{state}"
end
end
def delete(%Ecto.Multi{} = multi, struct, options \\ @default_transaction_options) do
model_key = options[:model_key] || :model
version_key = options[:version_key] || :version
ecto_options = options[:ecto_options] || []
multi
|> Ecto.Multi.delete(model_key, struct, ecto_options)
|> Ecto.Multi.run(version_key, fn repo, %{} ->
version = make_version_struct(%{event: "delete"}, struct, options)
repo.insert(version, options)
end)
end
def commit(%Ecto.Multi{} = multi) do
repo = RepoClient.repo()
transaction = repo.transaction(multi)
case RepoClient.strict_mode() do
true ->
case transaction do
{:error, _, changeset, %{}} ->
filtered_changes =
Map.drop(changeset.changes, [:current_version_id, :first_version_id])
{:error, Map.merge(changeset, %{repo: repo, changes: filtered_changes})}
{:ok, map} ->
{:ok, Map.drop(map, [:initial_version])}
end
_ ->
case transaction do
{:error, _, changeset, %{}} -> {:error, Map.merge(changeset, %{repo: repo})}
_ -> transaction
end
end
end
defp get_state(%Ecto.Changeset{data: %{__meta__: %{state: state}}}), do: state
defp get_state(%{__struct__: _}) do
raise ArgumentError,
"giving a struct to PaperTrail.insert_or_update/2 or " <>
"PaperTrail.insert_or_update!/2 is not supported. " <>
"Please use an Ecto.Changeset"
end
end