Add related savings methods
This PR will adds an easier way to save related models, without having to pay attention to fill the id of the related model. It will take some time to be implemented, but I already submitted it as a draft-PR so we can discuss about the API.
API
// Automatically fill the bakery_id into the baker_bob Model and then insert it
seafront_bakery
.insert_related(
bakery::Relation::Baker,
baker_bob,
&ctx.db
)
.await?;
// Update the bakery_id column to change a one-to-n relationships
baker_bob
.set_related(
baker::Relation::Bakery,
seaside_bakery, // Bob changed his boss from seafront to seaside
&ctx.db
)
.await?;
// Automatically fill the bakery_id into both the mud_cake and cheese_cake Models
// and then insert them
seaside_bakery
.insert_many_related(
bakery::Relation::Cake,
vec![mud_cake, cheese_cake],
&ctx.db
)
.exec(&ctx.db)
.await?;
// Attach multiple many-to-many relations
// mud_cake and cheese_cake was already inserted in the database previously
baker_bob
.attach_related(
cakes_bakers::Relation::Cake,
vec![mud_cake, cheese_cake],
&ctx.db
)
.await?;
// Detach some of the many-to-many relationships
baker_bob
.detach_related(
cakes_bakers::Relation::Cake,
vec![mud_cake],
&ctx.db
)
.await?;
// Detach all related models, works whatever the relationship type is
baker_bob
.detach_all_related(
cakes_bakers::Relation::Cake,
&ctx.db
)
.await?;
The API seems good. I would probably change attach_related for set_many_related so that it's clearer what this function does.
So if I'm not wrong the different failure points would be:
-
insert_related: When the related records have already been saved into the database. -
set_related: When the related records have not been saved into the database. -
insert_many_related: Whenever one of the related records has already been saved into the database. -
attach_related: Whenever one of the related records has not been saved into the database previously. -
detach_related: If the related record does not exist in the database. -
detach_all_related: Never?
Maybe we could create a new method so that the record is automatically inserted if not saved already and then it updates the relation IDs. This method should be called set_related and set_may_related and the current set_related and set_many_related (attach_related) could be renamed to update_related and update_many_related.
Thoughts on this?
If I understand it correctly
-
insert_*will insert the related models in db and link the related models by setting the*_idforeign key columns correctly -
attach_*&detach_*will just update the*_idcolumns of related models without deleting it from db
Correct?
Yes, that is what I have understood from the tests @RomainMazB has made.
About the delete part that seems the most reasonable thing as in order to delete the related record from the database, we should need to check that it is not related to other models first.
Maybe @RomainMazB can clarify if we have understood this correctly?
Have no thoughts on the API for now, I guess we'd want more input from users!
Do have a thought in the implementation strategy. I don't think we should fiddle too much with the current ActiveModel.
We can try introduce one more layer of abstraction, a RelationManager or something that builds atop ActiveModel. Because as we went by, we'd probably created a mini GraphQL api in the end.
@RomainMazB pinged me on Discord about this, so I'll give my input fwiw.
I like @PauMAVA's suggestion on renaming attach_related to set_many_related. That makes sense. I'm not quite sure if the detach_related method works for all of one-to-one, one-to-many and many-to-many relations. If so, naming is good. If there are dedicated methods for one-to-* and many-to-many, this method too should be named differently. I'm assuming detach_related works on all types of relations though?
As for deletions, I'm thinking that in this context deletions are deletions of the relation, not of the related object. So if I have a many-to-many relation between bakeries and cakes, the fact that I removed the relation between a bakery and a cake does not mean I need to remove the cake. Removing the related object is probably something that should be done explicitly.
Something like that, anyway.
Finally, the suggestion to implement an additional abstraction layer to implement these relations, I'm not sure about that, if I may be so frank. I like my ORMs smart, powerful and simple. I use them for convenience reasons. Additional layers of abstraction might make an ORM smarter and more powerful, but not necessarily simpler.
When I started with programming, I used to use the ORMs that came with Django and Rails. Different times, for sure, but in terms of relation management, I like what they do. Simple, powerful API, implemented directly on the models themselves and smart.
I think the suggested API comes reasonably close to that, so I would be happy with this.
Anyway, I'm an amateur, so I might get this all wrong ;)