sequelize: unique validation does not work (as described in documentation)
What I’m doing
I have a simple User model. I want to make sure its emails are unique. The documentation claims that this is possible: http://docs.sequelizejs.com/manual/tutorial/models-definition.html
// The unique property is simply a shorthand to create a unique constraint. someUnique: { type: Sequelize.STRING, unique: true }
Here is my model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
name: DataTypes.STRING,
email: {
type: DataTypes.STRING,
validate: { isEmail: true },
unique: true
},
}, {});
return User;
};
What do you expect to happen?
I expect to not be able to save the same email twice.
What is actually happening?
This test fails:
it('User model invalidates email duplicates', async () => {
const uniqueEmail = "john.smith@email.com";
await User.create({ email: uniqueEmail });
return User.create({ email: uniqueEmail })
.then(() => { assert.fail("duplicate email accepted") });
});
Output:
1) User model
invalidates
email duplicates:
AssertionError: duplicate email accepted
Dialect: postgres Dialect version: 10 Database version: 10 Sequelize version: 4.42.0 Tested with latest release: No (If yes, specify that version)
About this issue
- Original URL
- State: open
- Created 5 years ago
- Reactions: 4
- Comments: 25 (6 by maintainers)
Commits related to this issue
- holy shit https://github.com/sequelize/sequelize/issues/10360 — committed to lesion/gancio by lesion 4 years ago
You’re welomce 😃
I agree that the documentation could be clearer on this. Sequelize is the only ORM I’ve ever used, so it is completely possible that you’re right here on what a Model should or should not do. But to me (and again I might be biased by lack of experience with other ORMs), your “automatic assumption” seems a bit extreme. Taking your own code, for example:
Surely the
type: DataTypes.STRING
part is directly associated with the database itself, because it specifies the type of the column of the SQL table. That said, I don’t see why theunique: true
couldn’t similarly be related to the schema definition itself (just my 2 cents).But again I agree that a documentation improvement would be good. So don’t close the issue yet even if I convinced you.
By the way, if for some reason, in your specific situation, you can’t modify the database itself to add the unique constraint, you could create a workaround by defining a custom validator in your model that performs a getAll query and checks the results to guarantee uniqueness (if I’m not terribly mistaken, async validators are supported, so that should work).
Also, beyond the improvements in the documentation, this issue could become a ‘feature request’ for a different type of unique constraint, one that happens on the ORM layer by means of an extra query…
This issue has not been resolved.
The current way to fix this is by adding indices at the model and migration levels, and also to drop the database entirely and re-create it before running the migration – this was at least the case for me, undoing and applying the migrations was not correctly adding the constraint in the SQL table definition.
To clarify, here is what worked for me:
In the model definition:
In the migration file:
I find it absurd that
unique: true
does not already apply this constraint to the table definition, unless one callssync()
which should never be used except for testing purposes, for reasons stated above.to conclude for does coming here looking for a code solution here is a link to the docs with a code example findOrCreate
Thank you again. I have implemented a custom validator at the column level that asynchronously checks the database for existing email records with a different user ID, so my particular workaround exists. (I hope to generalize that to any abstract model though I need to learn my way around the internal of the instance variable first).
Your comments on “automatic assumption” are fair, so I thought of laying out as an argument that’s logical rather than (as in Rails) opinionated by tradition:
I’d argue that if models had uniqueness validators (and I think evolution will select for this), the current “unique: true” concept will only add confusion without any big benefits (point 9), therefore we’re better off trading it in.
Thanks for reading my 2c 😃
On Thu, Jan 17, 2019 at 5:38 PM Pedro Augusto de Paula Barbosa < notifications@github.com> wrote:
Hey everyone, I had the same issue and solved it by doing a migration like this:
@papb I totally agree with @aminariana I really don’t understand the current behavior of the unique property why not implement a simple
prefix to the create query in order to achieve this sure there is a performance consideration
but I think an ORM should prioritize
ease of use
andlong term maintainability
if performance was my and most ORM users first consideration we would design our tables and write queries manually
also performing two queries to achieve this simple validation is a lot worst from a performance standpoint
and lastly this can also be achieved by indexing the email field but again indexing to perform validation to a field you are not planning to query by is just weird
I’m working with a PostgreSQL database. This is how my table is described now, after applying the changes mentioned in the previous comment:
I remove those snippets from my model and migration files, i.e., go back to my initial problem state, drop the database and recreate it (
db:drop
followed bydb:create
followed bydb:migrate
), and this is how it is now described:As can be seen, the
email
column is no longer unique.Note: I ran
db:migrate:undo:all
anddb:migrate
to revert back to the correct behavior and it does work. So my earlier note on having to drop the database entirely was incorrect; it wasn’t working for me for whatever reason, previously, but I was likely doing something wrong.In any case, the unique constraint in
createTable
is not applying the constraint. This is the relevant snippet of mycreateTable
function:The same problem, the unique constraint doesn’t work as expected on Model.init().
Update: When Sequelize is used to generate the tables, the unique constraint in the model definition works as expected using the technique in the Unique Constraint section of the docs.
But if you have pre-existing tables not generated by Sequelize, this does not appear to work by itself, even when using
.sync()
.