How to have optional values for an ecto embedded schema?

Background

I am trying to create an embedded schema that needs to save information about orders.
Sometimes orders have no customer_id and that is fine (for the application).

defmodule Order do
  use Ecto.Schema

  embedded_schema do
    field(:customer_id, :integer | nil)
    field(:stage, enum: [:bought | :in_shop_cart | :canceled])
    field(:details, :string)
  end
end

Problem

The issue here is that I get a compile error for customer_id:

(CompileError) misplaced operator |/2

The | operator is typically used between brackets as the cons operator:

    [head | tail]

where head is a single element and the tail is the remaining of a list.
It is also used to update maps and structs, via the %{map | key: value} notation,
and in typespecs, such as @type and @spec, to express the union of two types

I understand the issue here is the use of :integer | nil. This is not a list, so the advice I get is not usable.

Question

How can I represent optional values in my embedded schema ? (specifically :something | nil)

field(:customer_id, :integer)

That’s it, you don’t have to use nil. If you want this field to be required by the database, you should add null: false.

All fields are optional by default, you only make them mandatory through validations in a changeset.

Remove the invalid Elixir syntax and you’re good to go.

1 Like

So for my scenario, the field :details is nullable by default, correct?

A fixed version would therefore be:

defmodule Order do
  use Ecto.Schema

  embedded_schema do
    field(:customer_id, :integer)
    field(:stage, enum: [:bought | :in_shop_cart | :canceled])
    field(:details, :string)
  end
end

And then in the database columns, set null: false. I will also need to validate this requirement in changeset validations as @dimitarvp pointed out, correct?

[quote=“Fl4m3Ph03n1x, post:4, topic:51836”]

defmodule Order do
  use Ecto.Schema

  embedded_schema do
    field(:customer_id, :integer)
    field(:stage, enum: [:bought | :in_shop_cart | :canceled])
    field(:details, :string)
  end
end

I made a mistake in my previous answer, you should add null: false to your migration, and in schema, you can just skip it.

Yes, use validate_required to validate the field is there.

In my migration?

I understand from the docs that the only thing I can do is the following:

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

  def change do
    alter table("action") do
      add(:order, :map)
    end
  end
end

From here:

https://hexdocs.pm/ecto/embedded-schemas.html#migrations

I have a table called “action” and I am going to add a new column called “order” which will be the embedded schema I mention. I did not find anything that allows me to specify what fields can or cannot be null when defining the embedded schema or the migration.

Have I missed something?

No, you got it right, null: false is for regular schemas, there are also no migrations for embedded schemas.

1 Like

Depending on the database you can use certain constraints for whatever column type is used to back :map. In postgres this would be json/jsonb column and you can e.g. use check constraints to enforce properties on them.

For a topic like this I’d strongly suggest looking at your used database docs just as much as at ecto.

1 Like