Unable to preload associations for Ecto schema

Hello all,

Its a pleasure to join this community! I just started learning Elixir. I love it so much.
I come from Python.

One of the issues I have been fighting with the Phoenix Web Framework is related to loading schema associations. I still cant fully get the hang of it.
I suspect I am doing something wrong in the relationship definitions.
I am developing a simple app to learn about Elixir.

And it consists of a few schemas:

defmodule NimPhoenix.Accounts.User do
  use Ecto.Schema
  import Ecto.Changeset
  alias NimPhoenix.Enums.NotificationTimesEnum

  @primary_key {:user_id, :binary_id, autogenerate: true}
  schema "users" do
    field :handle, :string
    field :name, :string
    field :first_name, :string
    field :last_name, :string
    field :email, :string
    field :password, :string, virtual: true
    field :hashed_password, :string
    field :active, :boolean, default: true
    field :open, :boolean, default: true
    field :visible, :boolean, default: true
    field :confirmed_at, :utc_datetime
    field :notification_time, NotificationTimesEnum, default: :WHENEVER

    has_many :thread_participants, NimPhoenix.Messaging.ThreadParticipant,
      foreign_key: :user_id

    has_many :threads, through: [:thread_participants, :thread]

    has_many :messages, NimPhoenix.Messaging.Message,
      foreign_key: :sender_id

    has_many :bids, NimPhoenix.Messaging.Bid

    timestamps(type: :utc_datetime)
  end

Bid:

defmodule NimPhoenix.Messaging.Bid do
  use Ecto.Schema
  import Ecto.Changeset
  alias NimPhoenix.Enums.BidTypeEnum

  @primary_key {:bid_id, :binary_id, autogenerate: true}
  schema "bids" do
    field :bid_type, BidTypeEnum
    field :nod, :integer

    belongs_to :bidder, NimPhoenix.Accounts.User,
      foreign_key: :bidder_id,
      type: :binary_id

    belongs_to :thread, NimPhoenix.Messaging.Thread,
      foreign_key: :thread_id,
      type: :binary_id

    timestamps(type: :utc_datetime)
  end

  def changeset(bid, attrs) do
    bid
    |> cast(attrs, [:bid_type, :nod, :bidder_id, :thread_id])
    |> validate_required([:bid_type, :bidder_id, :thread_id])
    |> validate_inclusion(:nod, 1..365)
  end

end

Thread:

defmodule NimPhoenix.Messaging.Thread do
  use Ecto.Schema
  import Ecto.Changeset
  alias NimPhoenix.Enums.NegotiationStateEnum

  @primary_key {:thread_id, :binary_id, autogenerate: true}
  schema "threads" do
    field :nego_state, NegotiationStateEnum, default: :IN_PROGRESS
    field :nod_agreed, :integer

    has_many :thread_participants, NimPhoenix.Messaging.ThreadParticipant,
      foreign_key: :thread_id

    has_many :users, through: [:thread_participants, :user]
    has_many :messages, NimPhoenix.Messaging.Message,
      foreign_key: :thread_id
    has_many :bids, NimPhoenix.Messaging.Bid,
      foreign_key: :thread_id

    timestamps(type: :utc_datetime)
  end

  def changeset(thread, attrs) do
    thread
    |> cast(attrs, [:nego_state, :nod_agreed])
    |> validate_required([:nego_state])
  end
end

In the LiveView html, I want to be able to show the user handle of the last bid. But it was not loaded, so I preload when grabbing the thread, as so:

  def get_thread(thread_id) do
    thread =
      Repo.get(Thread, thread_id)
      |> Repo.preload([:thread_participants,:messages, :users, :bids [:bidder]])
    if thread, do: {:ok, thread}, else: {:error, :not_found}
  end

However, if I add this part to the preload:

bids [:bidder]])

It fails with:

[debug] QUERY OK source="bids" db=0.4ms idle=4.6ms
SELECT b0."bid_id", b0."bid_type", b0."nod", b0."bidder_id", b0."thread_id", b0."inserted_at", b0."updated_at" FROM "bids" AS b0 WHERE (b0."bid_id" = $1) ["070e5ddb-5c54-4503-b896-65630907512d"]
↳ NimPhoenix.Messaging.get_bid/1, at: lib/nim_phoenix/messaging/messaging.ex:179
[error] Task #PID<0.1804.0> started from #PID<0.1745.0> terminating
** (Ecto.QueryError) deps/ecto/lib/ecto/association.ex:1220: field `id` in `where` does not exist in schema NimPhoenix.Messaging.Thread in query:

from t0 in NimPhoenix.Messaging.Thread,
  where: t0.id == ^"e6b1d0d5-bcfe-4955-bd23-f14c3cf5b48d",
  select: {t0.id, t0}

    (elixir 1.17.3) lib/enum.ex:2531: Enum."-reduce/3-lists^foldl/2-0-"/3
    (elixir 1.17.3) lib/enum.ex:1829: Enum."-map_reduce/3-lists^mapfoldl/2-0-"/3
    (elixir 1.17.3) lib/enum.ex:2531: Enum."-reduce/3-lists^foldl/2-0-"/3
    (ecto 3.12.4) lib/ecto/repo/queryable.ex:214: Ecto.Repo.Queryable.execute/4
    (ecto 3.12.4) lib/ecto/repo/queryable.ex:19: Ecto.Repo.Queryable.all/3
    (ecto 3.12.4) lib/ecto/repo/preloader.ex:332: Ecto.Repo.Preloader.fetch_query/8
    (elixir 1.17.3) lib/task/supervised.ex:101: Task.Supervised.invoke_mfa/2
    (elixir 1.17.3) lib/task/supervised.ex:36: Task.Supervised.reply/4

If I remove it, I am unable to load the user for the bid.

I find it weird how it complains on the id field. I have no id fields, so I guess its defaulting to it.

I think this is a syntax error:

      |> Repo.preload([:thread_participants,:messages, :users, :bids [:bidder]])

Which I guess should look like this:

      |> Repo.preload([:thread_participants, :messages, :users, [bids: :bidder]])

Also you can do everything in a single query:

import Ecto.Query

# ...

  def get_thread(thread_id) do
    thread =
      Repo.one(
        from t in Thread,
          where t.thread_id == thread_id,
          preload: [:thread_participants, :messages, :users, [bids: :bidder]]
        )

    if thread, do: {:ok, thread}, else: {:error, :not_found}
  end

Thank you!

Updating the preload line with

      |> Repo.preload([:thread_participants,:messages, :users, [:bids [:bidder]])

Still gives me the same error:

[info] Sent 500 in 17ms
[error] ** (Ecto.QueryError) deps/ecto/lib/ecto/association.ex:1226: field `id` in `where` does not exist in schema NimPhoenix.Accounts.User in query:

from u0 in NimPhoenix.Accounts.User,
  where: u0.id in ^["003535cc-2a27-418b-ac79-26bf9be02783", "fe5068d2-1985-414a-b142-01745506608c"],
  select: {u0.id, u0}

The second statement gives me syntax error, unexpected comma

        from t in Thread,
          where t.thread_id == thread_id,

Finally fixed this.
It turns out, I had to remove the foreign key and mark it as a reference.
Also, I had to specify the “type” as “:binary_id”

So the schema for bid def now looks like:


    belongs_to :bidder, NimPhoenix.Accounts.User,
      references: :user_id,
      type: :binary_id

    belongs_to :thread, NimPhoenix.Messaging.Thread,
      references: :thread_id,
      type: :binary_id