Updating many_to_many with put_assoc and an array of ids

Hello!

I’ve got a simple schema Product with many_to_many :tags relation without an intermediate schema for the join table.

many_to_many :tags, Tag, join_through: "products_tags"

Now the question is quite simple, in Ruby’s ActiveRecord, there’s a convenient setter where you can just set to an array of IDs of the associated records, and it will automatically update the join records, ie.: product.tag_ids = [1, 2, 5, 6]

In Ecto if I want to use put_assoc, it requires me to preload all the tags in a array, so I can perform put_assoc(:tags, tags)?

What if I don’t want to preload them, like it’s done in ActiveRecord?

3 Likes

Maybe you can insert your data directly into the join table? Might not be the best approach, but it would work (if there are no conflicts; if there are, then :on_conflict option might be helpful).

product_id = 1
tag_ids = [1, 2, 5, 6]
new_entries = for tag_id <- tag_ids, do: %{product_id: product_id, tag_id: tag_id}
Repo.insert_all("products_tags", new_entries)
1 Like

Check out chapter 9 in:

http://blog.plataformatec.com.br/wp-content/uploads/2016/11/whats-new-in-ecto-2-0.pdf

In particular the section title “put_assoc vs cast_assoc”. I think what you want is put_assoc :slight_smile: More docs here:

https://hexdocs.pm/ecto/Ecto.Changeset.html#put_assoc/4

1 Like

Hey @aseigo, thanks for your answer. But if you look in the guide, he’s basically getting all the existing tags from the database and creating the tags which do not exist.

In my case I just receive an array of ids of tags, I don’t want to load them, just create the intermediate join…

So I think something like @idi527 wrote is maybe the way to go. But because I’m receiving a final array everytime I will have to remove first all the existing join records, before inserting all them again… so I will have to use a transaction or a Multi…

I thought first that this kind of use case was common and would be integrated in Ecto, it’s been there for a while in Ruby’s ActiveRecord and Mongoid…

4 Likes

Bumping this thread, is there any elegant solution for this common problem now?

If I understand correctly, put_assoc only make sure parent is associated with the record.
So why can’t I fool it by doing this?
put_assoc(:tags, [%Tag{id: 1}, %Tag{id: 3}]) or more elegantly…
put_assoc(:tags, Enum.map(tag_ids, fn x -> %Tag{id: x} end))
Its all ecto need to know to update association right?

1 Like

2021 and that’s still a question :thinking:
What is the elegant way to overwrite a full association with an array of ids?

3 Likes

Just hacked this which seems to be working: Ecto put_assoc with an array of ids · GitHub

1 Like