Foreign keys with Sequelize are not created - node.js

I use Sequelize for my Server (with mysql dialect); in Sequelize's documentation is written that this:
var Task = this.sequelize.define('Task', { title: Sequelize.STRING })
, User = this.sequelize.define('User', { username: Sequelize.STRING })
User.hasMany(Task)
Task.belongsTo(User)
creates automatically foreign key references with constraints;
but for me this doesn't happen:
var Shop = sequelize.define('Shop', {
name: Sequelize.STRING,
address: Sequelize.STRING,
phone: Sequelize.STRING,
email: Sequelize.STRING,
percentage: Sequelize.FLOAT,
text: Sequelize.TEXT,
categories: Sequelize.TEXT,
start: Sequelize.DATE,
end: Sequelize.DATE
});
var Offer = sequelize.define('Offer', {
name: Sequelize.STRING,
deadline: Sequelize.DATE,
optionDuration: Sequelize.INTEGER
});
Shop.hasMany(Offer);
Offer.belongsTo(Shop);
This creates the two tables shops and offers, both of them with only "id" primary key
I also have some n:m associations like:
Group.hasMany(Accesslevel);
Accesslevel.hasMany(Group);
but also in this case, in the join table that Sequelize creates, there are no foreign key;
so if I delete for ex. an acccesslevel, than the corresponding records in the join table accesslevelsgroups are not deleted.
Does anybody know if I'm doing something wrong or missing something?
What I need is to create all the foreign keys for the associations and the possibility to specify the behaviour 'onDelete' and 'onUpdate' (cascade)
-- UPDATE
I've created a route for executing sync:
myServer.get('/sync', function (req, res) {
sequelize.sync({force: true}).success(function() {
console.log('sync done');
res.send(200, 'sync done');
}).error(function(error) {
console.log('there was a problem');
res.send(200, 'there was a problem');
});
});
So then in the browser I type 127.0.0.1:port/sync to create the db structure

Did you add the relation after creating the table ( in the database ) ?
You need .sync({ force: true }) if you modify your scheme's and run your program again.
Sequelize will only create the table and all it's references if the table does not exist yet in the database.
Are you calling sync after all associations have been made ?
Are you using InnoDB ?
Oddly enough I can reproduce that, The easiest fix is to define the key manually I think, that's how I solved it, Might be a bug otherwise I'm not sure.
See here:
http://sequelizejs.com/docs/latest/associations#block-3-line-24

You should give foreign keys like that, it is not related to sync.
Offer.associate = function (models) {
models. Offer.belongsTo(models. Offer, {
onDelete: "CASCADE",
foreignKey: 'shopId',
targetKey: 'id'
});
};

This question was actually answered here
As trivial answers are converted to comments and it is hard to notice them under question I'll duplicate its main point here.
To add references constraints to the columns, you can pass the options onUpdate and onDelete to the association calls. . . .
User.hasMany(Task, { onDelete: 'SET NULL', onUpdate: 'CASCADE' })

Related

How can I add an additional column to the belongsToMany Association using Sequelize

I am using Node, Express, Postgres and Sequelize
Hi I am using using Sequelize I am successful in creating the through table with the belongsToMany for my Postgres database. But I want to add an additional column to it but cant figure out how to do it and I have searched the web and found no answer.
I got these two tables:
Customers
Store Products
This here below is my Sequelize method of creating the third table using the belongsToMany association (many-to-many) and I will be calling the table order_list.
customer.belongsToMany(storeProduct, {
through: 'order_list',
unique: false,
foreignKey: 'customers_cid',
// How can I add this additional column quantity to it?
quantity: { /// How do I add it?
type: DataTypes.INTEGER
}
});
storeProduct.belongsToMany(customer, {
through: 'order_list',
unique: false,
foreignKey: 'store_products_spid',
// How can I add this additional column quantity to it?
quantity: { /// How do I add it?
type: DataTypes.INTEGER
}
});
sequelize.sync({ alter: true });
So how can I successfully add the column I quantity with type of INTEGER to the this new order_list table?
Thanks in advance!
You need to define OrderList model explicitly with additional column and then use it in belongsToMany in the option through.
See Advanced Many-To_many guide in the official documentation.
Example:
const User_Profile = sequelize.define('User_Profile', {
selfGranted: DataTypes.BOOLEAN
}, { timestamps: false });
User.belongsToMany(Profile, { through: User_Profile });
Profile.belongsToMany(User, { through: User_Profile });

Sequelize magic method not found using Express and Node.JS

I'm coding an application using Sequelize, Express and Node.JS; after set a OneToMany relationship between User and Product, just like this:
app.js:
Product.belongsTo(User, { constraints: true, onDelete: 'CASCADE' });
User.hasMany(Product);
and from a controller called admin.js I'm trying to create a new Product using a magic method:
admin.js:
exports.postAddProduct = (req, res, next) => {
const title = req.body.title;
const imageUrl = req.body.imageUrl;
const price = req.body.price;
const description = req.body.description;
req.user.createProduct({
title: title,
imageUrl: imageUrl,
price: price,
description: description
})
.then((result) => {
console.log("Record successfully created");
return res.redirect("/admin/products");
})
.catch((err) => {
console.log(err);
});
};
postAddProduct is triggered after submit a form, the error I'm getting is this:
So, my question is: based on sequelize's official documentation, after define a relationship I can use methods for create, edit o search an entity, what am I missing to get access to these methods?
thanks for your comments
even though my model is called Product, the table's name is newproducts, so, in order to solve this I made this change:
req.user.createNewproduct({
title: title,
imageUrl: imageUrl,
price: price,
description: description })
After this, problem solved
Nice to see someone else taking Max's Node.js course!
I had this same problem. For me, it stemmed from the fact that I defined how my id column works differently. Instead of this...
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true
},
...I did this...
id: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4,
allowNull: false,
primaryKey: true,
},
and utilized the uuid package to make unique Product and User IDs. Because of this, I had to update how I defined my SQL relations in app.js:
Product.belongsTo(User, { constraints: true, onDelete: "CASCADE" });
User.hasMany(Product, { foreignKey: "id" });
So if you're using a custom ID generator or something, make sure to specify that you are in the options object and the function should appear!
You can read more about this in the docs: https://sequelize.org/docs/v6/core-concepts/assocs/#defining-the-sequelize-associations
Hope this helps!

How to return the result set of sequelize query in camel case fashion?

I have defined a table schema in the database in an underscored fashion but I want to return the result set API response in camel case fashion. I know I can process the underscored object returned by sequelize and convert it into camelcase fashion. Is there any functionality to return the response of a query in camelcase fashion in sequelize itself?
To archieve this you need to use field when defining your model.
module.exports = (sequelize, DataTypes) => {
const yourTable = sequelize.define('yourTable', { // table name use it for Sequelize
camelCase: { //camelCase name that you'll use with sequelize.
field: 'under_score', //underscore name on yor database.
type: DataTypes.STRING
},
keyId: { //need to the same with association
field: 'key_id',
type: DataTypes.INTEGER
},
}, {
tableName: 'your_table', // then name of the table on the db
underscored: true,
});
yourTable.associate = (models) => {
yourTable.belongsTo(models.otherTable, {
as: 'Something',
foreignKey: 'key_id', //put attention here and keyId above.
onDelete: 'cascade'
});
}
}

How do I reference an association when creating a row in sequelize without assuming the foreign key column name?

I have the following code:
#!/usr/bin/env node
'use strict';
var Sequelize = require('sequelize');
var sequelize = new Sequelize('sqlite:file.sqlite');
var User = sequelize.define('User', { email: Sequelize.STRING});
var Thing = sequelize.define('Thing', { name: Sequelize.STRING});
Thing.belongsTo(User);
sequelize.sync({force: true}).then(function () {
return User.create({email: 'asdf#example.org'});
}).then(function (user) {
return Thing.create({
name: 'A thing',
User: user
}, {
include: [User]
});
}).then(function (thing) {
return Thing.findOne({where: {id: thing.id}, include: [User]});
}).then(function (thing) {
console.log(JSON.stringify(thing));
});
I get the following output:
ohnobinki#gibby ~/public_html/turbocase1 $ ./sqltest.js
Executing (default): INSERT INTO `Users` (`id`,`email`,`updatedAt`,`createdAt`) VALUES (NULL,'asdf#example.org','2015-12-03 06:11:36.904 +00:00','2015-12-03 06:11:36.904 +00:00');
Executing (default): INSERT INTO `Users` (`id`,`email`,`createdAt`,`updatedAt`) VALUES (1,'asdf#example.org','2015-12-03 06:11:36.904 +00:00','2015-12-03 06:11:37.022 +00:00');
Unhandled rejection SequelizeUniqueConstraintError: Validation error
at Query.formatError (/home/ohnobinki/public_html/turbocase1/node_modules/sequelize/lib/dialects/sqlite/query.js:231:14)
at Statement.<anonymous> (/home/ohnobinki/public_html/turbocase1/node_modules/sequelize/lib/dialects/sqlite/query.js:47:29)
at Statement.replacement (/home/ohnobinki/public_html/turbocase1/node_modules/sqlite3/lib/trace.js:20:31)
It seems that specifying {include: [User]} instructs Sequelize to create a new User instance matching the contents of user. That is not my goal. In fact, I find it hard to believe that such behaviour would ever be useful—I at least have no use for it. I want to be able to have a long-living User record in the database and at arbitrary times create new Things which refer to the User. In my shown example, I wait for the User to be created, but in actual code it would likely have been freshly loaded through User.findOne().
I have seen other questions and answers say that I have to explicitly specify the implicitly-created UserId column in my Thing.create() call. When Sequelize provides an API like Thing.belongsTo(User), I shouldn’t have to be aware of the fact that a Thing.UserId field is created. So what is the clean API-respecting way of creating a new Thing which refers to a particular User without having to guess the name of the UserId field? When I load a Thing and specify {include: [User]}, I access the loaded user through the thing.User property. I don’t think I’m supposed to know about or try to access a thing.UserId field. In my Thing.belongsTo(User) call, I never specify UserId, I just treat that like an implementation detail I shouldn’t care about. How can I continue to avoid caring about that implementation detail when creating a Thing?
The Thing.create() call that works but looks wrong to me:
Thing.create({
name: 'A thing',
UserId: user.id
});
Option 1 - risks DB inconsistency
Sequelize dynamically generates methods for setting associations on instances, e.g. thing.setUser(user);. In your use case:
sequelize.sync({force: true})
.then(function () {
return Promise.all([
User.create({email: 'asdf#example.org'}),
Thing.create({name: 'A thing'})
]);
})
.spread(function(user, thing) {
return thing.setUser(user);
})
.then(function(thing) {
console.log(JSON.stringify(thing));
});
Option 2 - does not work/buggy
It isn't documented, but from a code dive I think the following should work. It doesn't but that seems to be because of a couple of bugs:
// ...
.then(function () {
return models.User.create({email: 'asdf#example.org'});
})
.then(function(user) {
// Fails with SequelizeUniqueConstraintError - the User instance inherits isNewRecord from the Thing instance, but it has already been saved
return models.Thing.create({
name: 'thingthing',
User: user
}, {
include: [{
model: models.User
}],
fields: ['name'] // seems nec to specify all non-included fields because of line 277 in instance.js - another bug?
});
})
Replacing models.User.create with models.User.build doesn't work because the built but not saved instance's primary key is null. Instance#_setInclude ignores the instance if its primary key is null.
Option 3
Wrapping the Thing's create in a transaction prevents an inconsistent state.
sq.sync({ force: true })
.then(models.User.create.bind(models.User, { email: 'asdf#example.org' }))
.then(function(user) {
return sq.transaction(function(tr) {
return models.Thing.create({name: 'A thing'})
.then(function(thing) { return thing.setUser(user); });
});
})
.then(print_result.bind(null, 'Thing with User...'))
.catch(swallow_rejected_promise.bind(null, 'main promise chain'))
.finally(function() {
return sq.close();
});
I have uploaded a script demo'ing option 2 and option 3 here
Tested on sequelize#6.5.1 sqlite3#5.0.2 I can use User.associations.Comments.foreignKey as in:
const Comment = sequelize.define('Comment', {
body: { type: DataTypes.STRING },
});
const User = sequelize.define('User', {
name: { type: DataTypes.STRING },
});
User.hasMany(Comment)
Comment.belongsTo(User)
console.dir(User);
await sequelize.sync({force: true});
const u0 = await User.create({name: 'u0'})
const u1 = await User.create({name: 'u1'})
await Comment.create({body: 'u0c0', [User.associations.Comments.foreignKey]: u0.id});
The association is also returned during creation, so you could also:
const Comments = User.hasMany(Comment)
await Comment.create({body: 'u0c0', [Comments.foreignKey]: u0.id});
and on many-to-many through tables you get foreignKey and otherKey for the second foreign key.
User.associations.Comments.foreignKey contains the foreignKey UserId.
Or analogously with aliases:
User.hasMany(Post, {as: 'authoredPosts', foreignKey: 'authorId'});
Post.belongsTo(User, {as: 'author', foreignKey: 'authorId'});
User.hasMany(Post, {as: 'reviewedPosts', foreignKey: 'reviewerId'});
Post.belongsTo(User, {as: 'reviewer', foreignKey: 'reviewerId'});
await sequelize.sync({force: true});
// Create data.
const users = await User.bulkCreate([
{name: 'user0'},
{name: 'user1'},
])
const posts = await Post.bulkCreate([
{body: 'body00', authorId: users[0].id, reviewerId: users[0].id},
{body: 'body01', [User.associations.authoredPosts.foreignKey]: users[0].id,
[User.associations.reviewedPosts.foreignKey]: users[1].id},
])
But that syntax is so long that I'm tempted to just hardcode the keys everywhere.

How to prevent Sequelize from inserting NULL for primary keys with Postgres

I have created a table in postgresql 9
create table stillbirth(id serial primary key, state varchar(100), count int not null, year int not null);
trying to write a sample on node.js with sequelize 1.4.1 version.
mapped the above table as
var StillBirth = sequelize.define('stillbirth',
{ id: {type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true},
state: Sequelize.STRING,
year: Sequelize.INTEGER,
count: Sequelize.INTEGER
}, {timestamps: false, freezeTableName: true});
now when i try to create a new instance of Stillbirth and save it, i get errors.
/** new instance create code **/
StillBirth
.build({state: objs[j].state, year: objs[j].year, count: objs[j].count})
.save()
.error(function(row){
console.log('could not save the row ' + JSON.stringify(row));
})
.success(function(row){
console.log('successfully saved ' + JSON.stringify(row));
})
error i get
*Executing: INSERT INTO "stillbirth" ("state","year","count","id") VALUES ('Andhra Pradesh',2004,11,NULL) RETURNING ;
could not save the row {"length":110,"name":"error","severity":"ERROR","code":"23502","file":"execMain.c","line":"1359","routine":"ExecConstraints"}
If you look at the sql that its generating, it puts null for the primary key which should ideally be generated by the db.
Can someone help me as to what am i missing here ?
You have to instantiate Sequelize with a special flag called omitNull:
var sequelize = new Sequelize('db', 'user', 'pw', {
omitNull: true
})
This will disable inserting undefined values as NULL. http://sequelizejs.com/#usage-options
You might need to update to v1.5.x or 1.6.0-betaX
To expand on the answer from sdepold, as he recommended, you can omitNull to prevent sequelize from adding null values to the generated SQL. In general, this is good, and it also allows you to perform partial updates.
var sequelize = new Sequelize('db', 'user', 'pw', {
omitNull: true
})
There is one caveat, though. How do you set a column to null if that's legitimately what you want to do?? The answer is that you can pass omitNull as part of your save.
user.address = null;
user.save({omitNull: false});
OR
user.update({address: null}, {omitNull: false});
It is also possible to define which attributes can be set via the create method. Using that would for example allow you to restrict the User model to set only a username and an address but not an admin flag:
User.create({ username: 'barfooz', isAdmin: true }, { fields: [ 'username' ] }).then(user => {
// let's assume the default of isAdmin is false:
console.log(user.get({
plain: true
})) // => { username: 'barfooz', isAdmin: false }
})
There is a workaround this without using omitNull.
Just do this:
StillBirth
.build({state: objs[j].state, year: objs[j].year, count: objs[j].count})
.save(['state','year','count'])
.error(function(row){
console.log('could not save the row ' + JSON.stringify(row));
})
.success(function(row){
console.log('successfully saved ' + JSON.stringify(row));
})
By sending an array of properties as parameter for save method you force sequelize to insert only the properties of that array omiting the id, leaving to the DB to auto create the id for you. =)

Resources