Help to persist one model nested on another, that references a 3rd one

Maybe What to do when you have to add a new parameter based on the result you get from a Service? would help. tl;dr I tend to avoid it.

Trying to create a Parent changeset and inserting it doesn’t work

Have you tried cast_assoc/3?

defmodule Models.Parent do
  use Ecto.Schema
  import Ecto.Changeset

  schema "parents" do
    field(:description, :string)
    has_one(:child, Models.Child)
    timestamps()
  end

  @required_fields [:description]

  def changeset(parent, params \\ :empty) do
    parent
    |> cast(params, @required_fields)
    |> cast_assoc(:child) # <---
    |> validate_required(@required_fields)
    |> validate_length(:description, min: 5)
  end
end

It would cast data for "child" params key and try and insert it into "child" table with parent’s foreign key set.

Question 2: is there a better way?

I’d definitely try to avoid casting foreign keys from outside data (params). See casting foreign keys in ecto changesets · Issue #29 · nccgroup/sobelow · GitHub. tl;dr it might make your app vulnerable.

I think all your problems should be handled by cast_assoc/3


Off-topic

defmodule Models.Child do
  ...
  @required_fields [:name, :state]
  @assoc_fields [:another_model_id]

  def changeset(child, params \\ :empty) do
    child
    |> cast(params, @required_fields ++ @assoc_fields)
    |> validate_required(@required_fields)
    |> validate_inclusion(:state, ~w[new old])
  end
end

You can concat the list of castable fields at compile time and avoid that work during run time

defmodule Models.Child do
  ...
  @required_fields [:name, :state]
  @assoc_fields [:another_model_id]
  @castable_fields @required_fields ++ @assoc_fields

  def changeset(child, params \\ :empty) do
    child
    |> cast(params, @castable_fields)
    |> validate_required(@required_fields)
    |> validate_inclusion(:state, ~w[new old])
  end
end

I don’t think :empty in params is being used anymore, it’s can replaced with an empty map.

def changeset(child, params \\ %{}) do