BenFenner
How to remove/delete a many-to-many relationhip?
I’m relatively new to Elixir/Phoenix (I have much more familiarity with RoR) but I’ve made it quite far in my new application by following tutorials and the documentation.
I am building a sort of one-stop shop for user accounts, where users can be created, and given permissions to one or more web sites (AKA resources) that I work on. Users can have many resources they belong to, and resources have many users that belong to it, so I went with a many-to-many relationship.
I created the user_resources table in the database as a full table with an auto-generated ID. Since a user can’t belong to a resource twice (or vice versa), I’ve created what I believe is a good unique index.
defmodule MyApp.Repo.Migrations.CreateUserResources do
use Ecto.Migration
def change do
create table(:user_resources) do
add :user_id, references(:users)
add :resource_id, references(:resources)
add :inserted_by, :integer
add :updated_by, :integer
timestamps()
end
create unique_index(:user_resources, [:user_id, :resource_id])
end
end
I also set up a schema for this relationship. I much prefer my relationships to be as fully fleshed out as possible, so that’s what I’ve done here.
defmodule MyApp.Account.UserResource do
use Ecto.Schema
import Ecto.Changeset
schema "user_resources" do
field :resource_id, :integer
field :user_id, :integer
field :inserted_by, :integer
field :updated_by, :integer
timestamps()
end
@doc false
def changeset(user_resource, attrs) do
user_resource
|> cast(attrs, [:user_id, :resource_id, :inserted_by, :updated_by])
|> validate_required([:user_id, :resource_id, :inserted_by, :updated_by])
end
end
I can successfully add a User to a Resource in my controller like this:
def add_user(conn, %{"resource_id" => resource_id, "user_id" => user_id}) do
resource = Domain.get_resource!(resource_id)
user = Account.get_user!(user_id)
Domain.add_user(resource, user)
conn
|> put_flash(:info, "User added to resource successfully.")
|> redirect(to: resource_path(conn, :show, resource_id))
end
Which calls add_user in the Domain Context, which does the real work:
def add_user(%Resource{} = resource, user) do
resource
|> Repo.preload(:users)
|> Ecto.Changeset.change()
|> Ecto.Changeset.put_assoc(:users, [user])
|> Repo.update!
end
This gives me a user_resources record in the database, and with the User in an Account Context, I have this function which gives me back a list of the users associated with the given resource:
def list_resource_users(resource) do
Repo.all(assoc(resource, :users))
end
So far, so good.
Now I want to remove the association, so I’m following the advice of this post: Many-to-many associations in phoenix and ecto - #20 by idi527
That part of my resource controller looks like this:
def remove_user(conn, %{"resource_id" => resource_id, "user_id" => user_id}) do
Domain.remove_user(resource_id, user_id)
conn
|> put_flash(:info, "User removed from resource successfully.")
|> redirect(to: resource_path(conn, :show, resource_id))
end
And the function defined in the Domain Context looks like this:
def remove_user(resource_id, user_id) do
"user_resources"
|> where(resource_id: ^resource_id)
|> where(user_id: ^user_id)
|> Repo.delete()
end
However, when I initiate that code, I get this error:
function Ecto.Query.__changeset__/0 is undefined or private
I have tried many different things, and gotten a few small improvements, but I think there must be something very simple I’m missing that might stand out to someone more experienced?
I appreciate any help I can get, and I’ll try to provide more info as I work on things.
Most Liked
kokolegorille
mikemccall
As @kokolegorille said put_assoc will set the users to whatever is in the list. ie. [user]. Or in other words remove any users not in that list. You will need to include all of the users when using put_assoc or manage the association separately for single records.
Something along the lines of:
resource = Repo.preload(resource, :users)
resource
|> Ecto.Changeset.put_assoc(:users, [user | resource.users])
This function should be used when working with the entire association at once (and not a single element of a many-style association) and using data internal to the application.
put_assoc/4
mikemccall
Repo.delete/2 expects a changeset_or_struct as the first argument. It looks like you’re piping an #Ecto.Query ( ie.queryable) into it. I believe delete_all/2 is what you’re looking for.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #hex









