Skip to content

Commit 577b7f4

Browse files
dev-guydev-guy
authored andcommitted
Unique slugs. Revert standard attribute definition to macro
1 parent 0fa5dff commit 577b7f4

File tree

3 files changed

+67
-30
lines changed

3 files changed

+67
-30
lines changed

lib/geo/geography/country.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ defmodule Geo.Geography.Country do
22
use Ash.Resource,
33
otp_app: :geo,
44
domain: Geo.Geography,
5-
data_layer: AshPostgres.DataLayer,
6-
extensions: [Geo.Resources.Attributes.Id]
5+
data_layer: AshPostgres.DataLayer
76

87
# === Attributes ===
8+
use Geo.Resources.Attributes.Id
9+
use Geo.Resources.Attributes.Timestamps
910
use Geo.Resources.Attributes.Name, allow_nil?: false, unique?: true
1011
use Geo.Resources.Attributes.Slug, allow_nil?: false, unique?: true
11-
use Geo.Resources.Attributes.Timestamps
1212

1313
attributes do
1414
attribute :iso_code, :ci_string do

lib/geo/resources/attributes/id.ex

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,12 @@
11
defmodule Geo.Resources.Attributes.Id do
22
@moduledoc """
3-
An Ash extension that adds a UUID v7 primary key attribute to a resource.
43
"""
54

6-
use Spark.Dsl.Extension,
7-
transformers: [
8-
__MODULE__.Transformer
9-
]
10-
11-
defmodule Transformer do
12-
@moduledoc false
13-
use Spark.Dsl.Transformer
14-
15-
def before?(Ash.Resource.Transformers.BelongsToAttribute), do: true
16-
def before?(_), do: false
17-
18-
def transform(dsl_state) do
19-
attribute = %Ash.Resource.Attribute{
20-
name: :id,
21-
type: :uuid_v7,
22-
allow_nil?: false,
23-
writable?: false,
24-
public?: true,
25-
primary_key?: true,
26-
default: &Ash.UUIDv7.generate/0
27-
}
28-
29-
{:ok, Spark.Dsl.Transformer.add_entity(dsl_state, [:attributes], attribute)}
5+
defmacro __using__(_opts) do
6+
quote do
7+
attributes do
8+
uuid_v7_primary_key :id
9+
end
3010
end
3111
end
3212
end

lib/geo/resources/changes/slugify_name.ex

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,72 @@ defmodule Geo.Resources.Changes.SlugifyName do
2828

2929
# If we have a name but no slug, generate one
3030
not is_nil(name) ->
31-
generated_slug = slugify(name)
32-
Ash.Changeset.force_change_attribute(changeset, :slug, generated_slug)
31+
base_slug = slugify(name)
32+
unique_slug = ensure_unique_slug(changeset, base_slug)
33+
Ash.Changeset.force_change_attribute(changeset, :slug, unique_slug)
3334

3435
# Otherwise, leave as is
3536
true ->
3637
changeset
3738
end
3839
end
3940

41+
defp ensure_unique_slug(changeset, base_slug) do
42+
# Try base slug first
43+
if !slug_exists?(changeset, base_slug) do
44+
base_slug
45+
else
46+
find_unique_slug_with_number(changeset, base_slug)
47+
end
48+
end
49+
50+
defp find_unique_slug_with_number(changeset, base_slug) do
51+
# Range 1: 1-9
52+
case find_in_range(changeset, base_slug, 1..9) do
53+
{:found, number} -> "#{base_slug}-#{number}"
54+
:not_found ->
55+
# Range 2: 1-99
56+
case find_in_range(changeset, base_slug, 1..99) do
57+
{:found, number} -> "#{base_slug}-#{number}"
58+
:not_found ->
59+
# Range 3: 1-9999
60+
case find_in_range(changeset, base_slug, 1..9999) do
61+
{:found, number} -> "#{base_slug}-#{number}"
62+
:not_found -> raise "Could not find unique slug after trying up to 9999"
63+
end
64+
end
65+
end
66+
end
67+
68+
defp find_in_range(changeset, base_slug, range) do
69+
Enum.find_value(range, :not_found, fn number ->
70+
slug_candidate = "#{base_slug}-#{number}"
71+
if !slug_exists?(changeset, slug_candidate) do
72+
{:found, number}
73+
end
74+
end)
75+
end
76+
77+
defp slug_exists?(changeset, slug) do
78+
resource = changeset.resource
79+
query = Ash.Query.filter(resource, slug: slug)
80+
81+
# Exclude the current record if it's an update
82+
query =
83+
case changeset.data do
84+
%{id: id} when not is_nil(id) ->
85+
Ash.Query.filter(query, id != ^id)
86+
_ ->
87+
query
88+
end
89+
90+
case Ash.read(query) do
91+
{:ok, []} -> false
92+
{:ok, _} -> true
93+
{:error, _} -> false
94+
end
95+
end
96+
4097
defp slugify(text) when is_binary(text) do
4198
text
4299
|> String.downcase()

0 commit comments

Comments
 (0)