Custom Ecto.Type for :id is not "loaded"

I have a custom ecto type defined like below.

defmodule MyApp.Account do
  use Ecto.Schema
  @primary_key {:id, MyApp.AccountID, autogenerate: true}
  schema "accounts" do
    field :name, :string
    # ... more fields here
  end
end

defmodule MyApp.AccountID do
  @behaviour Ecto.Type
  @hashids Hashids.new(min_len: 8, salt: "**salt**")
  @moduledoc """
  Converts integers to Hashids.

  See http://hashids.org/ for more information.
  """
  
  def type(), do: :id

  def cast(term) when is_integer(term), do: {:ok, Hashids.encode(@hashids, term)}
  def cast(term) when is_binary(term),  do: dump(term)
  def cast(_), do: :error

  def dump(term) when is_binary(term) do
    Hashids.decode!(@hashids, term)
    |> case do
      [int] -> {:ok, int}
      _ -> :error
    end
  end
  def dump(term) when is_integer(term), do: {:ok, term}
  def dump(_), do: :error

  def load(term) when is_integer(term), do: cast(term)
  def load(_), do: :error
end

When inserting into a Repo I get integer (:id) keys back from Ecto.

%MyApp.Account{name: "hello"}
|> MyApp.Repo.insert

This returns {:ok, %MyApp.Account{id: 323, name: "hello"} but I’m expecting that the id field should be passed through the load function on my custom type on it’s way back from the database.

Is this a bug in Ecto or am I doing something wrong?

I’m using the name :id because I want my database to generate the primary key for me, I have not figured out another way to “trick” the database (postgres) do it for me. :confused:

Your code seems to work for me

iex(2)> Repo.get(User, 1)

16:32:45.641 [debug] QUERY OK source="users" db=2.5ms decode=4.2ms
SELECT u0."id", u0."name" FROM "users" AS u0 WHERE (u0."id" = $1) [1]
%User{
  __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
  id: "ro5OZvxp",
  name: "asdf"
}

It returns user id as integer from insert since it returns what it sends, and it sends an integer.

Yep, it works fine when using Repo.get/2. But see what Repo.insert(%Users{name: "hello"}) returns :slight_smile:

See Response of insert doesn't contain default values

Yes, but in this case I’m actually getting the auto incremented integer from the database…

You are right, I’m not thinking straight …

Hmm, if I swap autogenerate: true for read_after_writes: true on the field options it seems to work as expected :slight_smile:

Can someone verify if this is the correct thing to do?

defmodule MyApp.Account do
  use Ecto.Schema
  @primary_key {:id, MyApp.AccountID, read_after_writes: true}
  schema "accounts" do
    field :name, :string
    # ... more fields here
  end
end

This might be a bug in ecto that we don’t correctly load ids. Could you open an issue, ideally with a simple way to reproduce the issue?

I’ll build you something to play with tomorrow :slight_smile:

I can’t find any issue on github. Can you post here the issue number ?
thanks