Update many_to_many joins with an array of IDs

369 views
Skip to first unread message

Nicolas Blanco

unread,
Sep 17, 2017, 2:58:42 PM9/17/17
to elixir-ecto
Hello :D !

I'm trying to find the best way to update the join table of a many_to_many relation with just an array of the relation IDs.
I don't want to preload all the relations, because I'm just changing the join table.

This seems quite simple, my schema has :

    many_to_many :groups, Group, join_through: "products_groups"

The client-side sends through the form an array of IDs of groups : { group_ids: ["1", "2"] }.

In Ruby's ActiveRecord and Mongoid, has_and_belongs_to_many relations automatically create a *_ids setter and getter on the object. So you can directly pass your hash containing the key *_ids and the magic of the ORM will automatically update the join table.

In Ecto, there's no such way, so I've come up with adding a virtual attribute and a function returning a Multi like this:

    ...
    field :group_ids, {:array, :integer}, virtual: true
    ...

    def group_ids_join(%Ecto.Multi{} = multi, %Ecto.Changeset{changes: %{group_ids: group_ids}} = changeset) do
      multi
      |> Multi.delete_all(:remove_group_ids, (from pg in "products_groups", where: pg.product_id == ^changeset.data.id))
      |> Multi.insert_all(:insert_group_ids, "products_groups", Enum.map(group_ids, fn group_id -> %{product_id: changeset.data.id, group_id: group_id} end))
    end
    def group_ids_join(%Ecto.Multi{} = multi, _), do: multi

Do you see a better way of handling this use case?

Like said, it's working fine, but I hope that this feature would somehow be integrated directly in Ecto as it looks like a very common use case.

José Valim

unread,
Sep 17, 2017, 3:43:58 PM9/17/17
to elixi...@googlegroups.com
If you use many_to_many or has_many, you can receive the IDs and use put_assoc/3.

Imagine that a project has many managers and you receive the manager IDs on the backend:

def update_managers(project_changeset, manager_ids) do
  managers = Repo.all m in Manager, m.id in ^manager_ids
  Ecto.Changeset.put_assoc(project_changeset, :managers, managers)
end

put_assoc will then compute which managers were added and which ones were removed. The only requirement is that the managers have been preloaded into the project.

I believe we also cover this in our ebook: http://pages.plataformatec.com.br/ebook-whats-new-in-ecto-2-0




José Valim
Skype: jv.ptec
Founder and Director of R&D

--
You received this message because you are subscribed to the Google Groups "elixir-ecto" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-ecto+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-ecto/07f410fa-e221-46b3-ac33-17d478119e2d%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Nicolas Blanco

unread,
Sep 17, 2017, 4:25:26 PM9/17/17
to elixir-ecto
Thanks José.

I think maybe I will use put_assoc because it's a lot less code... even if that means 2 additional SQL queries comparing to only updating the join table (one to preload the records, one for the given IDs)...

Nicolas.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-ecto...@googlegroups.com.

Ben Wilson

unread,
Sep 18, 2017, 8:00:13 AM9/18/17
to elixir-ecto
It's worth noting that ActiveRecord also does the same number of SQL queries internally.


In this case we have a user, has many memberships, has many organizations through memberships. Setting the organization_ids forces a load of the organizations, exactly like doing the explicit preload here in ecto.

Nicolas Blanco

unread,
Sep 18, 2017, 8:39:28 AM9/18/17
to elixir-ecto
OK.
Thanks Ben for the logging and comparison with ActiveRecord.

Nicolas.
Reply all
Reply to author
Forward
0 new messages