Assert embed field structure with Ecto

Hi! I am trying to figure out if it is possible to have have embedded field as a struct with empty/default values if the embedded field in params is nil.

So let’s say I have two embedded schemas like this.

defmodule EmbedSchema do
  use Ecto.Schema
  import Ecto.Changeset

  embedded_schema do
    field(:good_subfield)
    field(:pretty_subfield)
  end

  def changeset(schema, params) do
    schema
    |> cast(params, [:good_subfield, :pretty_subfield])
  end
end

defmodule ParentSchema do
  use Ecto.Schema
  import Ecto.Changeset

  embedded_schema do
    field(:a_field)
    embeds_one(:embed_field, EmbedSchema)
  end

  def new(params) do
    %__MODULE__{}
   |> cast(params, [:a_field])
   |> cast_embed(:embed_field)
   |> apply_changes()
  end
end

If I pass params

%{
  a_field: "val",
  embed_field: %{
    good_subfield: "val2"
  }
}

It works fine and I get embed field with a proper struct as embed_field value, but what if embed_field is nil or even missing. I would like to get:

%ParentSchema{
  a_field: "val",
  embed_field: %EmbedSchema{
    good_subfield: nil,
    pretty_subfield: nil
  }
}

but instead I get:

%ParentSchema{
  a_field: "val",
  embed_field: nil
}

Is it possible with Ecto?

You also need to use Ecto.Changeset.cast_embed.

But I am doing that here:

  import Ecto.Changeset
  ...
  def new(params) do
    %__MODULE__{}
   |> cast(params, [:a_field])
   |> cast_embed(:embed_field)
   |> apply_changes()
  end

Did that solve your problem?

No, but I got another idea, but maybe there is a better way to solve this. So my next idea is instead of doing cast_embed/3 I do put_embed/4. So I filter out fields with nil and then create embed fields by either passing value from params or default to %{}. Though I am not sure this is the best way to go about this.

# ParentSchema
  def new(params) do
    params = Enum.reject(params, &is_nil(elem(&1, 1))) |> Enum.into(%{})
    embed_field = params |> Map.get(:embed_field, %{}) |> EmbedSchema.new()

    %__MODULE__{}
   |> cast(params, [:a_field])
   |> put_embed(:embed_field, embed_field)
   |> apply_changes()
  end
# EmbedSchema
  def new(params) do
    %__MODULE__{}
    |> cast(params, [:good_subfield, :pretty_subfield])
  end

put_embed will replace anything you had previously in the field, by the way. Have that in mind.

I’d be willing to help you more but not sure how. Wanna do a small GitHub repo with a project demonstrating the problem?

I think this is actually fine, the code works as I want it to, why I’m using Ecto and embedded schema is to get some additional validation over using plain structs, as I want to also be sure that the nested fields are always there. Anyway, thanks a lot for your replies!