diff --git a/lib/paper_trail.ex b/lib/paper_trail.ex index 9618f38b..fbe3ebd5 100644 --- a/lib/paper_trail.ex +++ b/lib/paper_trail.ex @@ -15,7 +15,7 @@ defmodule PaperTrail do @doc """ Inserts a record to the database with a related version insertion in one transaction """ - def insert(changeset, options \\ [origin: nil, meta: nil, originator: nil, prefix: nil, model_key: :model, version_key: :version]) do + def insert(changeset, options \\ [origin: nil, meta: nil, originator: nil, prefix: nil, model_key: :model, version_key: :version, ecto_options: []]) do PaperTrail.Multi.new() |> PaperTrail.Multi.insert(changeset, options) |> PaperTrail.Multi.commit() @@ -24,8 +24,9 @@ defmodule PaperTrail do @doc """ Same as insert/2 but returns only the model struct or raises if the changeset is invalid. """ - def insert!(changeset, options \\ [origin: nil, meta: nil, originator: nil, prefix: nil, model_key: :model, version_key: :version]) do + def insert!(changeset, options \\ [origin: nil, meta: nil, originator: nil, prefix: nil, model_key: :model, version_key: :version, ecto_options: []]) do repo = RepoClient.repo() + ecto_options = options[:ecto_options] || [] repo.transaction(fn -> case RepoClient.strict_mode() do @@ -51,13 +52,13 @@ defmodule PaperTrail do current_version_id: initial_version.id }) - model = repo.insert!(updated_changeset) + model = repo.insert!(updated_changeset, ecto_options) target_version = make_version_struct(%{event: "insert"}, model, options) |> serialize() Version.changeset(initial_version, target_version) |> repo.update! model _ -> - model = repo.insert!(changeset) + model = repo.insert!(changeset, ecto_options) make_version_struct(%{event: "insert"}, model, options) |> repo.insert! model end diff --git a/lib/paper_trail/multi.ex b/lib/paper_trail/multi.ex index 6bff1eea..da0b4090 100644 --- a/lib/paper_trail/multi.ex +++ b/lib/paper_trail/multi.ex @@ -16,10 +16,11 @@ defmodule PaperTrail.Multi do defdelegate to_list(multi), to: Ecto.Multi def insert(%Ecto.Multi{} = multi, changeset, options \\ [ - origin: nil, meta: nil, originator: nil, prefix: nil, model_key: :model, version_key: :version + origin: nil, meta: nil, originator: nil, prefix: nil, model_key: :model, version_key: :version, ecto_options: [] ]) do model_key = options[:model_key] || :model version_key = options[:version_key] || :version + ecto_options = options[:ecto_options] || [] case RepoClient.strict_mode() do true -> @@ -46,7 +47,7 @@ defmodule PaperTrail.Multi do current_version_id: initial_version.id }) - repo.insert(updated_changeset) + repo.insert(updated_changeset, ecto_options) end) |> Ecto.Multi.run(version_key, fn repo, %{initial_version: initial_version, model: model} -> target_version = make_version_struct(%{event: "insert"}, model, options) |> serialize() @@ -56,7 +57,7 @@ defmodule PaperTrail.Multi do _ -> multi - |> Ecto.Multi.insert(model_key, changeset) + |> 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) diff --git a/priv/repo/migrations/20200827222744_add_uniqueness_constraint_to_companies_name.exs b/priv/repo/migrations/20200827222744_add_uniqueness_constraint_to_companies_name.exs new file mode 100644 index 00000000..c757aff3 --- /dev/null +++ b/priv/repo/migrations/20200827222744_add_uniqueness_constraint_to_companies_name.exs @@ -0,0 +1,8 @@ +defmodule PaperTrail.Repo.Migrations.AddUniquenessConstraintToCompaniesName do + use Ecto.Migration + + def change do + create unique_index(:simple_companies, [:name]) + create unique_index(:strict_companies, [:name]) + end +end diff --git a/test/paper_trail/bang_functions_simple_mode_test.exs b/test/paper_trail/bang_functions_simple_mode_test.exs index d2344200..7097870c 100644 --- a/test/paper_trail/bang_functions_simple_mode_test.exs +++ b/test/paper_trail/bang_functions_simple_mode_test.exs @@ -79,6 +79,21 @@ defmodule PaperTrailTest.SimpleModeBangFunctions do end) end + test "PaperTrail.insert!/2 passes ecto options through (e.g. upsert options)" do + user = create_user() + _result = create_company_with_version(@create_company_params, [originator: user]) + + new_create_company_params = @create_company_params |> Map.replace!(:city, "Barcelona") + + ecto_options = [on_conflict: {:replace_all_except, ~w{name}a}, conflict_target: :name] + result = create_company_with_version(new_create_company_params, [originator: user, ecto_options: ecto_options]) + + assert Company.count() == 1 + assert Version.count() == 2 + + assert Map.take(serialize(result), [:name, :city]) == %{name: "Acme LLC", city: "Barcelona"} + end + test "updating a company with originator creates a correct company version" do user = create_user() inserted_company = create_company_with_version() diff --git a/test/paper_trail/bang_functions_strict_mode_test.exs b/test/paper_trail/bang_functions_strict_mode_test.exs index 4db650fd..a0f30cca 100644 --- a/test/paper_trail/bang_functions_strict_mode_test.exs +++ b/test/paper_trail/bang_functions_strict_mode_test.exs @@ -117,6 +117,21 @@ defmodule PaperTrailTest.StrictModeBangFunctions do end) end + test "PaperTrail.insert!/2 passes ecto options through (e.g. upsert options)" do + user = create_user() + _result = create_company_with_version(@create_company_params, [originator: user]) + + new_create_company_params = @create_company_params |> Map.replace!(:city, "Barcelona") + + ecto_options = [on_conflict: {:replace_all_except, ~w{name}a}, conflict_target: :name] + result = create_company_with_version(new_create_company_params, [originator: user, ecto_options: ecto_options]) + + assert Company.count() == 1 + assert Version.count() == 2 + + assert Map.take(serialize(result), [:name, :city]) == %{name: "Acme LLC", city: "Barcelona"} + end + test "updating a company creates a company version with correct item_changes" do user = create_user() inserted_company = create_company_with_version() diff --git a/test/paper_trail/base_test.exs b/test/paper_trail/base_test.exs index 32cff923..aae2615e 100644 --- a/test/paper_trail/base_test.exs +++ b/test/paper_trail/base_test.exs @@ -87,6 +87,21 @@ defmodule PaperTrailTest do assert result == ecto_result end + test "PaperTrail.insert/2 passes ecto options through (e.g. upsert options)" do + user = create_user() + {:ok, _result} = create_company_with_version(@create_company_params, [originator: user]) + + new_create_company_params = @create_company_params |> Map.replace!(:city, "Barcelona") + + ecto_options = [on_conflict: {:replace_all_except, ~w{name}a}, conflict_target: :name] + {:ok, result} = create_company_with_version(new_create_company_params, [originator: user, ecto_options: ecto_options]) + + assert Company.count() == 1 + assert Version.count() == 2 + + assert Map.take(serialize(result[:model]), [:name, :city]) == %{name: "Acme LLC", city: "Barcelona"} + end + test "updating a company with originator creates a correct company version" do user = create_user() {:ok, insert_result} = create_company_with_version() diff --git a/test/paper_trail/strict_mode_test.exs b/test/paper_trail/strict_mode_test.exs index e43a4d0d..7b874b06 100644 --- a/test/paper_trail/strict_mode_test.exs +++ b/test/paper_trail/strict_mode_test.exs @@ -126,6 +126,21 @@ defmodule PaperTrailStrictModeTest do assert result == ecto_result end + test "PaperTrail.insert/2 passes ecto options through (e.g. upsert options)" do + user = create_user() + {:ok, _result} = create_company_with_version(@create_company_params, [originator: user]) + + new_create_company_params = @create_company_params |> Map.replace!(:city, "Barcelona") + + ecto_options = [on_conflict: {:replace_all_except, ~w{name}a}, conflict_target: :name] + {:ok, result} = create_company_with_version(new_create_company_params, [originator: user, ecto_options: ecto_options]) + + assert Company.count() == 1 + assert Version.count() == 2 + + assert Map.take(serialize(result[:model]), [:name, :city]) == %{name: "Acme LLC", city: "Barcelona"} + end + test "updating a company creates a company version with correct item_changes" do user = create_user() {:ok, insert_company_result} = create_company_with_version()