Autogenerated unique short IDs

What is the best way in Ash to generate short IDs e.g. invite codes that are unique and will keep generating till it gets a unique one? Actions?
14 Replies
ZachDaniel
ZachDaniel3y ago
Yep! You can do it in a before action hook and check until you get a unique one 🙂
harry
harryOP3y ago
Check manually or does ash have checking built in?
ZachDaniel
ZachDaniel3y ago
There is nothing that will repeatedly check and retry built in
harry
harryOP3y ago
👌🏼 So i write an action that generates then tryies to query for it, if it finds it -> generate a new one, else return
ZachDaniel
ZachDaniel3y ago
Well, you’d just include that as a change on a create action.
harry
harryOP3y ago
yeah
ZachDaniel
ZachDaniel3y ago
create :create do
# assuming short_id is allow_nil: false, you'd use this to not require it for this action
allow_nil_input [:short_id]

change fn changeset, _ ->
Ash.Changeset.before_action(changeset, fn changeset ->
Ash.Changeset.force_change_attribute(changeset, :short_id, generate_short_id(...))
end)
end
end
create :create do
# assuming short_id is allow_nil: false, you'd use this to not require it for this action
allow_nil_input [:short_id]

change fn changeset, _ ->
Ash.Changeset.before_action(changeset, fn changeset ->
Ash.Changeset.force_change_attribute(changeset, :short_id, generate_short_id(...))
end)
end
end
harry
harryOP3y ago
🔥 thanks yeah thats what I was plannin
iex(2)> Project.Resources.Invites.generate_code
[debug] QUERY OK source="invites" db=22.0ms idle=213.1ms
SELECT c0."id", c0."code", c0."sent_to", c0."max_uses", c0."created_at", c0."updated_at", c0."company_id" FROM "invites" AS c0 WHERE (c0."code"::text = $1::text) ["ExpDCJLb"]
↳ AshPostgres.DataLayer.run_query/2, at: lib/data_layer.ex:599
"ExpDCJLb"
iex(3)>
iex(2)> Project.Resources.Invites.generate_code
[debug] QUERY OK source="invites" db=22.0ms idle=213.1ms
SELECT c0."id", c0."code", c0."sent_to", c0."max_uses", c0."created_at", c0."updated_at", c0."company_id" FROM "invites" AS c0 WHERE (c0."code"::text = $1::text) ["ExpDCJLb"]
↳ AshPostgres.DataLayer.run_query/2, at: lib/data_layer.ex:599
"ExpDCJLb"
iex(3)>
@alphabet Enum.concat([?0..?9, ?A..?Z, ?a..?z])
@max_tries 15

def generate_code(attempts \\ 1, len \\ 8) do
code = for _ <- 1..len, into: "", do: <<Enum.random(@alphabet)>>

with {:ok, []} <-
Invite
|> Ash.Query.for_read(:read)
|> Ash.Query.filter(code == ^code)
|> Project.Api.read() do
code
else
_ ->
if attempts > @max_tries do
throw("failed to generate code")
else
generate_code(attempts + 1, len)
end
end
end
@alphabet Enum.concat([?0..?9, ?A..?Z, ?a..?z])
@max_tries 15

def generate_code(attempts \\ 1, len \\ 8) do
code = for _ <- 1..len, into: "", do: <<Enum.random(@alphabet)>>

with {:ok, []} <-
Invite
|> Ash.Query.for_read(:read)
|> Ash.Query.filter(code == ^code)
|> Project.Api.read() do
code
else
_ ->
if attempts > @max_tries do
throw("failed to generate code")
else
generate_code(attempts + 1, len)
end
end
end
Just putting the code here for anyone else :magic_sparkles:
ZachDaniel
ZachDaniel3y ago
🥳
harry
harryOP3y ago
An optimisation would be checking for count rather than an actual query but idk if you can do that
ZachDaniel
ZachDaniel3y ago
Not quite yet, but I'm working on Api.aggregate(query, :count, ...) to do exactly that
Terris
Terris3y ago
This is a tangent but you might find this interesting. https://morioh.com/p/52d5efc4cba3
ExULID: Unique Lexicographically Sortable Identifier (ULID) in Elixir
In this tutorial, We'll learn ExULID: Universally Unique Lexicographically Sortable Identifier (ULID) in Elixir. Implemented according to ulid/spec.
harry
harryOP3y ago
They are a bit long for what I need xD thanks for sending though
Terris
Terris3y ago
I know. The js ulid library I use lets you control the length and other aspects of the format. This elixir one is a bit deficient.

Did you find this page helpful?