alexdesousa / ayesql

Library for using raw SQL in Elixir
MIT License
135 stars 13 forks source link

How to deal with UUIDs ? #34

Closed gevera closed 9 months ago

gevera commented 10 months ago

Hey Alexander!

I have a question regarding UUIDs. More specifically, how to deal with them in more elegant way. As of right now, if I generate a migration with the a binary_id like this one:

defmodule MyApp.Repo.Migrations.CreateCustomersTable do
  use Ecto.Migration

  def change do
    create table(:customers, primary_key: false) do
      add :id, :uuid, primary_key: true
      add :name, :string
      add :age, :integer
      add :telephone, :string

      timestamps()
    end
  end
end

And having a sql query like so:

-- name: create_a_customer
-- docs: Creates a new customer
INSERT INTO customers(id, name, age, telephone, inserted_at, updated_at)
    VALUES (:id, :name, :age, :telephone, now(), now());

There is a need to generate an UUID and cast it with something like this:

  def generate_binary_id do
    {:ok, binary_id} = Ecto.UUID.generate() |> Ecto.UUID.dump()
    binary_id
  end

And ultimately, for creating a customer the code will look like so:

MyApp.Customers.Repo.create_a_customer([id: MyApp.Utils.Helpers.generate_binary_id, name: "test customer", age: 20, telephone: "123454546"])

So the issue here, is when I retrieve the results with AyeSQL , I will need to convert the binary_id back and forth

As an example, if I run the following command

MyApp.Customers.Repo.get_customers([])

Ill get a response that looks like so:

{:ok,
 [
   %{
     id: <<50, 38, 12, 143, 112, 182, 77, 25, 142, 24, 241, 225, 72, 225, 47,
       241>>,
     name: "Kalu Rinpoche",
     age: 101,
     telephone: "983475398453",
     inserted_at: ~N[2023-12-05 07:08:20.000000],
     updated_at: ~N[2023-12-05 07:08:20.000000],
     manager_of_business_id: nil
   },
   %{
     id: <<170, 66, 177, 254, 155, 15, 67, 18, 143, 111, 186, 106, 192, 199,
       126, 33>>,
     name: "test customer",
     age: 20,
     telephone: "123454546",
     inserted_at: ~N[2023-12-05 07:43:54.000000],
     updated_at: ~N[2023-12-05 07:43:54.000000],
     manager_of_business_id: nil
   }
 ]}

That's understandable, since Postgres keeps UUID as 16 bit binaries. Now for me in order to display IDs as strings, again, we will need a helper

def decode_binary_id(binary_id) do
    {:ok, uuid} = Ecto.UUID.cast(binary_id)
    uuid
 end

And then use it in the Customers service

defmodule MyApp.Customers.Service do
  alias MyApp.Utils.Helpers

  def gat_all_customers do
    case MyApp.Customers.Repo.get_customers([]) do
      {:ok, customers} ->
        Enum.map(customers, fn c -> %{c | id: Helpers.decode_binary_id(c.id)} end)
      {:error, _} -> []
    end
  end
end

I hope it makes sense. So my question will be, if there is a more streamlined way of handling the UUIDs ?


Btw, wonderful library. I feel more comfortable using it then Ecto queries. Maybe I am just old school ?

alexdesousa commented 9 months ago

@gevera Thank you for using my library. I'm always happy to see people finding it useful :smile:.

Now, regarding the UUIDs, you can always do the casting in the database itself e.g. for the statement you've posted, I would just write the following:

-- name: create_a_customer
-- docs: Creates a new customer
INSERT INTO customers(id, name, age, telephone, inserted_at, updated_at)
    VALUES (:id::TEXT::UUID, :name, :age, :telephone, now(), now());

When reading values from the database, you can do the same trick, but backwards e.g:

SELECT id::UUID::TEXT FROM customers;

I hope this helps :smile:

gevera commented 9 months ago

Worked like a charm. Thanks