The many ways to reconcile many_to_many

Over all I have a working example, I’m not sure if I did a good job at what I’m trying to go after. This is more an ask for opinions than a how to do.

My use case is.

I want to associate clients to other clients. Currently I’m using client_groups as a way to allow for multiple associations for a given client. Note, while I have this set up to allow for N client_groups (“Because I don’t know what the my client wants yet”) for the moment I am assuming there will only ever be one if at all thus why I’m only ever using the head of the client_group list.

So one major task for my system is to show all the other associated clients for given client (“See get_other_clients below”)

defmodule MyApp.Organization.ClientGroup do
  ...

  schema "client_groups" do
    many_to_many(
      :clients,
      MyApp.Intake.Client,
      join_through: "clients_client_groups",
      on_replace: :delete,
      on_delete: :delete_all
    )
  end
 ...
end

Clients

defmodule MyApp.Intake.Client do
  ...
  schema "clients" do
    many_to_many(
      :groups,
      MyApp.Organization.ClientGroup,
      join_through: "clients_client_groups",
      on_replace: :delete,
      on_delete: :delete_all
    )

    timestamps()
  end
  ...
end

In my intake context I have a method for looking up other clients related to a given client.

  def get_other_clients(%Client{} = c) do
    client =
      c
      |> Repo.preload([:groups, groups: :clients])

    group =
      client.groups
      |> List.first()

    case group do

      nil -> # Incase there is no group
        []

      _ ->
        group.clients
        |> List.delete(c) 
       # Note I had pulled client from the params (c) because preloading will end up changing the struct. 
       # Thus why I'm not using client vs c
    end
  end

So here are my questions.

First: How can I do get_other_clients better?
Second: Is there a better pattern over all to achieve this same effect?

So far this is my next refactor.

  def get_other_clients(%Client{groups: %Ecto.Association.NotLoaded{}} = client) do
    client
    |> Repo.preload(:groups)
    |> get_other_clients
  end

  def get_other_clients(%Client{groups: []}), do: []

  def get_other_clients(%Client{groups: [group | _t]} = client) do
    group =
      group
      |> Repo.preload([:clients, clients: :groups])

    group.clients
    |> List.delete(client)
  end