** (RuntimeError) cannot encode association :address from MyApp.Model.Property to JSON because the association was not loaded

Hi guys I’m having troubles with one preload.

I’m creating one property and I’m adding preloads, but when not have any register for has_one association is returning nil and through this error

     ** (RuntimeError) cannot encode association :address from MyApp.Model.Property to JSON because the association was not loaded.
     
     You can either preload the association:
     
         Repo.preload(MyApp.Model.Property, :address)
     
     Or choose to not encode the association when converting the struct to JSON by explicitly listing the JSON fields in your schema:
     
         defmodule MyApp.Model.Property do
           # ...
     
           @derive {Jason.Encoder, only: [:name, :title, ...]}
           schema ... do

I’ve print the Property created and looks like this

%MyApp.Model.Property{
  __meta__: #Ecto.Schema.Metadata<:loaded, "properties">,
  address: nil,
  currency: "USD",
  description: "Depto 2 ambientes",
  id: 908,
  inserted_at: ~N[2021-05-14 15:29:52],
  price: 260000,
  property_features: nil,
  property_images: [],
  property_type: "department",
  spaces: 2,
  status: true,
  subscriptions: [],
  updated_at: ~N[2021-05-14 15:29:52],
  user: #Ecto.Association.NotLoaded<association :user is not loaded>,
  user_id: 3093
}

I think that this could be for the nil returned in the property?

This is my function to create the property.

  def create_property(user) do
    Repo.insert!(%Property{
      description: "Depto 2 ambientes",
      price: 260_000,
      currency: "USD",
      spaces: 2,
      status: true,
      property_type: "department",
      user_id: user.id
    }) |> Repo.preload([:address, :subscriptions, :property_features, :property_images]) |> IO.inspect
  end

and this is the schema in property.ex

  @derive {Jason.Encoder, except: [:__meta__, :inserted_at, :updated_at, :user]}

  schema "properties" do
    field :spaces, :integer
    field :currency, :string
    field :description, :string
    field :price, :integer
    field :property_type, :string
    field :status, :boolean, default: false
    belongs_to :user, User
    has_one :address, Address
    has_one :property_features, PropertyFeatures
    has_many :subscriptions, Subscription
    has_many :property_images, PropertyImages

    timestamps()
  end

Any have idea where is the problem?

What does your database say? Does that property object actually have linked address?

There’s something odd going on - the printout shows nil in address, but the error message is from inside a protocol implementation for Ecto.Association.NotLoaded:

The code in create_property looks like it should return a correctly loaded association (nil is still “loaded”, just empty), so the next place to investigate is where the result from create_property is used.

2 Likes

is used in a test:

user_controller_text.exs

    test "return an array with properties", %{conn: conn} do
      user_1 = UserControllerTest.create_user()
      user_2 = UserControllerTest.create_user()
      create_property(user_1)
      create_property(user_2)
      conn = get(conn, Routes.property_path(conn, :index))
      assert conn.status == 200

      assert [
               %{
                 "address" => _address,
                 "currency" => _currency,
                 "description" => _description,
                 "id" => _id,
                 "price" => _price,
                 "property_type" => _property_type,
                 "spaces" => _spaces,
                 "status" => _status,
                 "subscriptions" => [],
                 "user_id" => _user_id
               },
               %{
                 "address" => _address2,
                 "currency" => _currency2,
                 "description" => _description2,
                 "id" => _id2,
                 "price" => _price2,
                 "property_type" => _property_type2,
                 "property_features" => _property_features2,
                 "spaces" => _spaces2,
                 "status" => _status2,
                 "subscriptions" => [],
                 "user_id" => _user_id2
               }
             ] = Jason.decode!(conn.resp_body)
    end

This is property_controller.ex

defmodule MyAppWeb.PropertyController do
  use MyAppWeb, :controller

  alias MyApp.CrudBase
  alias MyApp.Model.Property

  def index(conn, _params) do
    json(conn, CrudBase.all(Property, [:address, :subscriptions, :property_features, :property_images]))
  end
end

crud_base.ex

defmodule MyApp.CrudBase do
  alias MyApp.Repo
  alias MyApp.Errors.ErrorHandler

  def all(model, preloads) do
    Repo.all(model) |> Repo.preload([preloads])
  end
end

I suspect the extra [ ] around preloads here is confusing Repo.preload.

1 Like

Yes this is the solution for this test. Thanks. But I’ve other test that is failing could you please help me with this?

this is the update method

test "return property when data is valid", %{conn: conn} do
      user = UserControllerTest.create_user()
      property = create_property(user)
      property_param = @update_attrs |> put_in(["user_id"], property.id)

      conn =
        UserControllerTest.sigin_and_put_token(conn, user)
        |> put(Routes.property_path(conn, :update, property.id), property: property_param)

      assert 200 = conn.status
      assert {:ok, property_updated} = Jason.decode(conn.resp_body)

      updated_description = @update_attrs["description"]
      updated_price = @update_attrs["price"]
      updated_currency = @update_attrs["currency"]
      updated_spaces = @update_attrs["spaces"]

      assert %{
               "address" => _address,
               "id" => _user_id,
               "description" => _description,
               "price" => _price,
               "currency" => _currency,
               "spaces" => _spaces,
               "property_type" => _property_type
             } = property_updated

      assert updated_description == property_updated["description"]
      assert updated_price == property_updated["price"]
      assert updated_currency == property_updated["currency"]
      assert updated_spaces == property_updated["spaces"]
end

this is the update function in the property_controller.ex

  def update(conn, %{"id" => id, "property" => property_params}) do
    CrudBase.update(Property, id, property_params, [:address, :subscriptions, :property_features, :property_images])
    |> case do
      %Property{} = property -> json(conn, property)
      {:error, error} -> conn |> put_status(400) |> json(%{error: error})
    end
  end

this is the update method in my crud_base.ex

  def update(model, id, attrs, preloads) do
    changeset = Repo.get!(model, id) |> model.changeset_update(attrs)
    IO.inspect(preloads, label: model)
    case changeset.valid? do
      true -> Repo.update!(changeset) |> Repo.preload(preloads)
      false -> {:error, ErrorHandler.changeset_error_to_map(changeset)}
    end
  end

and this is the changeset

  def changeset_update(property, attrs) do
    property
    |> cast(attrs, [:description, :price, :currency, :spaces, :property_type])
  end

The endpoint is working but the test fail with the same error I can’t identify the error.

You have any idea?

** (RuntimeError) cannot encode association :address from MyApp.Model.Property to JSON because the association was not loaded.
     
     You can either preload the association:
     
         Repo.preload(MyApp.Model.Property, :address)
     
     Or choose to not encode the association when converting the struct to JSON by explicitly listing the JSON fields in your schema:
     
         defmodule MyApp.Model.Property do
           # ...
     
           @derive {Jason.Encoder, only: [:name, :title, ...]}
           schema ... do
     

The problem is here because if I try get and user with properties created need the preload to show this complete entity.

  def create_user() do
    Repo.insert!(%User{
      name: "Fabian",
      last_name: "Hernandez",
      phone: random_num(),
      email: "bian#{random_num()}@gmail.com",
      photo: "unaphoto",
      document: random_num(),
      document_type: "DNI",
      password: Bcrypt.hash_pwd_salt("password"),
      type: :client
    })
    |> Repo.preload([:properties])
  end

When I try of return one user with properties and this properties not have the preloads

here is the example

%MyApp.Model.User{
  __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
  document: 798048377,
  document_type: "DNI",
  email: "bian264519099@gmail.com",
  id: 3973,
  inserted_at: ~N[2021-05-15 04:34:59],
  last_name: "Hernandez",
  name: "Fabian",
  password: "$2b$04$GTA9i8JvQEpUVCKLx5l6Kuo9BtUa1KCbOoJ3rhXZpqMUa3Reob4Y2",
  phone: 953856669,
  photo: "unaphoto",
  properties: [
    %MyApp.Model.Property{
      __meta__: #Ecto.Schema.Metadata<:loaded, "properties">,
      address: #Ecto.Association.NotLoaded<association :address is not loaded>,
      currency: "USD",
      description: "Depto 2 ambientes",
      id: 1108,
      inserted_at: ~N[2021-05-15 04:34:59],
      price: 260000,
      property_features: #Ecto.Association.NotLoaded<association :property_features is not loaded>,
      property_images: #Ecto.Association.NotLoaded<association :property_images is not loaded>,
      property_type: "department",
      spaces: 2,
      status: true,
      subscriptions: #Ecto.Association.NotLoaded<association :subscriptions is not loaded>,
      updated_at: ~N[2021-05-15 04:34:59],
      user: #Ecto.Association.NotLoaded<association :user is not loaded>,
      user_id: 3973
    }
  ],
  type: :client,
  updated_at: ~N[2021-05-15 04:34:59]
}

The question is. How I could preload the attrs of the property from this place?

Repo.preload can handle nested associations. If you need the properties for that User to have a loaded address association, you’d write something like:

|> Repo.preload([properties: [:address]])
2 Likes

Thank you very much for the help, with this I was able to fix the error.
:slight_smile: