Additional attributes on join table for many-to-many relationship
I have an app that has courses and teachers. The relationship is many to many since each course can have many teachers and each teacher can have many courses. In the join table/resource I have an additional attribute called role (one can be primary or extra teacher for a course). I managed to connect the resources so that I can create a new relationship by calling:
TheSchoolApp.Courses.Teacher.update(teacher, %{courses: [%{id: course.id, role: "primary"}]})
The problem is that it doesn't fill in the role attribute. It just leaves it as nil.
The attributes and relationship for CourseTeacher is: attributes do uuid_primary_key(:id) attribute(:role, :string, allow_nil?: true) create_timestamp(:inserted_at) end relationships do belongs_to :teacher, TheSchoolApp.Courses.Teacher, primary_key?: true, allow_nil?: false belongs_to :course, TheSchoolApp.Courses.Course, primary_key?: true, allow_nil?: false end And the action for teacher is: update :update do argument :courses, {:array, :map} do allow_nil? false end change manage_relationship(:courses, type: :append_and_remove) end
The attributes and relationship for CourseTeacher is: attributes do uuid_primary_key(:id) attribute(:role, :string, allow_nil?: true) create_timestamp(:inserted_at) end relationships do belongs_to :teacher, TheSchoolApp.Courses.Teacher, primary_key?: true, allow_nil?: false belongs_to :course, TheSchoolApp.Courses.Course, primary_key?: true, allow_nil?: false end And the action for teacher is: update :update do argument :courses, {:array, :map} do allow_nil? false end change manage_relationship(:courses, type: :append_and_remove) end
14 Replies
I think, but am unsure, that the attributes you pass in here are only looked at as courses.
What you could do to debug this is look at the config for :append_and_remove which is the same as:
I guess In your case the on_lookup action is called. Which would be the default
create
action on the join resource. You could declare your own primary action in the resource and check what kind of values you get in the changeset, maybe you can then extrapolate from there.
Otherwise, you could just create an action on the join resource that takes the role and the course/teacher id and join them that way.
link to manage_relationship docs: https://www.ash-hq.org/docs/module/ash/latest/ash-changeset#function-manage_relationship-4
It does seems like ash removes the role attribute:
if I override with a manual create action:
There was some discussion in the last days about supporting additional attributes on
many_to_many
join resources, but at the moment I believe that it is not supported natively.
If a teacher will only ever be part of a course with one role, then you can add:
Then select directly from the join resource to find the teacher's role.Aha I see. Im trying to find that disc, do you know where it was? I guess I can do it manually like normal ecto if I know that it will be added in the future (because still a cost of using ash so wanna know that I actually save some time vs just using normal ecto etc).
Probably worth opening a new one anyways here or in #ideas since it's a useful topic.
The main problem is that the Ash concept of
many_to_many
resources intends to be as transparent as belongs_to
and has_one
, so it's primarily about deciding how to attach that additional information, or whether to support it at all.You can update the join attributes, you just have to use this rather explicit/verbose format
So you'd have to specify every option
Thanks Zach!
Hmm im probably missing something when i put this in the :update action in the teacher resource:
Then I get this complication errror:
I guess im putting the on_create in the wrong place somehow
ah, sorry
I just misspoke
on_no_match
That is just one of the options you'll need to specify to make this work though
Here is what type: :append_and_remove
is actually doing under the hood
aha okay i see. I will try it!
So to have
append_and_remove
behavior while setting a join table attribute, you probably want this.
Where :join_table_create_action
is a create action on the joining resource
and :lookup_action
is the read action on the destination resource
Actually, you don't have to specify the others since they don't change. So it could also be:
Thank you! It worked! Sorry for all the questions.
Not a problem 😄 Glad we got it sorted 🥳
Hopefully I can "pay it back" in the future by creating some content around ash 🙂 Got to learn myself first