I’m trying to have a many to many relationship through an association table with an extra column.
I want to let users create a MyApp.Leagues.League
resource and on create they should also be automatically added to the MyApp.Leagues.User
association table with role: :admin
.
I want the admin to be the actor performing the create action.
I’m using the default Accounts.User
from ash_authentication.
I’ve tried a variety combinations of on_lookup
and on_match
. From the docs, I think I should be using on_lookup: :relate
because the Accounts.User
already exists, I’m creating a new League
and a new Leagues.User
so I don’t need to worry about missing records, I really just want to match an existing user.
However, it seems nothing is being inserted.
Here’s the code.
defmodule MyApp.Leagues.User do
use Ash.Resource,
otp_app: :my_app,
domain: MyApp.Leagues,
data_layer: AshPostgres.DataLayer
postgres do
table "league_users"
repo MyApp.Repo
end
actions do
defaults [:read, :create, :update]
end
attributes do
# simple enum with :admin | :user values
attribute :role, MyApp.Leagues.UserRole do
allow_nil? false
public? true
default :user
end
end
relationships do
belongs_to :league, MyApp.Leagues.League, primary_key?: true, allow_nil?: false
belongs_to :user, MyApp.Accounts.User, primary_key?: true, allow_nil?: false
end
end
defmodule MyApp.Leagues.League do
use Ash.Resource,
otp_app: :my_app,
domain: MyApp.Leagues,
data_layer: AshPostgres.DataLayer
postgres do
table "leagues"
repo MyApp.Repo
end
actions do
create :create do
accept [:name]
change fn changeset, ctx ->
Ash.Changeset.manage_relationship(
changeset,
:users,
[%{id: ctx.actor.id, role: :admin}],
on_lookup: :relate,
join_keys: [:role]
)
end
end
end
attributes do
uuid_primary_key :id
create_timestamp :created_at
update_timestamp :updated_at
attribute :name, :string do
allow_nil? false
public? true
end
end
relationships do
many_to_many :users, MyApp.Accounts.User do
through MyApp.Leagues.User
source_attribute_on_join_resource :league_id
destination_attribute_on_join_resource :user_id
end
end
end
These two posts in the forum are very similar, but the suggestions didn’t fully solve my issue.
- Simple manage_relationship with additional field to set
- Add extra attributes in Ash.Changeset.manage_relationship with many-to-many relationship
From there, I’ve added join_keys
which was missing
join_keys: [:role]
I’m a bit confused because this seems like it should be fairly straightforward.
EDIT: if it can be helpful in understanding more, I have logged the changeset and context in the change
Ash.Changeset<
domain: MyApp.Leagues,
action_type: :create,
action: :create,
attributes: %{name: "My league"},
relationships: %{},
errors: [],
data: %MyApp.Leagues.League{
users: #Ash.NotLoaded<:relationship, field: :users>,
users_join_assoc: #Ash.NotLoaded<:relationship, field: :users_join_assoc>,
__meta__: #Ecto.Schema.Metadata<:built, "leagues">,
id: nil,
created_at: nil,
updated_at: nil,
name: nil
},
valid?: true
>
%Ash.Resource.Change.Context{
actor: %MyApp.Accounts.User{
__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
id: "22c6a0e0-ac34-478a-a4e2-7717c7db14a0",
email: #Ash.CiString<"email1748014983857613000@example.com">,
confirmed_at: nil
},
tenant: nil,
authorize?: true,
tracer: nil,
bulk?: false
}
EDIT2: If I remove the role: :admin
this works, but obviously the inserted League.User
has the default role: :user
EDIT3: if I pass the ctx.actor
instead of a new map with id
and role
, the League.User
is inserted correctly, but with a default role: :user