Many to many relationships with Ecto

Elixir with Ecto

A University of College London study discovered something we all knew: humans are lazy. Meaning that most of the times, when we have a problem we take the easiest way to solve it. But it doesn’t mean we are solving the problem right away.

You might ask yourself, what does this have to do with Elixir and Ecto? Well, at Coletiv we have been working on a project where a user can manage multiple projects and a project can be managed by multiple users, this is a trivial case known as many to many.

In terms of database diagram/structure, I created both user and project tables, plus the so-called Many to Many table (user_project), as the diagram below illustrates.

Database with many_to_many table — user_project

First implementation — Scaffolding all
At first, I thought the easiest way to solve the problem was to scaffold each table and manually create a method that directly managed the projects from a user in the user_project table. 
It worked perfectly fine, the unit tests proved the solution worked just fine!

Step 1
Create a migration that adds user_project table.

Step 2
Create module UserProject with schema and changeset.

Step 3
Create a context function that updates the projects a user has access to. This naive solution deleted all current projects and inserted the list of projects passed as a parameter. This means that if, for example, we were adding a new project to the user and keeping all the old ones, we would still have to delete the old ones and re-add them again with the new list.

The right way — Use Ecto powers

One of the things we don’t facilitate at Coletiv is pull requests and code reviews. As soon as I did a pull request with these changes, my fellow colleagues immediately refused to merge the pull request and asked: “Shouldn’t Ecto manage the link between users and projects for you?”. This kept me thinking for a while 🤔.

I decided to get back to the drawing board and started reading the Ecto documentation again until I found the put_assoc/4 function that did just what I was looking for: manage many_to_many associations. But it didn’t work straight away.

Warning: When you use many_to_many and put_assoc/4 you should be aware of the preloads, you need to ensure that the many_to_many relationship data is loaded so that it is updated. And don’t forget to use the on_replace option when declaring the relationship on your model. It took me a while to figure out why the relationship was not being updated, kudos for David Magalhães for the help.

If you are still confused, please check the steps below:

Step 1
Create a migration that adds user_project table.

Step 2
Add many_to_many relations in Project and User module.

Step 3. Add new changeset to user module for editing projects (adding the put_assoc/4)

Step 4
Create a function that makes use of the changeset to update the projects of a user.

Lesson learned

In short, the lesson to take from here is to not always go for the easiest solution as it might not be the best in the long term. And always take time to learn, evolve and experiment new ways of doing things so that you don’t get stuck with outdated techniques.

Thank you for reading!

I hope this article helps you and your team use Many to Many relationships on your Elixir projects that use Ecto.

Don’t forget to follow Coletiv on Medium, Twitter, and LinkedIn as we keep posting more and more interesting articles on multiple technologies. And if you think we should write about some specific topic, please let us know.

Thanks again for reading! Feel free to comment and share any question you might have!