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
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.