From 1e7dd40363747706ed5c4b66b79fe02dabcc8569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20M=C3=A4nnchen?= Date: Fri, 4 Dec 2020 01:20:43 +0000 Subject: [PATCH] Allow to configure Originator Field Options (#115) also adds better support for :binary_id UUIDs --- .circleci/config.yml | 1 + README.md | 19 +++++++- config/test.exs | 10 +++- lib/paper_trail/serializer.ex | 9 ++-- lib/version.ex | 8 ++-- .../20201130190530_create_projects.exs | 12 +++++ .../20201130190545_create_people.exs | 12 +++++ .../20201130190555_create_versions.exs | 22 +++++++++ test/support/repos.ex | 4 ++ test/support/uuid_with_custom_name_models.exs | 36 ++++++++++++++ test/test_helper.exs | 2 + test/uuid/uuid_with_custom_name_test.exs | 48 +++++++++++++++++++ 12 files changed, 175 insertions(+), 8 deletions(-) create mode 100644 priv/uuid_with_custom_name_repo/migrations/20201130190530_create_projects.exs create mode 100644 priv/uuid_with_custom_name_repo/migrations/20201130190545_create_people.exs create mode 100644 priv/uuid_with_custom_name_repo/migrations/20201130190555_create_versions.exs create mode 100644 test/support/uuid_with_custom_name_models.exs create mode 100644 test/uuid/uuid_with_custom_name_test.exs diff --git a/.circleci/config.yml b/.circleci/config.yml index d7a63a03..36ea93a8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -27,6 +27,7 @@ jobs: - run: docker-compose run paper_trail mix test test/paper_trail - run: docker-compose run paper_trail mix test test/version - run: docker-compose run paper_trail mix test test/uuid + - run: docker-compose run paper_trail mix test test/uuid_with_custom_name workflows: version: 2 diff --git a/README.md b/README.md index d2199f6c..fb80d448 100644 --- a/README.md +++ b/README.md @@ -162,9 +162,26 @@ YES! Make sure you do the steps above. If you are using UUID or another type for your primary keys, you can configure the PaperTrail.Version schema to use it. +##### Example Config + ```elixir config :paper_trail, item_type: Ecto.UUID, - originator_type: Ecto.UUID + originator_type: Ecto.UUID, + originator_relationship_options: [references: :uuid] +``` + +###### Example User + +```elixir +defmodule Acme.User do + use Ecto.Schema + + @primary_key {:uuid, :binary_id, autogenerate: true} + schema "users" do + field :email, :string + + timestamps() + end ``` Remember to edit the types accordingly in the generated migration. diff --git a/config/test.exs b/config/test.exs index 98eeebed..86bd7346 100644 --- a/config/test.exs +++ b/config/test.exs @@ -1,6 +1,6 @@ use Mix.Config -config :paper_trail, ecto_repos: [PaperTrail.Repo, PaperTrail.UUIDRepo] +config :paper_trail, ecto_repos: [PaperTrail.Repo, PaperTrail.UUIDRepo, PaperTrail.UUIDWithCustomNameRepo] config :paper_trail, repo: PaperTrail.Repo, originator: [name: :user, model: User] @@ -20,4 +20,12 @@ config :paper_trail, PaperTrail.UUIDRepo, hostname: System.get_env("PG_HOST"), poolsize: 10 +config :paper_trail, PaperTrail.UUIDWithCustomNameRepo, + adapter: Ecto.Adapters.Postgres, + username: System.get_env("POSTGRES_USER"), + password: System.get_env("POSTGRES_PASSWORD"), + database: "paper_trail_uuid_with_custom_name_test", + hostname: System.get_env("PG_HOST"), + poolsize: 10 + config :logger, level: :warn diff --git a/lib/paper_trail/serializer.ex b/lib/paper_trail/serializer.ex index 9073457d..5af4fde7 100644 --- a/lib/paper_trail/serializer.ex +++ b/lib/paper_trail/serializer.ex @@ -26,7 +26,8 @@ defmodule PaperTrail.Serializer do originator_id: case originator_ref do nil -> nil - _ -> originator_ref |> Map.get(:id) + %{id: id} -> id + model when is_struct(model) -> get_model_id(originator_ref) end, origin: options[:origin], meta: options[:meta] @@ -46,7 +47,8 @@ defmodule PaperTrail.Serializer do originator_id: case originator_ref do nil -> nil - _ -> originator_ref |> Map.get(:id) + %{id: id} -> id + model when is_struct(model) -> get_model_id(originator_ref) end, origin: options[:origin], meta: options[:meta] @@ -66,7 +68,8 @@ defmodule PaperTrail.Serializer do originator_id: case originator_ref do nil -> nil - _ -> originator_ref |> Map.get(:id) + %{id: id} -> id + model when is_struct(model) -> get_model_id(originator_ref) end, origin: options[:origin], meta: options[:meta] diff --git a/lib/version.ex b/lib/version.ex index 2c206f88..b10e9c1d 100644 --- a/lib/version.ex +++ b/lib/version.ex @@ -27,9 +27,11 @@ defmodule PaperTrail.Version do belongs_to( PaperTrail.RepoClient.originator()[:name], PaperTrail.RepoClient.originator()[:model], - define_field: false, - foreign_key: :originator_id, - type: Application.get_env(:paper_trail, :originator_type, :integer) + Keyword.merge(Application.get_env(:paper_trail, :originator_relationship_options, []), + define_field: false, + foreign_key: :originator_id, + type: Application.get_env(:paper_trail, :originator_type, :integer) + ) ) end diff --git a/priv/uuid_with_custom_name_repo/migrations/20201130190530_create_projects.exs b/priv/uuid_with_custom_name_repo/migrations/20201130190530_create_projects.exs new file mode 100644 index 00000000..378407eb --- /dev/null +++ b/priv/uuid_with_custom_name_repo/migrations/20201130190530_create_projects.exs @@ -0,0 +1,12 @@ +defmodule PaperTrail.UUIDWithCustomNameRepo.Migrations.CreateProjects do + use Ecto.Migration + + def change do + create table(:projects, primary_key: false) do + add :uuid, :binary_id, primary_key: true + add :name, :string, null: false + + timestamps() + end + end +end diff --git a/priv/uuid_with_custom_name_repo/migrations/20201130190545_create_people.exs b/priv/uuid_with_custom_name_repo/migrations/20201130190545_create_people.exs new file mode 100644 index 00000000..2275b1b0 --- /dev/null +++ b/priv/uuid_with_custom_name_repo/migrations/20201130190545_create_people.exs @@ -0,0 +1,12 @@ +defmodule PaperTrail.UUIDWithCustomNameRepo.Migrations.CreatePeople do + use Ecto.Migration + + def change do + create table(:people, primary_key: false) do + add :uuid, :binary_id, primary_key: true + add :email, :string, null: false + + timestamps() + end + end +end diff --git a/priv/uuid_with_custom_name_repo/migrations/20201130190555_create_versions.exs b/priv/uuid_with_custom_name_repo/migrations/20201130190555_create_versions.exs new file mode 100644 index 00000000..993f42bc --- /dev/null +++ b/priv/uuid_with_custom_name_repo/migrations/20201130190555_create_versions.exs @@ -0,0 +1,22 @@ +defmodule PaperTrail.UUIDWithCustomNameRepo.Migrations.CreateVersions do + use Ecto.Migration + + def change do + create table(:versions) do + add :event, :string, null: false, size: 10 + add :item_type, :string, null: false + add :item_id, (if System.get_env("STRING_TEST") == nil, do: :binary_id, else: :string) + add :item_changes, :map, null: false + add :originator_id, references(:people, type: :binary_id, column: :uuid) + add :origin, :string, size: 50 + add :meta, :map + + add :inserted_at, :utc_datetime, null: false + end + + create index(:versions, [:originator_id]) + create index(:versions, [:item_id, :item_type]) + create index(:versions, [:event, :item_type]) + create index(:versions, [:item_type, :inserted_at]) + end +end diff --git a/test/support/repos.ex b/test/support/repos.ex index 9c4f2180..793a2d49 100644 --- a/test/support/repos.ex +++ b/test/support/repos.ex @@ -6,6 +6,10 @@ defmodule PaperTrail.UUIDRepo do use Ecto.Repo, otp_app: :paper_trail, adapter: Ecto.Adapters.Postgres end +defmodule PaperTrail.UUIDWithCustomNameRepo do + use Ecto.Repo, otp_app: :paper_trail, adapter: Ecto.Adapters.Postgres +end + defmodule User do use Ecto.Schema diff --git a/test/support/uuid_with_custom_name_models.exs b/test/support/uuid_with_custom_name_models.exs new file mode 100644 index 00000000..dfb87339 --- /dev/null +++ b/test/support/uuid_with_custom_name_models.exs @@ -0,0 +1,36 @@ +defmodule Project do + use Ecto.Schema + + import Ecto.Changeset + + @primary_key {:uuid, :binary_id, autogenerate: true} + schema "projects" do + field(:name, :string) + + timestamps() + end + + def changeset(model, params \\ %{}) do + model + |> cast(params, [:name]) + |> validate_required([:name]) + end +end + +defmodule Person do + use Ecto.Schema + import Ecto.Changeset + + @primary_key {:uuid, :binary_id, autogenerate: true} + schema "people" do + field(:email, :string) + + timestamps() + end + + def changeset(model, params \\ %{}) do + model + |> cast(params, [:email]) + |> validate_required([:email]) + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs index e88f5d5c..16fb8ccf 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -6,11 +6,13 @@ Mix.Task.run("ecto.migrate") PaperTrail.Repo.start_link() PaperTrail.UUIDRepo.start_link() +PaperTrail.UUIDWithCustomNameRepo.start_link() Code.require_file("test/support/multi_tenant_helper.exs") Code.require_file("test/support/simple_models.exs") Code.require_file("test/support/strict_models.exs") Code.require_file("test/support/uuid_models.exs") +Code.require_file("test/support/uuid_with_custom_name_models.exs") ExUnit.configure(seed: 0) diff --git a/test/uuid/uuid_with_custom_name_test.exs b/test/uuid/uuid_with_custom_name_test.exs new file mode 100644 index 00000000..af348542 --- /dev/null +++ b/test/uuid/uuid_with_custom_name_test.exs @@ -0,0 +1,48 @@ +defmodule PaperTrailTest.UUIDWithCustomNameTest do + use ExUnit.Case + import PaperTrail.RepoClient, only: [repo: 0] + alias PaperTrail.Version + import Ecto.Query + + setup_all do + Application.put_env(:paper_trail, :repo, PaperTrail.UUIDWithCustomNameRepo) + Application.put_env(:paper_trail, :originator, name: :originator, model: Person) + Application.put_env(:paper_trail, :originator_type, Ecto.UUID) + Application.put_env(:paper_trail, :originator_relationship_options, references: :uuid) + + Application.put_env( + :paper_trail, + :item_type, + if(System.get_env("STRING_TEST") == nil, do: Ecto.UUID, else: :string) + ) + + Code.eval_file("lib/paper_trail.ex") + Code.eval_file("lib/version.ex") + + repo().delete_all(Version) + repo().delete_all(Person) + repo().delete_all(Project) + :ok + end + + describe "PaperTrailTest.UUIDWithCustomNameTest" do + test "handles originators with a UUID primary key" do + person = + %Person{} + |> Person.changeset(%{email: "admin@example.com"}) + |> repo().insert! + + %Project{} + |> Project.changeset(%{name: "Interesting Stuff"}) + |> PaperTrail.insert!(originator: person) + + version = + Version + |> last + |> repo().one + |> repo().preload(:originator) + + assert version.originator == person + end + end +end