Farshid Ashouri
Farshid Ashouri

Reputation: 17701

Why functions in default values in Ecto models schema evaluate only once and how to fix?

I have a schema like this:

defmodule Ticketing.User do
  use Ticketing.Web, :model

  schema "users" do
    field :first_name, :string
    field :last_name, :string
    field :email, :string
    field :hashed_password, :string
    field :password, :string, virtual: true
    field :active, :boolean, default: false
    field :activation_key, :string, default: Ecto.UUID.generate
    field :description, :string

    timestamps()
  end

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:first_name, :last_name, :email, :hashed_password, :password,
        :active])
    |> validate_required([:first_name, :last_name, :email, :password])
  end
end

All new users have same UUID. does default value in schema evaluate only once? Should I implement the default value in changeset instead of schema?

Upvotes: 1

Views: 1588

Answers (2)

Dreetje
Dreetje

Reputation: 19

You could change the type to Ecto.UUID, and set the :autogenerate option to true. Not 100% sure if this will work, but worth a shot :)

Upvotes: -1

Dogbert
Dogbert

Reputation: 222198

Like @Mick MacCallum said, :default is evaluated at compile time in your code. If you're using PostgreSQL, you can fix this by changing the column's default value in the database to be gen_random_uuid(). This will make PostgreSQL itself generate a random UUID value for each record. gen_random_uuid() requires the pgcrypto PostgreSQL extension.

Generate a new migration:

$ mix ecto.gen.migration add_default_to_users_activation_key

and replace the newly created file's contents with:

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

  def up do
    execute "CREATE EXTENSION IF NOT EXISTS \"pgcrypto\";"
    alter table(:users) do
      modify :activation_key, :uuid, default: fragment("gen_random_uuid()")
    end
  end

  def down do
    alter table(:users) do
      modify :activation_key, :uuid
    end
  end
end

You should also add read_after_writes: true to the field declaration of activation_key in web/models/user.ex so that activation_key is read back from the database after inserts:

schema "users" do
  field :activation_key, Ecto.UUID, read_after_writes: true
end

Demo:

iex(1)> %MyApp.User{name: "John Doe"} |> MyApp.Repo.insert!
[debug] QUERY OK db=9.6ms
INSERT INTO "users" ("name","inserted_at","updated_at") VALUES ($1,$2,$3) RETURNING "id", "activation_key" ["John Doe", {{2016, 8, 4}, {7, 38, 14, 0}}, {{2016, 8, 4}, {7, 38, 14, 0}}]
%MyApp.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
 activation_key: "921b1599-fc74-48d7-b74a-b4ce4d8b1333", id: 1,
 inserted_at: #Ecto.DateTime<2016-08-04 07:38:14>, name: "John Doe",
 updated_at: #Ecto.DateTime<2016-08-04 07:38:14>}

Upvotes: 3

Related Questions