diff --git a/README.md b/README.md index 24945865..51923223 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,10 @@ The library source code is minimal and well tested. It is suggested to read the ```mix papertrail.install``` + You might want to edit the types for `:event_id` and `:originator_id` if you're + using UUID or other types for your primary keys before you execute + `mix ecto.migrate`. + 5. run the migration: ```mix ecto.migrate``` @@ -139,13 +143,25 @@ YES! Make sure you do the steps above. | ------------- | ------- | -------------------------- | ------------------------ | | event | String | either insert, update or delete | Library generates | | item_type | String | model name of the reference record | Library generates | -| item_id | Integer | model id of the reference record | Library generates | +| item_id | configurable (Integer by default) | model id of the reference record | Library generates | | item_changes | Map | all the changes in this version as a map | Library generates | -| originator_id | Integer | foreign key reference to the creator/owner of this change | Optionally set | +| originator_id | configurable (Integer by default) | foreign key reference to the creator/owner of this change | Optionally set | | origin | String | short reference to origin(eg. worker:activity-checker, migration, admin:33) | Optionally set | | meta | Map | any extra optional meta information about the version(eg. %{slug: "ausername", important: true}) | Optionally set | | inserted_at | Date | inserted_at timestamp | Ecto generates | +#### Configuring the types + +If you are using UUID or another type for your primary keys, you can configure +the PaperTrail.Version schema to use it. + +```elixir +config :paper_trail, item_type: Ecto.UUID, + originator_type: Ecto.UUID +``` + +Remember to edit the types accordingly in the generated migration. + ### Version origin references: PaperTrail records have a string field called ```origin```. ```PaperTrail.insert/2```, ```PaperTrail.update/2```, ```PaperTrail.delete/2``` functions accept a second argument to describe the origin of this version: ```elixir diff --git a/config/test.exs b/config/test.exs index 07c32d77..7cc64b07 100644 --- a/config/test.exs +++ b/config/test.exs @@ -1,6 +1,6 @@ use Mix.Config -config :paper_trail, ecto_repos: [PaperTrail.Repo] +config :paper_trail, ecto_repos: [PaperTrail.Repo, PaperTrail.UUIDRepo] config :paper_trail, repo: PaperTrail.Repo, originator: [name: :user, model: User] @@ -11,3 +11,11 @@ config :paper_trail, PaperTrail.Repo, database: "paper_trail_test", hostname: "localhost", poolsize: 10 + +config :paper_trail, PaperTrail.UUIDRepo, + adapter: Ecto.Adapters.Postgres, + username: "postgres", + password: "postgres", + database: "paper_trail_uuid_test", + hostname: "localhost", + poolsize: 10 diff --git a/lib/version.ex b/lib/version.ex index 97965dca..d4472468 100644 --- a/lib/version.ex +++ b/lib/version.ex @@ -6,17 +6,20 @@ defmodule PaperTrail.Version do @setter PaperTrail.RepoClient.originator || nil + @item_type Application.get_env(:paper_trail, :item_type, :integer) + @originator_type Application.get_env(:paper_trail, :originator_type, :integer) + schema "versions" do field :event, :string field :item_type, :string - field :item_id, :integer + field :item_id, @item_type field :item_changes, :map - field :originator_id, :integer + field :originator_id, @originator_type field :origin, :string, read_after_writes: true field :meta, :map if @setter do - belongs_to @setter[:name], @setter[:model], define_field: false, foreign_key: :originator_id + belongs_to @setter[:name], @setter[:model], define_field: false, foreign_key: :originator_id, type: @originator_type end timestamps(updated_at: false) diff --git a/mix.lock b/mix.lock index f13ddb85..77394f36 100644 --- a/mix.lock +++ b/mix.lock @@ -1,9 +1,9 @@ -%{"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], []}, - "db_connection": {:hex, :db_connection, "1.1.0", "b2b88db6d7d12f99997b584d09fad98e560b817a20dab6a526830e339f54cdb3", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, optional: true]}]}, - "decimal": {:hex, :decimal, "1.3.1", "157b3cedb2bfcb5359372a7766dd7a41091ad34578296e951f58a946fcab49c6", [:mix], []}, - "earmark": {:hex, :earmark, "1.1.0", "8c2bf85d725050a92042bc1edf362621004d43ca6241c756f39612084e95487f", [:mix], []}, - "ecto": {:hex, :ecto, "2.1.3", "ffb24e150b519a4c0e4c84f9eabc8587199389bc499195d5d1a93cd3b2d9a045", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, optional: true]}]}, - "ex_doc": {:hex, :ex_doc, "0.14.5", "c0433c8117e948404d93ca69411dd575ec6be39b47802e81ca8d91017a0cf83c", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]}, - "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], []}, - "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], []}, - "postgrex": {:hex, :postgrex, "0.13.0", "e101ab47d0725955c5c8830ae8812412992e02e4bd9db09e17abb0a5d82d09c7", [:mix], [{:connection, "~> 1.0", [hex: :connection, optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, optional: false]}]}} +%{"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, + "db_connection": {:hex, :db_connection, "1.1.0", "b2b88db6d7d12f99997b584d09fad98e560b817a20dab6a526830e339f54cdb3", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, + "decimal": {:hex, :decimal, "1.3.1", "157b3cedb2bfcb5359372a7766dd7a41091ad34578296e951f58a946fcab49c6", [:mix], [], "hexpm"}, + "earmark": {:hex, :earmark, "1.1.0", "8c2bf85d725050a92042bc1edf362621004d43ca6241c756f39612084e95487f", [:mix], [], "hexpm"}, + "ecto": {:hex, :ecto, "2.1.3", "ffb24e150b519a4c0e4c84f9eabc8587199389bc499195d5d1a93cd3b2d9a045", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, + "ex_doc": {:hex, :ex_doc, "0.14.5", "c0433c8117e948404d93ca69411dd575ec6be39b47802e81ca8d91017a0cf83c", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, + "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, + "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"}, + "postgrex": {:hex, :postgrex, "0.13.0", "e101ab47d0725955c5c8830ae8812412992e02e4bd9db09e17abb0a5d82d09c7", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"}} diff --git a/priv/uuid_repo/migrations/20170525133833_create_uuid_products.exs b/priv/uuid_repo/migrations/20170525133833_create_uuid_products.exs new file mode 100644 index 00000000..f14a5820 --- /dev/null +++ b/priv/uuid_repo/migrations/20170525133833_create_uuid_products.exs @@ -0,0 +1,12 @@ +defmodule PaperTrail.UUIDRepo.Migrations.CreateUuidProducts do + use Ecto.Migration + + def change do + create table(:products, primary_key: false) do + add :id, :binary_id, primary_key: true + add :name, :string, null: false + + timestamps() + end + end +end diff --git a/priv/uuid_repo/migrations/20170525142546_create_admins.exs b/priv/uuid_repo/migrations/20170525142546_create_admins.exs new file mode 100644 index 00000000..e06e2168 --- /dev/null +++ b/priv/uuid_repo/migrations/20170525142546_create_admins.exs @@ -0,0 +1,12 @@ +defmodule PaperTrail.UUIDRepo.Migrations.CreateAdmins do + use Ecto.Migration + + def change do + create table(:admins, primary_key: false) do + add :id, :binary_id, primary_key: true + add :email, :string, null: false + + timestamps() + end + end +end diff --git a/priv/uuid_repo/migrations/20170525142612_create_versions.exs b/priv/uuid_repo/migrations/20170525142612_create_versions.exs new file mode 100644 index 00000000..bffbf7bf --- /dev/null +++ b/priv/uuid_repo/migrations/20170525142612_create_versions.exs @@ -0,0 +1,22 @@ +defmodule PaperTrail.UUIDRepo.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, :binary_id + add :item_changes, :map, null: false + add :originator_id, references(:admins, type: :binary_id) + 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/paper_trail/uuid_test.exs b/test/paper_trail/uuid_test.exs new file mode 100644 index 00000000..e3f44a91 --- /dev/null +++ b/test/paper_trail/uuid_test.exs @@ -0,0 +1,52 @@ +defmodule PaperTrailTest.UUIDTest do + use ExUnit.Case + import PaperTrail.RepoClient, only: [repo: 0] + alias PaperTrail.Version + import Ecto.Query + + + setup do + Application.put_env(:paper_trail, :repo, PaperTrail.UUIDRepo) + Application.put_env(:paper_trail, :originator, [name: :admin, model: Admin]) + Application.put_env(:paper_trail, :originator_type, Ecto.UUID) + Application.put_env(:paper_trail, :item_type, Ecto.UUID) + Code.eval_file("lib/paper_trail.ex") + Code.eval_file("lib/version.ex") + repo().delete_all(Version) + repo().delete_all(Admin) + repo().delete_all(Product) + :ok + end + + test "creates versions with models that have a UUID primary key" do + product = + %Product{} + |> Product.changeset(%{name: "Hair Cream"}) + |> PaperTrail.insert! + + version = Version |> last |> repo.one + + assert version.item_id == product.id + assert version.item_type == "Product" + end + + test "handles originators with a UUID primary key" do + admin = + %Admin{} + |> Admin.changeset(%{email: "admin@example.com"}) + |> repo.insert! + + product = + %Product{} + |> Product.changeset(%{name: "Hair Cream"}) + |> PaperTrail.insert!(originator: admin) + + version = + Version + |> last + |> repo.one + |> repo.preload(:admin) + + assert version.admin == admin + end +end diff --git a/test/support/repo.ex b/test/support/repos.ex similarity index 83% rename from test/support/repo.ex rename to test/support/repos.ex index ca0ded51..af70cb12 100644 --- a/test/support/repo.ex +++ b/test/support/repos.ex @@ -2,6 +2,10 @@ defmodule PaperTrail.Repo do use Ecto.Repo, otp_app: :paper_trail end +defmodule PaperTrail.UUIDRepo do + use Ecto.Repo, otp_app: :paper_trail +end + defmodule User do use Ecto.Schema diff --git a/test/support/uuid_models.exs b/test/support/uuid_models.exs new file mode 100644 index 00000000..e58ac793 --- /dev/null +++ b/test/support/uuid_models.exs @@ -0,0 +1,36 @@ +defmodule Product do + use Ecto.Schema + + import Ecto.Changeset + + @primary_key {:id, :binary_id, autogenerate: true} + schema "products" do + field :name, :string + + timestamps() + end + + def changeset(model, params \\ %{}) do + model + |> cast(params, [:name]) + |> validate_required([:name]) + end +end + +defmodule Admin do + use Ecto.Schema + import Ecto.Changeset + + @primary_key {:id, :binary_id, autogenerate: true} + schema "admins" 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 3972ef9f..2eff7b9f 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,10 +1,12 @@ -Mix.Task.run "ecto.create", ~w(-r PaperTrail.Repo) -Mix.Task.run "ecto.migrate", ~w(-r PaperTrail.Repo) +Mix.Task.run "ecto.create" +Mix.Task.run "ecto.migrate" PaperTrail.Repo.start_link +PaperTrail.UUIDRepo.start_link Code.require_file("test/support/simple_models.exs") Code.require_file("test/support/strict_models.exs") +Code.require_file("test/support/uuid_models.exs") ExUnit.configure seed: 0