** (RuntimeError) cannot encode association :manufacturer from Cybord.PredictionModels.LeadPrediction to JSON because the association was not loaded

So I wasted 5 hours on trying to solve this one. And finally gave up in hope of some communal support .

I have a LeadPrediction schema that belongs to Manufacturer. I followed all the webs wisdom and got the following code going:

defmodule Cybord.PredictionModels.LeadPrediction do
  use Ecto.Schema
  import Ecto.Changeset

  alias Cybord.ComponentManufacturers.Manufacturer

  @derive {Jason.Encoder, only: [:manufacturer]}
  schema "lead_predictions" do
    field :capture_method, :string
    field :component_type, :string
    field :package, :string
    belongs_to :manufacturer, Manufacturer, references: :name, type: :string
    timestamps()
  end

  @doc false
  def changeset(lead_prediction, attrs) do
    lead_prediction
    |> cast(attrs, [:component_type, :package, :capture_method, :manufacturer_id])
    |> validate_required([:component_type, :package, :capture_method])
    |> cast_or_constraint_assoc(:manufacturer)
    |> foreign_key_constraint(:manufacturer)
  end

  defp cast_or_constraint_assoc(changeset, name) do
    {:assoc, %{owner_key: key}} = changeset.types[name]

    if changeset.changes[key] do
      assoc_constraint(changeset, name)
    else
      cast_assoc(changeset, name, required: true)
    end
  end

defmodule Cybord.ComponentManufacturers.Manufacturer do
  use Ecto.Schema
  import Ecto.Changeset

  @primary_key {:name, :string, autogenerate: false}
  @derive {Phoenix.Param, key: :name}
  @derive {Jason.Encoder, only: [:name]}
  schema "manufacturers" do
    timestamps()
  end

  @doc false
  def changeset(manufacturer, attrs) do
    manufacturer
    |> cast(attrs, [:name])
    |> unique_constraint(:name, name: :manufacturers_pkey)
    |> validate_required([:name])
  end
end
def create(conn, %{"lead_prediction" => lead_prediction_params}) do
    with {:ok, %LeadPrediction{} = lead_prediction} <-
           PredictionModels.create_lead_prediction_with_preloads(lead_prediction_params,
             preloads: [:manufacturer]
           ) do
      IO.puts("lead_prediction: #{inspect(lead_prediction)}")

      conn
      |> put_status(:created)
      |> put_resp_header("location", Routes.lead_prediction_path(conn, :show, lead_prediction))
      |> render("show.json", lead_prediction: lead_prediction)
    end
  end
 def create_lead_prediction_with_preloads(attrs \\ %{}, preloads \\ []) do
    preloads = Keyword.get(preloads, :preloads, [])

    {:ok, prediction} =
      %LeadPrediction{}
      |> Repo.preload(preloads)
      |> LeadPrediction.changeset(attrs)
      |> Repo.insert()

    prediction = get_lead_prediction_with_preloads!(prediction.id, preloads: [:manufacturer])
    {:ok, prediction}
  end
def get_lead_prediction_with_preloads!(id, preloads \\ []) do
    preloads = Keyword.get(preloads, :preloads, [])

    Repo.get!(LeadPrediction, id)
    |> Repo.preload(preloads)
  end

When I run this test:

 test "renders lead_prediction when data is valid", %{conn: conn} do
      conn =
        post(conn, Routes.lead_prediction_path(conn, :create), lead_prediction: @create_attrs)

      assert %{"id" => id} = json_response(conn, 201)["data"]

      conn = get(conn, Routes.lead_prediction_path(conn, :show, id))

      assert %{
               "id" => id,
               "capture_method" => "some capture_method",
               "component_type" => "some component_type",
               "package" => "some package"
             } = json_response(conn, 200)["data"]
    end

I get the following error:

** (RuntimeError) cannot encode association :manufacturer from Cybord.PredictionModels.LeadPrediction to JSON because the association was not loaded.
     
     You can either preload the association:
     
         Repo.preload(Cybord.PredictionModels.LeadPrediction, :manufacturer)
     
     Or choose to not encode the association when converting the struct to JSON by explicitly listing the JSON fields in your schema:
     
         defmodule Cybord.PredictionModels.LeadPrediction do
           # ...
     
           @derive {Jason.Encoder, only: [:name, :title, ...]}
           schema ... do
     
     code: conn = get(conn, Routes.lead_prediction_path(conn, :show, id))
     stacktrace:
       (ecto 3.4.4) lib/ecto/json.ex:4: Jason.Encoder.Ecto.Association.NotLoaded.encode/2
       (jason 1.2.1) lib/encode.ex:182: Jason.Encode.map_naive_loop/3
       (jason 1.2.1) lib/encode.ex:183: Jason.Encode.map_naive_loop/3
       (jason 1.2.1) lib/encode.ex:173: Jason.Encode.map_naive/3
       (jason 1.2.1) lib/encode.ex:172: Jason.Encode.map_naive/3
       (jason 1.2.1) lib/encode.ex:35: Jason.Encode.encode/2
       (jason 1.2.1) lib/jason.ex:197: Jason.encode_to_iodata!/2
       (phoenix 1.5.3) lib/phoenix/controller.ex:776: Phoenix.Controller.render_and_send/4
       (cybord 0.1.0) lib/cybord_web/controllers/lead_prediction_controller.ex:1: CybordWeb.LeadPredictionController.action/2
       (cybord 0.1.0) lib/cybord_web/controllers/lead_prediction_controller.ex:1: CybordWeb.LeadPredictionController.phoenix_controller_pipeline/2
       (phoenix 1.5.3) lib/phoenix/router.ex:352: Phoenix.Router.__call__/2
       (cybord 0.1.0) lib/cybord_web/endpoint.ex:1: CybordWeb.Endpoint.plug_builder_call/2
       (cybord 0.1.0) lib/cybord_web/endpoint.ex:1: CybordWeb.Endpoint.call/2
       (phoenix 1.5.3) lib/phoenix/test/conn_test.ex:225: Phoenix.ConnTest.dispatch/5
       test/cybord_web/controllers/lead_prediction_controller_test.exs:47: (test)

But when I inspect the lead_prediction in the LeadPredictionController :create i get:

lead_prediction: %Cybord.PredictionModels.LeadPrediction{__meta__: #Ecto.Schema.Metadata<:loaded, "lead_predictions">, capture_method: "some capture_method", component_type: "some component_type", id: 343, inserted_at: ~N[2020-07-04 17:38:57], manufacturer: %Cybord.ComponentManufacturers.Manufacturer{__meta__: #Ecto.Schema.Metadata<:loaded, "manufacturers">, inserted_at: ~N[2020-07-04 17:38:57], name: "some manufacturer", updated_at: ~N[2020-07-04 17:38:57]}, manufacturer_id: "some manufacturer", package: "some package", updated_at: ~N[2020-07-04 17:38:57]}

And as you see, it seems that everything is loaded as required.
I simply don’t understand what I am doing wrong.

1 Like

The error says:

code: conn = get(conn, Routes.lead_prediction_path(conn, :show, id))

So it is not the :create action that is the issue, it is the :show action. Try looking in that direction.

3 Likes

Thanks, it was easily solved. I guess this is what you get for staring at screen for too long.

2 Likes