Using belongs_to in an embedded schema to create new records

Hi there.

I have another problem which I can’t fight on myself.

I have a deeply nested schema in my Ecto model. One of the deep inside branches looks like this:

embeds_many :configs, ConfigSpec, on_replace: :delete do
  def changeset(config_spec, attrs) do
    config_spec
    |> cast(attrs, [:target])
    |> cast_assoc(:config,
      required: true,
      with: &PtahServer.DockerConfigs.DockerConfig.changeset/2
    )
    |> validate_required([:target])
  end

  field :target, :string

  belongs_to :config, PtahServer.DockerConfigs.DockerConfig
end

Everything works fine until I want to add a new ConfigSpec.

I have been using the hidden inputs approach with the :configs_sort field, but whenever I click “add an item” I’m getting non-loaded association error on the :config field.

It works perfectly fine if database already has some data (I preload everything what’s required), fails only for items being created at the moment.

** (ArgumentError) using inputs_for for association `config` from `PtahServer.Services.Service.ServiceSpec.TaskTemplate.ContainerSpec.ConfigSpec` but it was not loaded. Please preload your associations before using them in inputs_for

What will be the right approach here?

Form is really deeply nested. :slightly_smiling_face:

stack[services][0][spec][task_template][container_spec][configs][0][config]

stack[services] is the list of Ecto models, whereas spec and everything inside (except the config field) are embedded schemas.

For the record, here is my PR: feat: #86 add support for custom tls certs by bohdan-shulha · Pull Request #87 · ptah-sh/ptah_server · GitHub

The related code belongs to lib/ptah_server_web/live/stack_live/components/service_component.html.heex

I have tried to manually override values in the changeset, but no luck - embedded records can be created, but the entity defined via belongs_to is not created.

You need to build an empty association for every new one you want. For example:

defmodule Bar do
  schema "bars" do
    belongs_to :foo, Foo
  end
end

defmodule Foo do
  schema "foos" do
    has_many :bars, Bar
  end

  def append_empty_bar(changeset) do
    new_bar = %Bar{}
    current_bars = Ecto.Changeset.get_assoc(changeset, :bars) # in your case, `get_embed`
    updated_bars = current_bars ++ [new_bar]

    Ecto.Changeset.put_assoc(:bars, updated_bars) # or in your case, `put_embed`
  end
end

# In some context function:
changeset =
  Foo.changeset()
  |> Foo.append_new_bar()

I wouldn’t necessarily call this exemplary code, I just wanted to make it as clear as I could (apologies if I failed here).

You should take a look at this lib: ecto_nested_changeset.