Prefixed base62 UUIDv7 primary key implementation with Ash

moissela
2023-07-28

moissela:

Hi all, I’m trying to implement a UUIDv7 based primary key for all our Ash resources (and relationships), storing that as UUID type column on PostgreSQL and presenting that outside as a prefixed base62 string ( 0188aadc-f449-7818-8862-5eff12733f64 will be shown as acct_02tRrww6GFm4urcMhyQpAS ), like what is described here for Ecto ( https://danschultzer.com/posts/prefixed-base62-uuidv7-object-ids-with-ecto ).

I’ll be happy to contribute with the result of this as a code sample or better as an elixir package as soon as I get it all working properly.

What I’d like to know, maybe from <@197905764424089601>, is what is the better way with Ash to add a Resource’s DSL helper like uuid_primary_key ?

With older version of Ash and Spark I’ve successfully patched the Resource’s DSL, but with newer version it seems that here https://github.com/ash-project/ash/blob/main/lib/ash/resource/dsl.ex#L127 on Ash side is missing the patchable?: true option that’s is defined here https://github.com/ash-project/spark/blob/f997db91f74e772cd19656148092f439743f9cd8/lib/spark/dsl/section.ex#L42 on Spark side. It seems a philosophical decision on the framework side to explicitly disallow DSL patching, so I’m wondering what is the correct way to do it.

It would be great to be able to contribute to ash via an external package that will add to the Resource’s DSL a simple helper like prefix_uuidv7_primary_key .

Another question is how to know in the Ash type module definition the Ash Resource in which it is included as an attribute, in order to generate or obtain the right prefix (maybe with Resource metadata or through a new DSL specific section).

Unfortunately I think this is not possible right now so I’m trying to get around the problem by using the constraints like constraints: [prefix: "acct"] because that it’s the only flexible parameter I found on the Ash types.

And of course any advice or tip is welcome 😅

.terris:

I don’t know how to extend Ash, so I’m useless. However, I want this solution, so I cheerlead thou. I built something that uses calculations but just as a spike. I know that’s not what I or anyone else wants - for one, it doesn’t work with Ash JSONApi or Ash GraphQL. https://gist.github.com/dev-guy/51a4c12983424d3480cd1201263b50c9

zachdaniel:

I think you could do this without an extension actually.

defmodule prefix_uuidv7_primary_key(name, opts) do
  quote do
    opts = 
      unquote(opts) 
      |> Keyword.merge(type: YourType, more: :options, ...)
      |> Keyword.update(:constraints, [resource: __MODULE__], &Keyword.put(&1, :resource, __MODULE__))

    attribute unquote(name), opts
  end
end

zachdaniel:

That is one potentially simple way of doing it, by just providing a macro that does that work

zachdaniel:

what you can also do is use transformers with your extension to alter the attribute and add that constraint. It’s a bit more complex, but transformers can pretty much do anything they want to a resource.

moissela:

Thanks Zach for the tip, I’ll try both!

moissela:

<@197905764424089601> , do You mean through this https://ash-hq.org/docs/guides/ash/latest/tutorials/extending-resources#make-the-extension-configurable right? Can I also auto add the postgres migration_defaults DSL part with the configurable extension?

zachdaniel:

Yes 🙂

zachdaniel:

You’d need to use Transformer.set_opt to set an updated value.

moissela:

Hi <@197905764424089601>, another question for you 😅

I’m still working on this post’s subject feature that I’m packaging as a hex module named Ash UUIDv7. As promised I’ll opensourcing that as soon as ready.

For completeness I would add this gist’s psql function https://gist.github.com/kjmph/5bd772b2c2df145aa645b837da7eca74 as a migration so that there’s also a postgres-side defined default for fields using the AshUUIDv7 type, of course I’ve implemented the Ash-side autogenerator yet.

I’ve seen how you’ve implemented a similar requirement for the ash-functions extension here https://github.com/ash-project/ash_postgres/blob/3d9a0cfb4f6748174bfa7396d4677b71f2556610/lib/migration_generator/migration_generator.ex#L276 .

So my question: can I define a function in the project Repo, that people will inject using my module, so that for having that psql migration auto-generated the only requirements will be add use AshUUIDv7.Postgres and add something like ashuuidv7-functions to the installed_extensions/0 list?

I think that if we permit having a mix of string and function captures in the installed_extensions/0 list, it will be simple to modify how this is working right now in a retro-compatible way and easily add the feature that module like mine needed.

Of course I’m happy to try to contribute myself to the AshPostgres repo if you think that is a good idea 🙂

zachdaniel:

There is not a way to have your own thing like the ash-functions extension currently

zachdaniel:

That would need to be added, happy for PRs in that directiona

korbin.mcgee:

<@477481255097597972> I too was trying to figure out how to use UUIDv7. Made a support post about it as well. I was thinking yesterday that maybe it could be added to something like the uniq package. They already define extentions for Ecto here: https://github.com/bitwalker/uniq/blob/f229d462c939ec655dd8ac8abbfe7325f2e83e6e/lib/uuid.ex#L951 . Someone could define extensions for Ash similarly.

moissela:

Hi <@197905764424089601>, PR ready here https://github.com/ash-project/ash_postgres/pull/162

moissela:

Hi <@275030586547109888> , the ash extension is almost ready. It uses Uniq for uuid generation with support for version 4 and 7, with outside prefixed and encoded strings, or only encoded, or straight raw uuid. I’ll post here as soon as releasing 😉

korbin.mcgee:

Are you still intending to release a completely new package or are you contributing that extension to Uniq?

moissela:

actually a completely new package with Uniq as a dependency

korbin.mcgee:

Idk if Uniq’s maintainers would except an extension contribution in the first place, but why a new package vs contributing to existing package? I’m asking out curiosity as I don’t really know the advantages/disadvantages of either approach.

moissela:

Simply because we’ve start working on the extension based on our company’s projects needs and at that time we not used Uniq for uuids generation. Right now we’ve switched to Uniq for releasing the extension as opensource but Uniq represents only a dependency for us and not all the extension. In the future we could switch to a faster or better Uniq alternative, who knows

moissela:

Hi <@197905764424089601>, the AshUUID extension is ready here https://github.com/zoonect-oss/ash_uuid .

Unfortunately we can’t release it on hex until this https://github.com/ash-project/ash_postgres/pull/162 will be merged: as you can see here https://github.com/zoonect-oss/ash_uuid/actions/runs/5796129613/job/15709244164#step:3:309 :

Dependencies excluded from the package (only Hex packages can be dependencies): ash_postgres
Error: Process completed with exit code 1.

mavu.io:

how lucky: I needed this for a new project today, and it got published 4 hrs ago ! thanks <@477481255097597972>!

zachdaniel:

<@477481255097597972> I’ve merged your PR, but will not be at a computer until tonight to publish a new version of the package

zachdaniel:

Just released a new version of ash_postgres

moissela:

Thank you <@197905764424089601>: I’m sorry to have disturbed you on vacation 🙈 I’ll post AshUUID to hex tomorrow 👍🏻