How to do many to many relationships with Phoenix Contexts?

I have a newbie question, but I think it’s tangentially relevant to the discussion as it’s not obvious how to do it, even from the Phoenix documentation.

A many-to-many relationship. A user has many chat rooms. A chat room has many users.

In SQL, this requires another table user_room. I would normally create this as a model, and then on my user model, add:
many_to_many :users, User, join_through: "user_rooms"

The question is:

  • In the world of contexts, where do I put user_room, and why?
  • If I’m joining tables in Ecto, is that really separate?
1 Like

One thing that doesn’t seem to get emphasized enough is that Phoenix Contexts are primarily about cleaning up the controller code and getting domain logic out of the controllers. Compare from

Programming Phoenix
https://media.pragprog.com/titles/phoenix/code/controllers_views_templates/listings/rumbl/web/controllers/user_controller.ex

defmodule Rumbl.UserController do
  use Rumbl.Web, :controller

  def index(conn, _params) do
    users = Repo.all(Rumbl.User) # accessing the Ecto repo directly
    render conn, "index.html", users: users
  end
end

to Programming Phoenix 1.4
https://media.pragprog.com/titles/phoenix14/code/controllers_views_templates/listings/rumbl/lib/rumbl_web/controllers/user_controller.ex

defmodule RumblWeb.UserController do
  use RumblWeb, :controller

  alias Rumbl.Accounts

  def index(conn, _params) do
    users = Accounts.list_users() # here it's up to the Accounts context to figure out where the data comes from
    render(conn, "index.html", users: users)
  end
end

From that perspective it’s OK to start with one context. Once domain logic starts piling up in your first context module you’ll probably have a much better idea how to break out additional contexts.

When it comes to Bounded Contexts you need a lot more domain information and understanding up front in order to make some good decisions. Ideally each bounded context would have data autonomy i.e. it’s own database (separate Ecto repo) so you wouldn’t be using Ecto joins any way.

programming-phoenix-1-4_b2_0 p.73:

Sometimes it may be tricky to determine if two resources belong to the same context or not. In those cases, prefer distinct contexts per resource and refactor later if necessary. Otherwise you can easily end-up with large contexts of loosely related entities. In other words: if you are unsure, you should prefer explicit modules (contexts) between resources.

but

Chris McCord warns about premature extraction - i.e. it’s all about tradeoffs

Also:

3 Likes

Since its just an association table you probably do not even need a Schema for it. As you’ve written it, none is required. So there is nothing to put in a context. If you ended up with attributes on that association, probably the nature of the attributes would tell you the appropriate context. I’d guess it belongs in the same context as Rooms though, as User will likely be related to entities all over the app.

That is done inside the schema files for the models you are trying to link. The context handles mostly controller logic like CRUD, which you will need to update to work with the new relation.

If you’re interested in a example, this is one of the better ones: https://alchemist.camp/episodes/unified-tagging-system

5 Likes