The Logic of saving an object in the db with many-to-many relationship

Hi all,

I implemented a LiveView for editing a group. The screenshot show it. My idea was to give the use the possibility to copy from left select to the right select the users with the help of that button (“=>”) in the middle of the dialog. Therefore I created in the index.eex of the LiveView for groups a function for the event that would be fired if a user presses the button:

def handle_event("add_user_to_group", value, socket) do
    IO.write("add_user_to_group: ")
    #IO.inspect(socket.assigns)
    user = Accounts.get_user!(socket.assigns.selected_user)
    g = Accounts.load_all_details_for_group(socket.assigns.group)
    ng = Accounts.Group.add_user(g, user)
    #Accounts.update_group(g, %{user: ng})
    #Accounts.update_group(g, %{user_list: [1, 2, 3]})
    new_list = Accounts.userlist_to_selectformat_to_list(ng)
    {:noreply,
      socket
      |> assign(:user_in_this_group, ng)
      |> push_event("selectchange", %{selectchange: new_list})
    }
  end

To learn elixir and Phoenix, I created a lot of functions. Functions like user_add, that added a User to this group. They worked so far. You see that I fire back an event to JavaScript (to a hook) that add my User to this group. BUT! If I want to save the edited group into the database, I have no idea, how to realize this with the help of ecto. How to create a changeset? How to save? I have some helper-functions, the mix-tool created for me. You see for instance, that I implement also a function update_group. This function looks like:

def update_group(%Group{} = group, attrs) do
    IO.write("update_group: ")
    IO.write("inspecting the attrs")
    IO.inspect(attrs)
    group
    |> Group.changeset(attrs)
    |> Repo.update()
  end

This function changes the title (I the user wants that), but do not save the new added group members. The group is prepared for many-to-many:

defmodule Lmsphx.Accounts.Group do
  use Ecto.Schema
  import Ecto.Changeset
  import Ecto.Query
  alias Lmsphx.Accounts

  schema "group" do
    field :title, :string
    #has_many :user, Accounts.User

    many_to_many(:user, Accounts.User, join_through: Lmsphx.Accounts.UserGroup)

    timestamps()
  end
...

Please help me with a hint. I have problems with the logic of Phoenix/elixir. How can I realize the saving of this new added user.

regards,
Sven

I think Ecto.Changeset.put_assoc can help you with your problem.

But since your new to Ecto I would suggest reading Ectos Getting Started first, to learn about Schemas, Changesets, etc…

Dear moodle19,

yes you’re right. But I want to give an solution for all newbies with eco ;-).

My solution: First of all I have to say, that in my group-Template was the on_replace in the schema not set. But it seems to be very important. Without a “on_replace”, the database would not be updated. So I added:

schema "group" do
    field :title, :string
    #has_many :user, Accounts.User

    many_to_many(:user, Accounts.User, join_through: Lmsphx.Accounts.UserGroup, on_replace: :mark_as_invalid)

    timestamps()
  end

The next is to add the following in update_group:

def update_group(%Group{} = group, attrs) do
    group
    |> Repo.preload(:user)
    |> Group.changeset(attrs)
    |> Ecto.Changeset.put_assoc(:user, attrs.user)
    |> Repo.update()
  end

Now I can in handle_event call the update_group-function with: Accounts.update_group(g, %{user: ng})
ng holds the new group with the newly added member.

So thank you very much moogle19. Also for the link to the “Getting Started”. The use of iex helps very much for debugging and for finding the right data structures.

Have a nice evening,
Sven

1 Like