Changeset is not validating data

Background

I am trying to create a schema with some basic validations. However, when I spin up my app, it looks like the validations are not running.

Code

This is the schema for a reservation.

defmodule MyApp.Reserve do
  @moduledoc """
  Represents a reservation for a guest.
  """

  use Ecto.Schema

  import Ecto.Query

  alias Ecto.Changeset

  schema "reserves" do
    field :guest_id, Ecto.UUID
    field :reserved, :decimal
    field :paid, :decimal
    field :category, Ecto.Enum, values: [:new, :returning]
  end

  def changeset(reserve, params \\ %{}) do
    reserve
    |> Changeset.cast(params, [:guest_id, :reserved, :paid, :category])
    |> Changeset.validate_required([:guest_id, :reserved, :paid, :category])
    |> Changeset.validate_number(:reserved, greater_than_or_equal_to: 0)
    |> Changeset.validate_number(:paid, greater_than_or_equal_to: 10)
    |> Changeset.validate_inclusion(:category, ["new", "returning"])
  end

end

Iex

alias MyApp.Repo
alias MyApp.Reserve

reserve = %Reserve{guest_id: "bcf6dba1-8542-462e-ac16-6cfbe7be4cf6", reserved: 0, paid: -10}

Reserve.changeset(reserve)
#Ecto.Changeset<action: nil, changes: %{}, errors: [], data: #MyApp.Reserve<>, valid?: true, ...>

Question

This should be valid?: false since paid is a negative number. What am I missing?

The changes will be validated and you may see errors after “applying an action” - it can be e.g. when you call Repo to insert or update, or if you need to you can call apply_action/2 yourself

edit: also, if you’re making changes to a struct you would pass the changes in the params map - and those would then get casted and get validated

That’s not the issue. Validations run at the time the validation function is called.

Validations generally only validate changes, not the existing data on the reserve struct – they’re expected to be valid as supplied. You’re calling the changeset/2 function with no changes. validate_required is the only exception to that for Ecto.Changeset supplied validations.

3 Likes

To have Ecto.Changeset work as designed you should give it an empty %Reserve{} and then pass parameters in the form of a regular map that contain all the pieces of data you want validated. Or a mix.

4 Likes

oh, right, I got confused because Phoenix is not showing errors when the action is not set

1 Like

Note there are times where it is okay to pass in a struct with fields set, namely when you explicitly don’t want to validate them because they come from your own code. Sometimes I’ll throw in something which is set statically (e.g. Post{type: :something}) or a foreign key that way.

Yeah. First parameter to cast is data you already trust.

1 Like