Issue with a `many_to_many` assoc and `join_keys`

I’m trying to set up the usual many-to-many relationship between two schemas: User and Conversation. However, the primary key on the conversations table is uuid rather than id.

What I have so far is the following:

  # conversation.ex
  @primary_key {:uuid, :binary_id, autogenerate: true}
  schema "conversations" do
    many_to_many :users, User,
      join_through: "conversations_users",
      join_keys: [user_id: :id, conversation_uuid: :uuid]
  end
  
  # user.ex
  schema "users" do
    many_to_many :conversations, Conversation,
      join_through: "conversations_users",
      join_keys: [user_id: :id, conversation_uuid: :uuid]
  end

Which makes sense to me, after reading the documentation about join_keys but unfortunately produces the following error on compile:

== Compilation error in file lib/myapp_web/models/conversation.ex ==
** (ArgumentError) schema does not have the field :id used by association :users, please set the :join_keys option accordingly
    lib/ecto/association.ex:959: Ecto.Association.ManyToMany.struct/3
    lib/ecto/schema.ex:1734: Ecto.Schema.association/5
    lib/ecto/schema.ex:1876: Ecto.Schema.__many_to_many__/4
    lib/myapp_web/models/conversation.ex:23: (module)
    (stdlib) erl_eval.erl:677: :erl_eval.do_apply/6
    (elixir) lib/kernel/parallel_compiler.ex:208: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/6

So I’m stuck here and I have a feeling I’m missing something really simple, but I can’t figure out what it is :slight_smile: Any help is appreciated! Thank you!

Edit: forgot to mention version numbers:

{:phoenix, "~> 1.4"},
{:phoenix_ecto, "~> 4.0"},
{:ecto_sql, "~> 3.0"},

Elixir: v1.8

Is your user schema meant to have a normal id or an uuid ? Perhaps try explicitly stating user has an id ?

Maybe you should just make a mediator schema (UserConversation) and just make Ecto connect both models through that instead of through the table explicitly? Might be worth the shot.

Thank you for the replies everyone!

I’m not sure what you mean — the user has only id, which is the primary key. The conversation is the odd one with uuid for primary key.

I did try that as well, I followed the examples in the docs and made a schema like so

defmodule ConversationUser do
  # ...
  @primary_key false

  schema "conversations_users" do
    belongs_to :user, User
    belongs_to :conversation, Conversation, foreign_key: :uuid
  end
end

and then used the schema for join_through, but that produced the same error message as above.

From the docs for join_keys:

It expects a keyword list with two entries, the first being how the join table should reach the current schema and the second how the join table should reach the associated schema.

Keyword lists are ordered - have you tried flipping the declaration for :users?

  # conversation.ex
  @primary_key {:uuid, :binary_id, autogenerate: true}
  schema "conversations" do
    many_to_many :users, User,
      join_through: "conversations_users",
      join_keys: [conversation_uuid: :uuid, user_id: :id]
  end

Ha! That’s a good point. I was really hoping that this would be it, but unfortunately this produces the same error, but for :conversations

** (ArgumentError) schema does not have the field :uuid used by association :conversations, please set the :join_keys option accordingly

So I’m guessing the order doesn’t matter.

FWIW This is the portion of the code that raises the exception — I’m trying to understand what is actually expected as input, but no luck so far:

Correction! @al2o3cr is totally right, the order does matter, and I wasn’t careful enough when making the changes.

The final solution is:

  # conversation.ex
  @primary_key {:uuid, :binary_id, autogenerate: true}
  schema "conversations" do
    many_to_many :users, User,
      join_through: "conversations_users",
      join_keys: [conversation_uuid: :uuid, user_id: :id]
  end
  
  # user.ex
  schema "users" do
    many_to_many :conversations, Conversation,
      join_through: "conversations_users",
      join_keys: [user_id: :id, conversation_uuid: :uuid]
  end

Notice that for the Conversation, the order is [conversation_uuid: :uuid, user_id: :id] and for the User it is [user_id: :id, conversation_uuid: :uuid]. What I did previously was using the same order for both, which produced the exception again.

I’ll try to find time to open an issue / PR when I get a chance :slight_smile: it will be great to improve the docs in this area. Thanks everyone!

1 Like