Search code examples
elixirphoenix-frameworkecto

Adding a random and unique field to Ecto model


I'd like to have a unique field in an Ecto model. This field should contain a random string which I can generate easily (for example, see here). However, I would like to avoid to generate the string and check if it's already present in the database, as that would expose me to race conditions.

I'd like to have it retry insertion until a unique string is found. But how do I it? Should it be inside the changeset/2 function?

defmodule LetsPlan.Event do
  use LetsPlan.Web, :model

  schema "events" do
    field :name, :string
    field :from, Ecto.DateTime
    field :to, Ecto.DateTime
    field :slug, :string

    timestamps
  end

  @required_fields ~w(from to)
  @optional_fields ~w(slug)

  def changeset(model, params \\ :empty) do
    model
    |> cast(params, @required_fields, @optional_fields)
    |> unique_constraint(:slug)
  end
end

Solution

  • It's been 4 months, so I guess you figured it out. You should create different changeset depending the action you are doing and a base changeset for "read" purposes.

    Explicit > Implicit

    Your model could end up like that:

    defmodule App.Classified do
    
      @rules_create %{
        :required_fields => ~w(tenant_id firstname lastname email password password_confirmation phone birthday description),
        :optional_fields => ~w(),
      }
    
      @rules_update %{
        :required_fields => ~w(firstname lastname email phone birthday description),
        :optional_fields => ~w()
      }
    
      def changeset(model, params \\ :empty) do
        model
        |> cast(params, [], [])
      end
    
      @doc """
      Changeset when you create a new classified
      """
      def create_changeset(model, params \\ :empty) do
        model
        |> cast(params, @rules_create.required_fields, @rules_create.optional_fields)
        |> validate_length(:description, min: 280)
        |> validate_length(:password, min: 6)
        |> validate_confirmation(:password)
        |> unique_constraint(:email)
        |> hash_password
        |> make_token
        |> make_search
      end
    
      @doc """
      Changeset when you update an classified
      """
      def update_changeset(model, params \\ :empty) do
        model
          |> cast(params, @rules_update.required_fields, @rules_update.optional_fields)
          |> validate_length(:description, min: 280)
          |> make_search
      end
    
    end