Skip to content

Commit

Permalink
Merge pull request #18 from nash-io/rafael/fix-item-id-for-composite-…
Browse files Browse the repository at this point in the history
…primary-keys

Fix item_id for composite primary keys
  • Loading branch information
rschef authored Jun 14, 2024
2 parents 1df2a1b + 633353b commit 7d8d8fb
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 11 deletions.
29 changes: 25 additions & 4 deletions lib/paper_trail/serializer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ defmodule PaperTrail.Serializer do
@moduledoc false
import Ecto.Query

alias Ecto.UUID
alias PaperTrail.RepoClient
alias PaperTrail.Version

@type options :: PaperTrail.options()

@default_ignored_ecto_types [Ecto.UUID, :binary_id, :binary]
@default_ignored_ecto_types [UUID, :binary_id, :binary]

def make_version_struct(%{event: "insert"}, model, options) do
originator = RepoClient.originator()
Expand Down Expand Up @@ -221,17 +222,37 @@ defmodule PaperTrail.Serializer do
def get_item_type(%Ecto.Changeset{data: data}), do: get_item_type(data)
def get_item_type(%schema{}), do: schema |> Module.split() |> List.last()

@spec get_model_id(Ecto.Changeset.t() | struct()) :: String.t() | integer()
def get_model_id(%Ecto.Changeset{data: data}), do: get_model_id(data)

def get_model_id(model) do
{_, model_id} = List.first(Ecto.primary_key(model))
model_id =
case Ecto.primary_key(model) do
[{_, model_id}] ->
model_id

composite_primary_keys ->
composite_primary_keys
|> Enum.sort_by(fn {field, _value} -> field end, :asc)
|> Enum.map_join(":", fn {_field, value} -> to_string(value) end)
end

case PaperTrail.Version.__schema__(:type, :item_id) do
:integer ->
model_id

_ ->
"#{model_id}"
UUID ->
if UUID.cast(model_id) == :error do
model_id
|> then(&:crypto.hash(:sha256, &1))
|> :binary.part(0, 16)
|> UUID.cast!()
else
model_id
end

_else ->
to_string(model_id)
end
end

Expand Down
22 changes: 15 additions & 7 deletions priv/uuid_repo/migrations/20170525142613_create_items.exs
Original file line number Diff line number Diff line change
@@ -1,30 +1,38 @@
defmodule PaperTrail.UUIDRepo.Migrations.CreateItems do
@moduledoc false
use Ecto.Migration

def change do
create table(:items) do
add :item_id, :binary_id, null: false, primary_key: true
add :title, :string, null: false
add(:item_id, :binary_id, null: false, primary_key: true)
add(:title, :string, null: false)

timestamps()
end

create table(:foo_items) do
add :title, :string, null: false
add(:title, :string, null: false)

timestamps()
end

create table(:bar_items, primary_key: false) do
add :item_id, :string, primary_key: true
add :title, :string, null: false
add(:item_id, :string, primary_key: true)
add(:title, :string, null: false)

timestamps()
end

create table(:uuid_items) do
add :item_id, :uuid, null: false, primary_key: true
add :title, :string, null: false
add(:item_id, :uuid, null: false, primary_key: true)
add(:title, :string, null: false)

timestamps()
end

create table(:composite_primary_keys_items, primary_key: false) do
add(:item_id, :uuid, primary_key: true)
add(:bar_id, :uuid, primary_key: true)

timestamps()
end
Expand Down
32 changes: 32 additions & 0 deletions test/support/uuid_models.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
defmodule Product do
@moduledoc false
use Ecto.Schema

import Ecto.Changeset
Expand All @@ -18,7 +19,9 @@ defmodule Product do
end

defmodule Admin do
@moduledoc false
use Ecto.Schema

import Ecto.Changeset

@primary_key {:id, :binary_id, autogenerate: true}
Expand All @@ -36,7 +39,9 @@ defmodule Admin do
end

defmodule Item do
@moduledoc false
use Ecto.Schema

import Ecto.Changeset

@primary_key {:item_id, :binary_id, autogenerate: true}
Expand All @@ -54,7 +59,9 @@ defmodule Item do
end

defmodule UUIDItem do
@moduledoc false
use Ecto.Schema

import Ecto.Changeset

@primary_key {:item_id, Ecto.UUID, autogenerate: true}
Expand All @@ -72,7 +79,9 @@ defmodule UUIDItem do
end

defmodule FooItem do
@moduledoc false
use Ecto.Schema

import Ecto.Changeset

@primary_key {:id, :id, autogenerate: true}
Expand All @@ -90,7 +99,9 @@ defmodule FooItem do
end

defmodule BarItem do
@moduledoc false
use Ecto.Schema

import Ecto.Changeset

@primary_key {:item_id, :string, autogenerate: false}
Expand All @@ -106,3 +117,24 @@ defmodule BarItem do
|> validate_required([:item_id, :title])
end
end

defmodule CompositePkItem do
@moduledoc false
use Ecto.Schema

import Ecto.Changeset

@primary_key false
schema "composite_primary_keys_items" do
field(:item_id, Ecto.UUID, primary_key: true)
field(:bar_id, Ecto.UUID, primary_key: true)

timestamps()
end

def changeset(model, params \\ %{}) do
model
|> cast(params, [:item_id, :bar_id])
|> validate_required([:item_id, :bar_id])
end
end
45 changes: 45 additions & 0 deletions test/uuid/uuid_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ defmodule PaperTrailTest.UUIDTest do
import Ecto.Query
import PaperTrail.RepoClient, only: [repo: 0]

alias Ecto.UUID
alias PaperTrail.Version

defmodule CustomPaperTrail do
Expand Down Expand Up @@ -86,6 +87,50 @@ defmodule PaperTrailTest.UUIDTest do
assert version.item_id == uuid_item.item_id
end

test "versioning models with composite primary keys" do
item_id1 = UUID.generate()
bar_id1 = UUID.generate()

item1 =
%CompositePkItem{}
|> CompositePkItem.changeset(%{item_id: item_id1, bar_id: bar_id1})
|> CustomPaperTrail.insert!()

version1 = Version |> last() |> repo().one

assert version1 == PaperTrail.get_version(item1)

assert %{
event: "insert",
item_changes: %{"bar_id" => ^bar_id1, "item_id" => ^item_id1},
item_id: version_id1,
item_type: "CompositePkItem"
} = version1

assert match?({:ok, _}, UUID.cast(version_id1))

item_id2 = UUID.generate()
bar_id2 = UUID.generate()

item2 =
%CompositePkItem{}
|> CompositePkItem.changeset(%{item_id: item_id2, bar_id: bar_id2})
|> CustomPaperTrail.insert!()

version2 = PaperTrail.get_version(item2)

assert %{
event: "insert",
item_changes: %{"bar_id" => ^bar_id2, "item_id" => ^item_id2},
item_id: version_id2,
item_type: "CompositePkItem"
} = version2

assert match?({:ok, _}, UUID.cast(version_id2))

assert version_id1 != version_id2
end

test "test INTEGER primary key for item_type == :string" do
if PaperTrail.Version.__schema__(:type, :item_id) == :string do
item =
Expand Down

0 comments on commit 7d8d8fb

Please sign in to comment.