NodeJS Sequelize - Multiple Ownership - node.js

In my application I have Users and Contacts. Users make ContactRequests to other Users. The ContactRequest has a requester and requestee field. I don't know how to go about this. I don't want a Users, ContactRequests, and ContactRequestsUsers table (as sequelize would if I defined a hasMany or hasOne on both Users and ContactRequests).
So let me start by defining some models-
var User = sequelize.define('User', {
id: {type: seq.INTEGER, primaryKey: true, autoIncrement: true},
username: {
type: sequelize.STRING(32)
}
});
var ContactRequest = sequelize.define('ContactRequest', {
id: {type: seq.INTEGER, primaryKey: true, autoIncrement: true}
});
Not sure what associations I should make. It seems like I should be able to do this several ways (in theory) but I can't get any of the ways I've tried to work.
// User Has Many?
User.hasMany(ContactRequest, {as: 'Requester', foreignKey: 'requester'});
User.hasMany(ContactRequest, {as: 'Requestee', foreignKey: 'requestee'});
// ContactRequest Belongs to?
ContactRequest.belongsTo(User, {});
// Experience Has One?
ContactRequest.hasOne(User, {as: 'Requester'});
ContactRequest.hasOne(User, {as: 'Requestee'});
With each variation I've tried different methods to checking to see if userA has made a ContactRequest to userB.
// Checking if a user has a contact request?
var userA = 'someuser1';
var userB = 'someuser2';
ContactRequest.find({where: {'Requester.username': userA, 'Requestee.username': userB}, include: [{model: User, as: 'Requester'}, {model: User, as: 'Requestee'}]}).success(function(cr) {
console.log(cr);
});
// Maybe by the user?
User.find({where: {username: userA}}).success(function(someuser) {
someuser.getRequestee({where: {'Requestee.username': userB}, include: [{model: User, as: 'Requestee'}]}).success(function(cr) {
console.log(cr);
});
});
Not sure where I'm going wrong but I can't get this to work. Any help or foresight would be appreciated.

Related

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!

Sequelize - How to get entries from one table that are not assiciated by another table

I used Sequelize to define three Entities for users rating posts:
var User = sequelize.define('User', {
email: {
type: Sequelize.STRING,
primaryKey: true
}
});
var Post = sequelize.define('Post', {
link: {
type: Sequelize.STRING,
primaryKey: true
}
});
var Rating = sequelize.define('Rating', {
result: Sequelize.STRING
});
Rating.belongsTo(Post);
Post.hasMany(Rating);
Rating.belongsTo(User);
User.hasMany(Rating);
A user can rate several posts. Each rating belongs to exactly one user and one post.
Now I'd like to query for a given user all posts that are not already rated by this user. I tried a thousand ways but without success. Any idea how to achieve this in Sequelize? Thanks a lot!
There are two methods either use raw query in Sequelize or via Sequelize query as well -
Raw -
return Db.sequelize.query("SELECT * FROM Post P WHERE (P.id NOT IN (SELECT postId FROM Ratings R WHERE R.userId="+userId+")) ",{ type: Sequelize.QueryTypes.SELECT });
Sequelize -
return Post.findAll({
where: {
Sequelize.literal("(posts.id NOT IN (SELECT R.postId FROM Rating R WHERE R.userId="+userId+"))")
}
});

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.

Creating instance with an association in Sequelize

Using Sequelize, I've created two models: User and Login.
Users can have more than one Login, but a login must have exactly one user, which means a Login cannot be saved without a User ID.
How do I .create a Login with a User association all in one swoop?
Current Code (Doesn't Work)
// Set up the models
var User = sequelize.define('User', {});
var Login = sequelize.define('Login', {});
Login.belongsTo(User, {
onDelete: 'cascade',
foreignKey: {
field: 'userId',
allowNull: false,
}
});
// Create the instances
var user = User.create().then(function() {
// THIS IS WHERE I WOULD LIKE TO SET THE ASSOCIATION
var login = Login.create({
userId: user.get('id')
});
)};
The above results in SequelizeValidationError: notNull Violation: UserId cannot be null
Assuming you have the right association between users and login,
you can just create a user including a login:
User.create({
name: "name",
Login: {...}
},{
include: Login
})
you can find more information here:
https://sequelize.org/docs/v6/advanced-association-concepts/creating-with-associations/
First of all you need to setup the relations in both ways, like this:
// Set up the models
var User = sequelize.define('User', {});
var Login = sequelize.define('Login', {});
// Set the correct associations
User.hasMany(Login, {})
Login.belongsTo(User, {});
Then, you need to properly get the instances returned by the promises:
// Create the instances
User.create({}).then(function(newUser) {
// now you can use newUser acessors to create the login
return newUser.createLogin({});
).then(function(newLogin){
// newLogin
}).catch(function(error){
// error
});
In your .then, the callback receives the model instance created by the previous call. You need to specify the argument inside the callback function.
var user = User.create().then(function(user) {
// THIS IS WHERE I WOULD LIKE TO SET THE ASSOCIATION
var login = Login.create({
userId: user.get('id')
});
return login
}).then(function(login) {
// all creation are complete. do something.
});
Also something important I would like to point out is your missing var statements! Those are important but not related to this question. See Declaring variables without var keyword
An Update to #Arwed Mett's answer
//Create Association Alias or just setting association alias by using 'as' keyword will also work
Login.User = Login.belongsTo(User);
User.create({
name: "name",
Login: {...}
}, {
include: [{
association: Login.User
}]
});
Refrence link - http://docs.sequelizejs.com/manual/tutorial/associations.html#creating-with-associations
For those like me who were trying to create an instance of a model including another instance, like:
var login1 = await Login.create(...);
var user1 = await User.create({
Login: login1
}, {
include: Login
});
You can't because this method is used to embed an instance (Login) which is not already existing and that will be created at the parent instance (User) creation level.
So, if you want to embed an already existing Login in the newly created User, do instead:
var login1 = await Login.create(...);
var user1 = await User.create({
loginId: login1.get('id')
}, {});
You have association between User an Login with constraint allowNull at false. You must create Login before User or set allowNull at true in model and the table to DB (LoginId Null constraint)
var User = sequelize.define('User', {});
var Login = sequelize.define('Login', {});
Login.belongsTo(User, {
onDelete: 'cascade',
foreignKey: {
field: 'userId',
allowNull: false,
}
});
Solution
Login.create({
username: "username",
User: {...}
},{
include: User
})
I have the same issue recently!
I have a typo mistake with the foreignKey config. Use field instead of name caused the issue.
The change below will fix it.
{
foreignKey: {
name: 'userId',
allowNull: false,
}
}
As an extra you could also nest your creation to be even more effective and concise.
// Set up the models
var User = sequelize.define('User', {});
var Login = sequelize.define('Login', {});
...
User.create({
name: "name",
Login:
{
users: {..i.e several users if a user belongs to another user..}
}
},{
include:{
model: Login,
include: User //nested model.Create
}
})
as seen here: https://github.com/sequelize/sequelize/issues/7252

Foreign keys with Sequelize are not created

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' })

Resources