Setting values saved in database by embedded_schema

How can one manually control how an embedded_schema gets saved in the database?

When I Repo.insert! a Person with the data.name field blank, the database shows %{name: nil} and I’d like nil values to not be saved in the database.

defmodule App.PersonData do

  embedded_schema do
    field :name, :string
    field :slug, :string
  end
end
defmodule App.Person do

  schema "people" do
    embeds_one :data, App.PersonData
  end
end
1 Like

Hey! Can you elaborate more on what you mean by “I’d like nil values not to be saved in the database”? It seems a bit contradictory since the concept of nil actually represents the absence of a value.

Schemas are about data containers with a known/fixed set of keys – structs at runtime. Leaving out keys contradicts that abstraction.

You can however build a custom ecto type to do whatever transformation you need on a field value.

2 Likes

sorry, i meant null values

currently the jsonb field shows %{name: null} – would prefer to remove those when storing in the db

is there any documentation on how to create a custom type for an embedded_schema

You can refer to the Ecto documentation here: Ecto.Type — Ecto v3.12.5

looking at those docs, i’m still a bit lost on how to implement it – for example if I change my code to:

defmodule App.Person do
  schema "people" do
    embeds_one :data, App.CustomType
  end
end

how would i define the fields currently defined in:

defmodule App.PersonData do

  embedded_schema do
    field :name, :string
    field :slug, :string
  end
end

You can do field :data, App.CustomType. Embed macros only work with embedded schemas.

if you’re not super keen on creating your own custom type, there’s nothing stopping you from defining a changeset function in PersonData and relying on it to handle validations/overwrites.

e.g.,

# in person.ex
defmodule App.Person do
  schema "people" do
    embeds_one :data, App.CustomType
  end

  def changeset(person, attrs) do
    person
    |> cast_embed(
      :data,
      with: &App.PersonData.changeset/2,
    )
  end
end

# in person_data.ex
defmodule App.PersonData do

  embedded_schema do
    field :name, :string
    field :slug, :string
  end

  def changeset(person_data, attrs) do
    person_data
    |> cast(attrs, [:name, :slug])
    |> remove_nil_values(...)
  end

  def remove_nil_values(changeset, ...) do
    ...
  end
end
3 Likes

yes! this is what i’m looking to do :slight_smile: how would the remove_nil_values function look? it seems the App.PersonData struct always adds null in the db for empty values

You can just define the data field as a :map instead of an embedded schema. This way, you can achieve what you want.

2 Likes

While this works, it’s not a good advice if you are requiring validation.

1 Like

You can use cast_embed/3 to validate the embedded field.