Hello
I am trying to write a subject-follower relationship schema. I have this migration
defmodule TweetClone.Repo.Migrations.CreateUserRelationships do
use Ecto.Migration
def change do
create table(:user_relationships, primary_key: false) do
add :subject_id,
references(:users,
on_delete: :delete_all,
on_update: :update_all,
primary_key: true
)
add :follower_id,
references(:users,
on_delete: :delete_all,
on_update: :update_all,
primary_key: true
)
timestamps()
end
create unique_index(:user_relationships, [:subject_id, :follower_id])
end
end
and this schema
defmodule TweetClone.UserRelationships.UserRelationship do
use Ecto.Schema
import Ecto.Changeset
alias TweetClone.Accounts.User
@primary_key false
schema "user_relationships" do
belongs_to :follower, User, primary_key: true
belongs_to :subject, User, primary_key: true
timestamps()
end
@doc false
def changeset(user_relationship, %{subject: subject, follower: follower}) do
user_relationship
|> change
|> put_assoc(:subject, subject)
|> put_assoc(:follower, follower)
|> validate_required([:subject, :follower])
end
end
The intent is that UserRelationship not to have a auto generated id
but a composite made from the foreign keys subject_id
and follower_id
It works fine and when I want to delete that row I do
def delete_user_relationship(subject_id, follower_id) do
query =
from u in UserRelationship,
where: u.subject_id == ^subject_id,
where: u.follower_id == ^follower_id
Repo.delete_all(query)
end
and the row is deleted.
When I try do delete based on the changeset I use this function
def delete_user_relationship(subject, follower) do
attrs = %{
subject: subject,
follower: follower
}
%UserRelationship{}
|> UserRelationship.changeset(attrs)
|> Repo.delete()
end
iex(29)> UserRelationships.delete_user_relationship( user3, user1)
** (Ecto.NoPrimaryKeyValueError) struct `%TweetClone.UserRelationships.UserRelationship{__meta__: #Ecto.Schema.Metadata<:built, "user_relationships">, follower: #Ecto.Association.NotLoaded<association :follower is not loaded>, follower_id: nil, inserted_at: nil, subject: #Ecto.Association.NotLoaded<association :subject is not loaded>, subject_id: nil, updated_at: nil}` is missing primary key value
(ecto 3.4.2) lib/ecto/repo/schema.ex:903: anonymous fn/3 in Ecto.Repo.Schema.add_pk_filter!/2
(elixir 1.10.1) lib/enum.ex:2111: Enum."-reduce/3-lists^foldl/2-0-"/3
(ecto 3.4.2) lib/ecto/repo/schema.ex:427: anonymous fn/10 in Ecto.Repo.Schema.do_delete/4
So it complains that our changeset is missing a primary key.
I tried to change the UserRelationship
schema to pass only the user’s primary keys instead of the full users structs
@primary_key false
schema "user_relationships" do
field :subject_id, :integer, primary_key: true
field :follower_id, :integer, primary_key: true
belongs_to :follower, User
belongs_to :subject, User
timestamps()
end
but during compilation it complains that
== Compilation error in file lib/tweetclone/user_relationships/user_relationship.ex ==
** (ArgumentError) field/association :follower_id is already set on schema
(ecto 3.4.2) lib/ecto/schema.ex:2001: Ecto.Schema.put_struct_field/3
(ecto 3.4.2) lib/ecto/schema.ex:1758: Ecto.Schema.define_field/4
(ecto 3.4.2) lib/ecto/schema.ex:1841: Ecto.Schema.__belongs_to__/4
lib/tweetclone/user_relationships/user_relationship.ex:11: (module)
(stdlib 3.11.2) erl_eval.erl:680: :erl_eval.do_apply/6
(elixir 1.10.1) lib/kernel/parallel_compiler.ex:233: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/7
How can I fix the UserRelationship
schema so I can simply pass the users primary keys and be assigned as the composite primary key in the changeset? Or even passing the full User
structs and be able to delete with the changeset?