Validating a schema field to accept only certain content

Hi, I’m struggling to find a way to have my schema field only accept certain values. As an example, I have a field :flavor, :string and I would like to limit the possible values to be either “Donkey” or “Platypus”. What I’ve tried so far is using changesets and to do:
|> Ecto.Changeset.validate_inclusion(:flavour, ["Donkey", "Platypus"])
which doesn’t seem to do anything at all, as I’m still able insert any given value and yet get a valid?: true from changeset.

I don’t even know if changeset is the way to go, only that I did find this validate_inclusion(changeset, :gender, ["man", "woman", "other", "prefer not to say"]) from the ecto docs and figured out from the context it should probably be something I’m looking for.

1 Like

Hi @Gulliver,
It looks like you have a typo field :flavor but validate_inclusion(:flavour) (flavor vs flavour). If it’s not it, please share some more code, e.g. the definition of the function that returns the changeset and how you’re invoking it.

Flavor/Flavour, same flavour if you ask me :). Anyways, name was just an example hence I doubt it’s a typo issue.
Here’s some more code:

defmodule Karims.Kebab do
  use Ecto.Schema

  schema "kebabs" do
    field :flavour, :string
    timestamps
  end

  def changeset(kebab, params \\ %{}) do
    kebab
    |> Ecto.Changeset.cast(params, [:flavour])
    |> Ecto.Changeset.validate_inclusion(:flavour, ["Donkey", "Platypus"])
  end
end

Then in iex I do:
> kebab = %Karims.Kebab{flavour: “Chicken”}
> changeset = Karims.Kebab.changeset(kebab, %{})

which gives me:
> changeset.valid?
true

…which should be false in my eyes.
So what gives, I wonder?

Adding call to ‘Changeset.validate_required’ should do it; I think most validations aren’t run if the field isn’t present in the changeset

I tried with validate_required as well:

def changeset(kebab, params \\ %{}) do
  kebab
  |> Ecto.Changeset.cast(params, [:flavour])
  |> Ecto.Changeset.validate_required([:flavour])
  |> Ecto.Changeset.validate_inclusion(:flavour, ["Donkey", "Platypus"])
end

but still wouldn’t change anything. I wonder what this validate_inclusion actually does?

OK, I think I got it. When you do:

Karims.Kebab.changeset(%Karims.Kebab{flavour: "Chicken"}, %{})

your changeset.changes are empty and so there’s nothing to validate.

Try this instead:

Karims.Kebab.changeset(%Karims.Kebab{}, %{flavour: "Chicken"})
1 Like

Ok, I got it sort of working now, thanks!

Basically I’m doing now: Karims.Kebab.changeset(%Karims.Kebab{}, %{flavour: "Chicken"}) and my model looks like this:

defmodule Karims.Kebab do
  use Ecto.Schema
  import Ecto.Changeset
  alias Karims.Repo
  alias Karims.Kebab

  schema "kebabs" do
    belongs_to :food_type, Karims.Food_Type
    field :flavour, :string
    timestamps
  end

  def changeset(kebab, params \\ %{}) do
    kebab
    |> cast(params, [:flavour])
    |> cast_assoc(:food_type, required: true)
    |> validate_inclusion(:flavour, ["Donkey", "Platypus"])
  end

Which works as expected, “chicken” fails the validation while “Donkey” passes.

But now I have another problem: My cast_assoc(:food_type, required: true) wouldn’t work anymore.

After I do:
iex> food_type = Karims.Food_Type |> Karims.Repo.get_by(food: "Special Kebab")
iex> changeset = Karims.Kebab.changeset(%Karims.Kebab{}, %{flavour: "Donkey", food_type: food_type})
I get told:
** (Ecto.CastError) expected params to be a map, got: %Karims.food_type{__meta__: #Ecto.Schema.Metadata<:loaded, "food_types">, inserted_at: #Ecto.DateTime<2016-10-04 04:50:35>, food: "Special kebab", updated_at: #Ecto.DateTime<2016-10-04 09:50:35>}
in so many words and then some.

Here’s how Karims.Food_Type looks like:

defmodule Karims.Food_Type do
  use Ecto.Schema
  import Ecto.Changeset
  alias Karims.Repo
  alias Karims.Food_Type

  schema "food_types" do
    has_many :kebabs, Karims.Kebab
    field :food, :string
    timestamps
  end

  def changeset(food_type, params \\ %{}) do
    food_type
    |> cast(params, [:food])
    |> validate_inclusion(:food, ["Tasty kebab", "Special Kebab"])
  end

Before, with Karims.Kebab.changeset(%Karims.Kebab{flavour: "Chicken"}, %{}) cast_assoc still worked but then I couldn’t validate anything. Any ideas how this could be solved?