Ecto `change/2` preloading associations?

Are the items “preloaded” with an empty list, or are there actual items? If it’s empty I suspect there’s a difference in the test ctxt’s cart struct metadata states.

change/2 added support for assocs and embeds in v2.2.0-rc.0 about 7 years ago and has called Ecto.Changeset.Relation.load!/2 since then.

  @doc """
  Loads the relation with the given struct.

  Loading will fail if the association is not loaded but the struct is.
  """
  def load!(%{__meta__: %{state: :built}}, %NotLoaded{__cardinality__: cardinality}) do
    cardinality_to_empty(cardinality)
  end

  def load!(struct, %NotLoaded{__field__: field}) do
    raise "attempting to cast or change association `#{field}` " <>
          "from `#{inspect struct.__struct__}` that was not loaded. Please preload your " <>
          "associations before manipulating them through changesets"
  end

  def load!(_struct, loaded), do: loaded

  defp cardinality_to_empty(:one), do: nil
  defp cardinality_to_empty(:many), do: []

This function raises if the association isn’t loaded on the parent schema when the parent’s metadata state isn’t :built, otherwise it returns a default empty value depending on the relation’s cardinality.

built_cart = %MyApp.Carts.Cart{}
assoc_items = built_cart.cart_items

assert Ecto.get_meta(built_cart, :state) == :built
assert is_struct(assoc_items, Ecto.Association.NotLoaded)
assert Ecto.Changeset.Relation.load!(built_cart, assoc_items) == []


%{cart: ctxt_cart} = ctxt
assoc_items = ctxt_cart.cart_items

assert Ecto.get_meta(ctxt_cart, :state) == :loaded
assert is_struct(assoc_items, Ecto.Association.NotLoaded)
assert_raise RuntimeError, fn ->
  Ecto.Changeset.Relation.load!(ctxt_cart, assoc_items) == []
end

1 Like