getting the follwerList from the through table (sequelize) - node.js

Currently making a sns project.
Have a user model and made a N:M association which tells you who is following who.
So there is a connected models between 'user' and 'user'.
This is how my code looks like
static associate(db) {
db.User.hasMany(db.Post);
db.User.belongsToMany(db.User, {
foreignKey: 'followingId',
as: 'Followers',
through: 'Follow',
});
db.User.belongsToMany(db.User, {
foreignKey: 'followerId',
as: 'Followings',
through: 'Follow',
});
}
and I'm trying to show how many followers and following the user has at the profile page.
So what I did is when the /main pass
const User = require('../models/user');
router.use((req, res, next) => {
res.locals.followingList = User.findAll({
where : {followerId : req.user}
});
next();
});
Having a problem accessing the data from through table.
Having a problem accessing the data from through table.

If we want to get the user along with followings and followers then you need to use findOne (or findByPk if req.user is a primary key value) because we want a single user and just include both associations in the query though I don't recommend to include more than one M:N associations to the same query:
res.locals.followingList = await User.findOne({
where : {id : req.user},
include: [{
model: User,
as: 'Followers'
}, {
model: User,
as: 'Followings'
}]
});

Related

How to find data of source table from target table in sequelize?

I have two tables, traders, and messages
Traders is associated to messages as following
traders.hasMany(models.messages, {as: 'sender',foreignKey : 'senderId'});
traders.hasMany(models.messages, {as: 'reciever',foreignKey : 'recieverId'});
now when I try to find trader name along with all messages using following code
ctx.body = await ctx.db.messages.findAll({
include:[{
model: ctx.db.traders,
as:'sender'
}],
attributes:['type',['data','message'],'createdAt','senderId','name'],
where:{
conversationId:ctx.request.body.conversationId
}
})
I get the following error
SequelizeEagerLoadingError: traders is not associated to messages!
Try association from both models like,
traders.hasMany(models.messages, {
as: 'sender',
foreignKey: 'senderId'
});
messages.belongsTo(models.traders, {
as: 'sender',
foreignKey: 'senderId'
});

how to have 1:m association in Sequelize?

I have two models as following:
const User = sequlize.define("user", {...})
const Project = sequelize.define("project". {...})
And I want a 1:M relation between them such that a Project can have multiple users but a User can have a single project.
I tried the following code:
User.hasMany(Project, { as: "project" })
but hasMany adds foreign key to Project table instead of User table. I know its possible to do it the other way around i.e:
Project.hasMany(User, { as: "project" })
but if I do that then when I use the following code it throws no association error.
User.findOne({ where: { id: "username", password: "password"},
include: [{ model: Project, as: 'project', required: true }]
})

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.

Should one-to-one associations populate both sides?

I have two models (user & agent). I then create a user and an agent. I am expecting to see the association when using the blueprint routes for BOTH /user and /agent. I am only seeing the user model is associated with an agent via the /agent blueprint. The /user blueprint does not have any reference/association to an Agent.
The issue presents itself when I am trying to access the Agent via A userId with the following command:
User.findOne(req.body.userId).populate('agent').exec(function(err, agent)
"agent" is in fact the user information...not the agent.
Here are my models:
User:
attributes: {
agent: {
model: 'agent',
via: 'owner'
}
}
Agent:
attributes: {
owner: {
model: 'user'
}
}
Thanks for reading!
Sails does not fully support one-to-one model associations--you have to set the "foreign key" on both sides. See https://stackoverflow.com/a/27752329/345484 for more information. I'm tempted to just close this question as a duplicate of that one, but the setup is slightly different.
You have understood the populate wrong. populate() doesn't replace last call with new information. It takes attribute from model (that you specify in populate('attribute')) and replace id of that attribute in your model with another model information looked up by its id.
Let's dive into example.
You have User and Agent model.
// api/models/User.js
module.exports = {
attributes: {
agent: {
model: 'Agent',
via: 'owner'
}
}
};
// api/models/Agent.js
module.exports = {
attributes: {
owner: {
model: 'User',
via: 'agent'
}
}
};
You are calling User.findOne(userId).populate('agent').exec(function(err, user) {} and expect to get only agent as I understood. Wrong. It returns User model with Agent model as an attributes of User model.
// api/controllers/AnyController.js
module.exports = {
index: function(req, res) {
User
.findOne(req.param('userId'))
.populate('agent')
.then(function(user) {
console.log(user); // User information
console.log(user.agent); // Agent information for that user
return user;
})
.then(res.ok)
.catch(res.negotiate);
}
};
You can read about population more here - http://mongoosejs.com/docs/populate.html

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

Resources