This issue is born of my work on the fix-record-keys branch in relation to issue #55.
tl;dr: Should many-to-many relationships be downplayed, even discouraged, in favor of one-to-many and many-to-one relationships?
Atlas supports a "many-to-many" type of relationship. For example, if you have Threads and Tags, then each Thread might have many Tags on it, and likewise each Tag might apply to many Threads. This is defined in the database using an association table ("Taggings") that maps the many Thread IDs to many Tag IDs, and modeled in Atlas with one Thread & Tag to many Taggings, and many Taggings to one Thread & Tag.
The end result of the many-to-many is to make it look like the Thread has Tags on it, but that's not actually the case. The Thread has Taggings, and the Taggings have the Tags. So the Tags are attached to the Thread only indirectly.
Now, what's interesting here is that automatically setting the native record keys on a many-to-one, or setting the foreign record keys on a one-to-one or one-to-many, is straightforward. But it seems like you never actually need to set anything on the "far" side of many-to-many relationships, since everything is handled as part of the "association" ("through") portion of the relationships.
Further, if you have a many-to-many relationship and you add a new record to it, you need to manage two related fields: the many-to-many record set, and the "association" record sets. That is, if you create a new Tag and want to attach it to a Thread, you need to do something like this:
$thread->taggings->appendNew([
'thread' => $thread,
'tag' => $tag,
]);
$thread->tags[] = $tag;
With all that in mind, it occurs to me that having an explicit many-to-many relationship is convenient, but might have been a misstep. Instead, perhaps the thing to do is downplay the existence of many-to-many, discourage its use, and emphasize the actual one-to-many and many-to-one relationships in their place.
For example, right now you would do this to get the Tags on a Thread:
$thread = $atlas->fetchRecord(ThreadMapper::CLASS, 1)
->with([
'taggings',
'tags',
]);
foreach ($thread->tags as $tag) {
echo $tag->name;
}
If the many-to-many relationship is downplayed, you would do this instead:
$thread = $atlas->fetchRecord(ThreadMapper::CLASS, 1)
->with([
'taggings' => [
'tag',
],
]);
foreach ($thread->taggings as $tagging) {
echo $tagging->tag->name;
}
A little more verbose, but very clear on what is related to what (i.e., that the Thread owns Taggings, and the Taggings own the Tags).
This would also mean managing only one related, not two, when attaching a new Tag:
$thread->taggings->appendNew([
'thread' => $thread,
'tag' => $tag,
]);
// no $thread->tags to deal with
This downplaying/discouraging of many-to-many does not require any code changes. It will be more of a documentation and suggested practices thing.
Users of Atlas, what are your thoughts on this?