Putting Associations with many_to_many relationships

I am trying to build a sales territory table based off of a combination of states. I believe this constitutes a many_to_many relationship if I am trying to build associations between the territories and the states tables in my database. I am trying to work through the Ecto many_to_many associations section found here. However after several attempts at putting my own associations within the contexts of my territories and states tables I kept getting this error:

** (RuntimeError) attempting to cast or change association `tags` from `Gimli.Prospector.Assignment.AssociationTest.Post` that was not loaded. Please preload your associations before manipulating them through changesets (ecto 3.4.6) lib/ecto/changeset/relation.ex:80: Ecto.Changeset.Relation.load!/2 (ecto 3.4.6) lib/ecto/changeset.ex:1199: Ecto.Changeset.put_change/7 (ecto 3.4.6) lib/ecto/changeset.ex:1409: Ecto.Changeset.put_relation/5

As a way of troubleshooting I tried building a posts and a tags table and duplicating the code exactly as it appears in the many_to_many docs and it still gives the same error which leads me to think something might have been deprecated that I don’t know about. I am running elixir 1.11.3.

For quick reference here are the migrations for the posts and tags tables:

  use Ecto.Migration

  def change do
    create table(:posts) do
      add :header, :string
      add :body, :string
    end
  end
end```

```defmodule Gimli.Repo.Migrations.CreateTag do
  use Ecto.Migration

  def change do
    create table(:tags) do
      add :name, :string
    end
  end
end```

Here are the schemas for each respectively:

```defmodule Gimli.Prospector.Assignment.AssociationTest.Post do
    use Ecto.Schema

    schema "posts" do
        field :header, :string
        field :body, :string
        
        many_to_many :tags, Gimli.Prospector.Assignment.AssociationTest.Tag, join_through: "post_tags"
    end
end```

```defmodule Gimli.Prospector.Assignment.AssociationTest.Tag do
    use Ecto.Schema

    schema "tags" do
      field :name, :string
      # the following line was added
      many_to_many :posts, Gimli.Prospector.Assignment.AssociationTest.Post, join_through: "posts_tags"
    end
  end```

and here is the migration for the creation of the helper table per the documentation:

```defmodule Gimli.Repo.Migrations.CreatePostsTags do
  use Ecto.Migration

  def change do
    create table(:posts_tags) do
      add :tag_id, references(:tags)
      add :post_id, references(:posts)
    end

    create unique_index(:posts_tags, [:tag_id, :post_id])
  end
end```

and here is the code from the shell (condensed):

```alias Gimli.Prospector.Assignment.AssociationTest.Post
  alias Gimli.Prospector.Assignment.AssociationTest.Tag
  alias alias Gimli.Repo
  clickbait_tag = Repo.insert! %Tag{name: "clickbait"}
  misc_tag = Repo.insert! %Tag{name: "misc"}
  ecto_tag = Repo.insert! %Tag{name: "ecto"}
  post = %Post{header: "Clickbait header", body: "No real content"}
  post = Repo.insert!(post)
  post_changeset = Ecto.Changeset.change(post)
  post_with_tags = Ecto.Changeset.put_assoc(post_changeset, :tags, [clickbait_tag, misc_tag])```

This is the point that the `RuntimeError` happens. Again this is just copied line for line from the Ecto docs. Any help figuring this out would be very appreciated! Thanks!

Seems you have a typo here:

many_to_many :tags, Gimli.Prospector.Assignment.AssociationTest.Tag, join_through: "post_tags"

Should be "posts_tags". On to the actual question: the error is giving you the right solution - you need to preload the tags for the post, otherwise Ecto won’t know what to do with the tags that you’re trying to set on the post (insert them? update them? delete old ones?):

post = Repo.insert!(post)
post = Repo.preload(post, :tags)
# now Ecto knows the current tags and will diff them with what you're providing

See the docs for many_to_many/3, it has the preloading in the example. Same thing in the put_assoc/4 docs.

3 Likes

Thank you! I was misunderstanding the definition of the preload function! It worked!

1 Like