How to structure validation pipes in a model?

Here’s a part of my web/models/user.ex

@doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:name_user, :first_name, :name_last, :email, :password_hashed])
    |> validate_required([:name_user, :first_name, :name_last, :email, :password_hashed])
    |> validate_format([:name_user, ~w/], [:first_name, ~w/], [:name_last, ~w/], [:email, ~r/@/], [:password_hashed])
    |> validate_length([:name_user, min: 2, max: 254], [:first_name, min: 2, max: 127], [:name_last, min: 2, max: 127], [:email, max: 254], [:password_hashed, min: 8])
    |> unique_constraint([:name_user, :email])
  end
end

The error I’m getting after doing mix ecto.migrate is

== Compilation error on file web/models/user.ex ==
** (SyntaxError) web/models/user.ex:22: syntax error before: '@'
    (elixir) lib/kernel/parallel_compiler.ex:117: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/1

but I’m guessing this isn’t because I’ve not escaped that regex (as I found it on several tutorials where it’s supposedly working) - but rather that I’m writing out the multiple fields for validate_format wrongly.

At first, I wondered if I should use tuples instead. I can’t find examples of how to validate multiple fields.

Do you do this or put each validate_format, or example, on a new line/pipe per field needing validation?

It turns out that the error is from the regex.

Just to be sure I removed it after separatng out each field in format_length into its own pipe and then mix ecto.migrate worked.

Below web/models/user.ex includes one format_length pipe to show the error below it.

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

  schema "users" do
    field :name_user, :string
    field :first_name, :string
    field :name_last, :string
    field :email, :string
    field :password_hashed, :string
    field :password, :string, virtual: true

    timestamps()
  end

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:name_user, :first_name, :name_last, :email, :password_hashed])
    |> validate_required([:name_user, :first_name, :name_last, :email, :password_hashed])
    |> validate_format(:name_user, ~w/)
    |> validate_length(:name_user, min: 2, max: 254)
    |> validate_length(:first_name, min: 2, max: 127)
    |> validate_length(:name_last, min: 2, max: 127)
    |> validate_length(:email, max: 254)
    |> validate_length(:password_hashed, min: 8)
    |> unique_constraint([:name_user, :email])
  end
end

I’m copying the regex from github user.ex files and tut’s but not sure why I’m getting the ‘missing terminator’ error for regex eg

    == Compilation error on file web/models/user.ex ==
    ** (TokenMissingError) web/models/user.ex:31: missing terminator: / (for sigil ~w/ starting at line 22)
        (elixir) lib/kernel/parallel_compiler.ex:117: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/1

I’ve tried escaping the regex but still get a similar error.

What am I doing wrong?

I’m also still curious if it’s better to put all fields being validated the same way eg validate_format in the same pipe eg

|> validate_length([:name_user, min: 2, max: 254], [:first_name, min: 2, max: 127])

Which would be fastest?

I’m not deep into Ecto yet, but based on the error message here

== Compilation error on file web/models/user.ex ==
** (TokenMissingError) web/models/user.ex:31: missing terminator: / (for sigil ~w/ starting at line 22)
    (elixir) lib/kernel/parallel_compiler.ex:117: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/1

It seems that you forgot to terminate the sigil which is /. I believe the proper sigil will be something like this ~w//. Also, I just read the documentation of validate_format/4

validate_format(changeset, field, format, opts \\ [])
validate_format(t, atom, Regex.t, Keyword.t) :: t

it state that it accept regex for the third arity, which I believe is not valid with ~w//. You might want to use ~r// instead.

1 Like

Thanks!

I have mix ecto.migrate working now.

Anytime

1 Like