Singularize `as` in foreign keys in To-Many associations (amendment of PR #5957)
See original GitHub issuePR #5957 recently changed the names of foreign keys in associations where an as
alias is specified.
So now: Project.hasMany(User, {as: 'Workers'})
creates foreign key WorkersId
rather than UserId
.
This is great, but I do think the as
should be singularized, so foreign key is WorkerId
. This was already raised by @mickhansen in #5957, but I was too late to the party to get a comment in.
WorkersId
(plural), in my view, is not what the user would expect. It’s inconsistent with the naming when no as
alias is provided, and with the general Sequelize convention of singularizing/pluralizing. I can imagine it tripping people up quite often because it’s counter-intuitive.
Yes, using inflection
to singularize as
could in some few cases produce the wrong result, but in those cases the user can override it by specifying foreignKey
. Personally, I think that’s preferable to having to specify foreignKey
in all cases in order to maintain consistent naming.
I’d be happy to make a PR for this, if there’s agreement that this would be a good thing to do.
What do you think @mickhansen @janmeier @sushantdhiman?
Issue Analytics
- State:
- Created 7 years ago
- Reactions:1
- Comments:51 (51 by maintainers)
OK, just to rewind a bit, here’s my summary of discussion so far:
as
would be ideal if there was a foolproof way to do it.Now my personal view:
I think that inflection is accurate enough. It’s come a long way in past couple of years. It has 600,000 downloads a month, no open issues, and the maintainer is very quick to resolve issues that do arise. They now use Wiktionary as a reference, so questions of whether behavior is correct or not can be easily resolved. I think it’s safe to assume that its now working correctly for all but the most obscure words.
Personally (and you may think I’m mad) I don’t want to define
foreignKey
for every association. This is something I want (and expect) my ORM to do for me.To deal with the need for consistency I propose 3 things:
inflection
to latest before release of Sequelize v4 and pin it so it does not change until v5.Utils.inflection
rather than referring toinflection
directly. Then, if a user finds a bug in inflection that affects their app and they want to update it to a later version, they can useSequelize.Utils.inflection = require('inflection')
to inject a later version.The above would I think be the best route to do the “right” thing in 99% of cases and offer an easy, consistent way for users to deal with the 1%.
My god this has opened a whole can of worms!
I’ve looked into this further and here’s what I’ve found. (sorry that this is really long)
One-to-many
The foreign key, if it’s going to be based on
as
, needs to be based on the otheras
. i.e. theas
defined in the matchingbelongsTo
statement.So instead of
StateId
the foreign key becomesCapitalOfId
. This makes more sense!That’s actually what
City.belongsTo( State, { as: 'CapitalOf' } )
already does in Sequelize v3.However, if you define both
hasMany
andbelongsTo
as in the example above, Sequelize v3 doesn’t match the two statements as being two sides of the same association and you end up with two fields onCity
:StateId
andCapitalOfId
.The only way to link them together is to define the same
foreignKey
on both.Personally I don’t like this - I feel that the purpose of an ORM is to abstract away the database (including the column names) so I don’t feel that it’s good to be forced to used
foreignKey
to linkhasMany
andbelongsTo
statements.But there has to be some element in common between the 2 statements, otherwise Sequelize can’t possibly know which statements to link together with which. This is one reason why I think defining a two-way association with a single statement would be simpler (see #6091).
But anyway… I think:
as
for one-to-many was better than what we have on latest masterhasMany
Many-to-many
Sequelize v3 behavior
i.e. the
as
is not used at all for creating the keys. But it all works.If you define
foreignKey
, the behaviour is (to my eyes) a bit wonky:To make it work as expected, you have to switch the two
foreignKey
declarations:This seems to me counter-intuitive.
Current master
Current master uses
as
and createsMembersId
andMyGroupsId
. This has two problems:MyGroupsId
replacesPersonId
notGroupId
. So if you create a relationship between a Person “Bob” and a Group, you end up with Bob’s ID saved asMyGroupsId
where it should be inMembersId
There’s also an unrelated bug. If you don’t create a
Person
record for Frank, something even weirder happens - no rows get inserted into PersonGroup at all and no error is thrown. I’m guessing a foreign key error from the database is getting swallowed somewhere.Solution
We could either:
as
but switch theforeignKey
s round, oras
for both field names, so the fields becomeMemberId
andMyGroupId
(but the right way around)Concerning whether to use
as
, I’m not bothered either way. For one-to-one or one-to-many relationships, usingas
as the base for the foreign key is necessary because you can have multiple relationships between the same two entities.But with many-to-many relationships, each association creates a new through table, so there’s no possibility of two foreign keys being called the same thing in the same table.
So I kind of see using
as
as a solution to a problem that doesn’t really exist.However:
Many-to-many self-association
In a many-to-many _self-_association, providing
as
andthrough
is mandatory.Sequelize v3
In Sequelize v3, the behavior is broken:
belongsToMany
createsPersonParent
model with fieldsPersonId
andParentId
belongsToMany
deletes theParentId
field! So you only end up with a singlePersonId
field in the join table.To get it to work, you have to also specify
foreignKey
in both directions. But, again, the foreign keys have to be what seems to me to be the wrong way around:This works:
Current master
Foreign keys now don’t need to be provided.
ParentsId
andChildrenId
fields are created from theas
- good stuff. BUT the fields are the wrong way around.So I think for many-to-many self-associations, both Sequelize v3 and current master have it wrong.
Conclusions
All of the above is why I say that defining associations in Sequelize is confusing!
But one thing is for sure - the behavior on current master is wrong.
I see 3 possible ways forward:
as
as base for foreign key names). This would be a breaking change and I think will be confusing to old users who’ve got used to it, and will find changing over a head-scratcher.Thoughts?