Sequelize orm using Association methods - ecmascript-6

Sequelize orm using Association methods
I'm working on a API using Node and Sequelize ORM. The database being used is MYSQL. I've used belongsTo() in below code.
Each user have favorite games. Get the favorite games for each user using user_Id.
I got a sequelize result and but excepting another.
this.userFavoriteGameModel = require("../entity/favorite_game")(this.database, this.Sequelize);
this.gameModel = require("../entity/game")(this.database, this.Sequelize);
//Fecth user favorite game list
favoriteGameList(name, cb) {
let self = this;
var user_Id = 5;
self.userFavoriteGameModel.belongsTo(self.gameModel, {
as: "game_detail",
foreignKey: 'game_Id'
});
self.userFavoriteGameModel.findAll({
attributes: [
'game_Id'
],
where: {
user_id: user_Id
},
include: [{
model: self.gameModel,
as: 'game_detail',
attributes: ["game_Title", "game_Subtitle", "icon", "game_Rating"]
}],
group: ['game_Id']
}).then(function (result) {
cb(result);
let obj1 = result;
let obj2 = Object.assign({}, obj1);
console.log(JSON.stringify(obj2));
}, function (err) {
console.log('An error occurred while creating the table:', err);
cb(err);
});
}
}
[
{
"dataObject": [
{
"game_Id": 57,
"game_detail": {
"game_Title": "battlefield1",
"game_Subtitle": "Limited edition",
"icon": "image.png",
"game_Rating": 2
}
},
{
"game_Id": 58,
"game_detail": {
"game_Title": "battlefield2",
"game_Subtitle": "Limited edition",
"icon": "image.png",
"game_Rating": 1
}
}
]
}
]
Excepting Result :
[
{
"dataObject": [
{
"game_Id": 57,
"game_Title": "battlefield1",
"game_Subtitle": "Limited edition",
"icon": "image.png",
"game_Rating": 2
},
{
"game_Id": 58,
"game_Title": "battlefield2",
"game_Subtitle": "Limited edition",
"icon": "image.png",
"game_Rating": 1
}
]
}
]

sequelize orm make nested json as result of hasMany belongsTo or other relations if you dont want it as final result you could get the sequelize result then make it manually like what you want follow this:
var finalResult={}
for(var key in object){
if(typeof object['key'] == 'object'){
var parent=object['key']
for(var childKey in parent){
finalresult['childKey']=parent['childKey']
}
}else{
finalresult['key']=object['key']
}
}

Related

Retrieving data from MongoDB ánd MySQL simultaneously

I am trying to retrieve data from my MongoDB database which stores chat conversations. This works fine and returns what I want. However, I only save userIDs in MongoDB, so I need to query profile picture, username etc from my MySQL database. I tried the following:
app.get('/api/retrieveAllChats', (req, res) => {
var Conversation = mongoose.model('Conversation', ConversationSchema);
var ChatMessage = mongoose.model('Message', ChatMessageSchema);
var userID = req.query.userID.toString()
var members = []
var conversationData = []
var retrieveAllChats = new Promise(function(resolve, reject) {
Conversation.aggregate([{ $match: { "members.uID": userID } }, { $lookup: { foreignField: "c_ID", from: "messages", localField: "_id", as: "messages" } }, { "$unwind": "$messages" }, { "$sort": { "messages.t": -1 } }, { "$group": { "_id": "$_id", "lastMessage": { "$first": "$messages" }, "allFields": { "$first": "$$ROOT" } } }, { "$replaceRoot": { "newRoot": { "$mergeObjects": [ "$allFields", { "lastMessage": "$lastMessage" } ] } } }, { "$project": { "messages": 0 } }], function (err, conversations) {
if (err) return handleError(err);
conversations.forEach((conversation, i) => {
return new Promise(function (resolveConversations, rejectConversations) {
var membersPromise = conversation.members.forEach((member, x) => {
return new Promise(function (resolveUserData, rejectUserData) {
getUserData(member["uID"], function(userData) {
members.push({userID: member["uID"], joinDate: member["j"], userName: userData["userName"], userDisplayName: userData["userDisplayName"], userVerified: userData["userVerified"], userProfilePicURL: userData["userProfilePicURL"]})
console.log("userData: ", userData)
conversations[i].members[x].userData = userData
conversationData = conversations
resolveUserData({userID: member["uID"], joinDate: member["j"], userName: userData["userName"], userDisplayName: userData["userDisplayName"], userVerified: userData["userVerified"], userProfilePicURL: userData["userProfilePicURL"]})
})
})
})
resolveConversations()
})
})
resolve()
})
}).catch(error => {
console.log(error)
res.json({ errorCode: 500 })
})
retrieveAllChats.then(function() {
res.header("Content-Type",'application/json');
res.send(JSON.stringify(conversationData, null, 4));
})
})
However, the conversationData array is always empty. So I need a way to resolve the retrieveAllChats promise and pass the data I added to the existing conversations object to return it with all information I need. Any ideas on how I can do this? (getUserData is a function to retrieve the MySQL data, this one works fine and returns what I want)
You are trying to do async operation inside forEach which wouldn't work. You need to either use for...of or Promise.all.
Also, you can make this code much cimpler by using .exec() at the end of running any query or aggregation as that is supported by mongoose. Something like this should work. Make sure you change your routte line to this to tell it is an async function
app.get("/api/retrieveAllChats", async (req, res) => {
core logic
const conversions = await Conversation.aggregate([{"$match": {"members.uID": userID}}, {"$lookup": {"foreignField": "c_ID", "from": "messages", "localField": "_id", "as": "messages"}}, {"$unwind": "$messages"}, {"$sort": {"messages.t": -1}}, {"$group": {"_id": "$_id", "lastMessage": {"$first": "$messages"}, "allFields": {"$first": "$$ROOT"}}}, {"$replaceRoot": {"newRoot": {"$mergeObjects": ["$allFields", {"lastMessage": "$lastMessage"}]}}}, {"$project": {"messages": 0}}]);
for(const conversation of conversations) {
for(const member of conversation.members) {
// add your promise call here and either await it or use then to get the promise value.
}
}

Passing associated table attributes as main table attributes in sequelize

I have a query which is similar to the following.
const TODAY = new Date().setHours(0, 0, 0, 0);
const studentAttendances = await STUDENT_ATTENDANCES.findAll({
where: {
punch_in: { [Op.gt]: TODAY },
},
attributes: ['id', 'student_id', 'arrived_time'],
include: [
{
model: STUDENTS,
attributes: ['name'],
},
],
raw: true,
nest: true,
});
The current output given is an array of objects which look like the following.
{
"id": 1041,
"student_id": 16,
"arrived_time": "2019-05-29T08:29:41.000Z",
"student": {
"name": "Tom"
}
},
Instead of having a nested object as above how do i make the student name itself be an attribute of the main object ? Example as follows.
{
"id": 1041,
"student_id": 16,
"arrived_time": "2019-05-29T08:29:41.000Z",
"student": "Tom"
},
I hope to do this through sequelize without using any JS loops
Something like this should work, assuming your singular model name is "Student":
const studentAttendances = await STUDENT_ATTENDANCES.findAll({
where: {
punch_in: { [Op.gt]: TODAY },
},
attributes: [
[sequelize.col('Student.name'), 'studentName'], // will give you name as 'studentName'
'id', 'student_id', 'arrived_time'
],
include: [
{
model: STUDENTS,
attributes: [], // empty this out
},
]
});
I think you can handle it with pure javascript :
studentAttendances = studentAttendances.get({plain: true})
for(student in studentAttendances){
studentAttendances[student].student = studentAttendances[student].student.name
}

Return object with updated association in Sequelize

Using Sequelize and MySQL, updating an object including the associated object it has. Everything updates fine but I can't get the new associated object to return. If I do another GET request it's the new one but I need it to come back on the response after an update.
I'm trying to just reload that contact object before return.
The object looks like this:
{
"id": 1,
"details": "some task details",
"contact": { //associated object
"associatedId": 1,
"name": "Mike",
}
}
This is what I'm trying
db.task.findOne({
where: {
id: taskId,
userId: req.user.get('id')
},
include: [db.contact]
}).then(
function(task) {
if (task) {
return task.update(attributes);
} else {
res.status(404).send();
}
},
function() {
res.status(500).send();
}
).then(
function(task) {
if(task) {
res.json(task);
}
},
function(e) {
res.status(400).json(e);
}
);
All you need to do is returning: true :
return task.update(attributes,{
returning: true,
plain: true
});

How to get sub document only in mongoose?

I'm trying to extract only sub document from an array has the following schema :
const UserSchema = Schema({
name: {
type: String
},library:[{
story:{type: Schema.Types.ObjectId,ref: 'Story'}
}],
});
i tried to use :
module.exports.getUserStories = function(userId, callback){
User.findOne({_id: userId },callback)
.select('library.story')
};
and it gives this result :
{
"_id": "5949615072e15d2b34fa8f9d",
"library": [
{
"story": "592ae46cf2a0ba2b208cb092"
},
{
"story": "592ae608df26d80790092fe9"
},
{
"story": "592ae46cf2a0ba2b208cb092"
}
]
}
but what i'm expecting to get is only this :
[
{
"story": "592ae46cf2a0ba2b208cb092"
},
{
"story": "592ae608df26d80790092fe9"
},
{
"story": "592ae46cf2a0ba2b208cb092"
}
]
I already tried to use double selection like :
module.exports.getUserStories = function(userId, callback){
User.findOne({_id: userId },callback)
.select('library.story')
.select('story')
};
But is gives the same result
Try this one :
module.exports.getUserStories = function(userId, callback){
User.find({_id: userId },{'library.story'}).then(function(user){
if(user){
callback(user.library);
}});
};
Docs here
This output is expected to return by "select" but simply you can prepare the returned data to be as you need as following:
User.findOne({_id: userId }).select('library').then(function(result){
if(result){
//If there is returned item
var stories = result.library;
//Continue ...
}
},function(error){
//Error handling
})

Bookshelf.js get pivot table attributes

I am trying to get attributes from my m-n relational mySql table using Bookshelf.js.
I have a table users: id, name
and tournaments: id, place
and a pivot table: user_id, tournament_id, teamname
Those are my models:
var User = Bookshelf.Model.extend({
tableName: 'users',
tournaments: function () {
return this.belongsToMany(Tournament);
}
});
var Users = Bookshelf.Collection.extend({
model: User
});
var Tournament = Bookshelf.Model.extend({
tableName: 'tournaments',
users: function () {
return this.belongsToMany(User);
}
});
var Tournaments = Bookshelf.Collection.extend({
model: Tournament
});
var Tournaments_Users = Bookshelf.Model.extend({
tableName: 'tournaments_users'
});
Now when I do
Tournaments.forge().fetch({withRelated: ['users']})
.then(function (collection) {
res.send(collection.toJSON());
})
I get
{
"id": 1,
"place": "Berlin",
"users": [
{
"id": 1,
"name": "Jim",
},
{
"id": 2,
"name": "Tom",
}, ...
}
What I want:
{
"id": 1,
"place": "Berlin",
"users": [
{
"id": 1,
"name": "Jim",
"teamname" : "Team A"
},
{
"id": 2,
"name": "Tom",
"teamname" : "Team B"
}, ...
}
Anyone knows how to do this using Bookshelf?
You may use .withPivot() method.
Use it like this :
users: function () {
return this.belongsToMany(User).withPivot(['teamname']);
}
In your return, you will get a field named _pivot_teamname. Just rename them to get it good.
Documentation : http://bookshelfjs.org/#Collection-instance-withPivot
As long as I know, there is no way to fetch the pivot fields with a custom name.