No primary key value for struct and assiciation not loaded error at the same time

Hey,
I’m using phoenix live view 1.6 and getting the following error:

(Ecto.NoPrimaryKeyValueError) struct `%Test.Members.Member{__meta__: #Ecto.Schema.Metadata<:built, "member">, address: #Ecto.Association.NotLoaded<association :address is not loaded>, age: nil, id: nil, inserted_at: nil, name: nil, updated_at: nil}` is missing primary key value

I do preload the associated data:

Repo.get!(Member, id)
     |> Repo.preload(:address)

My schema ‘Member’:

  schema "member" do
    field :age, :integer
    field :name, :string

    has_many :address, Test.Members.Address
    timestamps()
  end

  @doc false
  def changeset(member, attrs) do
    member
    |> cast(attrs, [:name, :age])
    |> cast_assoc(:address)
    |> validate_required([:name, :age])
  end

schema ‘Address’:

schema "address" do

    field :number, :string

    field :street, :string

    belongs_to :member, Test.Members.Member

    timestamps()

  end

  @doc false

  def changeset(adress, attrs) do

    adress

    |> cast(attrs, [:street, :number])

    |> validate_required([:street, :number])

    |> assoc_constraint(:member)

  end

I’m following different articles on this topic, for example this one: Setup a has_many / belongs_to in Phoenix - Tutorials and screencasts for Elixir, Phoenix and LiveView

The error occurs when I’m trying to submit the data and trigger the action ‘save’.

My questions:

  • Are the missing primary key value and assoc. not loaded errors connected?
  • Does anyone know how to resolve them?

Thanks in advance!

Obviously the association is not loaded…

You need to preload in new, but also in edit, create, or update.

Please show your liveview code.

Thanks for the response. Yeah, I know that the association not loaded. But I don’t know what to do except preloading the association.

Here is the liveview code.

defmodule TestWeb.MemberLive.Index do
  use TestWeb, :live_view

  alias Test.Members
  alias Test.Members.Member

  @impl true
  def mount(_params, _session, socket) do
    IO.inspect(socket)
    {:ok, assign(socket, :member_collection, list_member())}
  end

  @impl true
  def handle_params(params, _url, socket) do
    {:noreply, apply_action(socket, socket.assigns.live_action, params)}
  end

  def handle_event("save", %{"member" => member_params}, socket) do
    case Members.create_member(member_params) do
      {:ok, _post} ->
        {:stop,
         socket
         |> put_flash(:info, "member created")
         |> redirect(to: socket.assigns.return_to)}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply, assign(socket, changeset: changeset)}
    end

  defp apply_action(socket, :edit, %{"id" => id}) do
    socket
    |> assign(:page_title, "Edit Member")
    |> assign(:member, Members.get_member!(id))
  end

  defp apply_action(socket, :new, _params) do
    IO.puts("handling apply_action new")
    IO.inspect(socket)
    socket
    |> assign(:page_title, "New Member")
    |> assign(:member, %Member{})
  end

  defp apply_action(socket, :index, _params) do
    socket
    |> assign(:page_title, "Listing Member")
    |> assign(:member, nil)
  end

  @impl true
  def handle_event("delete", %{"id" => id}, socket) do
    member = Members.get_member!(id)
    {:ok, _} = Members.delete_member(member)

    {:noreply, assign(socket, :member_collection, list_member())}
  end

  @impl true
  def handle_event("validate", %{"member" => member_params}, socket) do

    changeset = %Member{}
    |> Members.change_member(member_params)

  {:noreply, assign(socket, changeset: changeset)}
  end

Thanks.

And here the form:

  <%= form_for @changeset, "#", [phx_change: :validate, phx_submit: :save], fn f -> %>
  
    <%= label f, :name %>
    <%= text_input f, :name %>
    <%= error_tag f, :name %>
  
    <%= label f, :age %>
    <%= number_input f, :age %>
    <%= error_tag f, :age %>

    <%= inputs_for f, :address, fn a -> %>
      <%= label a, :street%>
      <%= number_input a, :street%>
      <%= error_tag a, :street%>
      
      <%= label a, :number%>
      <%= number_input a, :number%>
      <%= error_tag a, :number%>
    <% end %>
    <div>
      <%= submit "Save", phx_disable_with: "Saving..." %>
    </div>
    <% end %>

The address part isn’t displayed. I’m working on that too.

The problem is inside this… there is no way to pass a Member.

So, a new, without preload is used.

I know it’s the standard generated context, but You need to change to have an empty, preloaded Member.

Ok, thank you.
Do you men something like this?

def handle_event("save", %{"member" => member_params}, socket) do
    changeset = %Member{}
    |> Members.change_member(member_params)

    {:noreply, assign(socket, :changeset, changeset)}
  end

Yes, but with preload…

Repo.preload(%Member{}, :address)

Ok, thanks so far. I am currently trying to see if this works.