Object.assign() creates wierd properties when assigns mongoose doc - node.js

MessageThread.findById(req.body._id)
.populate({ path: "messages" })
.exec((err, foundMessageThread) => {
var filtered = foundMessageThread.messages.map(message=>{
return Object.assign({}, message, {isRead: true});
})
console.log("filtered", filtered);
});
console.log shows:
{ '$__':
InternalCache {
strictMode: true,
selected: {},
shardval: undefined,
saveError: undefined,
validationError: undefined,
adhocPaths: undefined,
removing: undefined,
inserting: undefined,
version: undefined,
getters: {},
_id: 5a4c7f2d8b49fc260c396f55,
populate: undefined,
populated: undefined,
wasPopulated: true,
scope: undefined,
activePaths: [Object],
pathsToScopes: {},
ownerDocument: undefined,
fullPath: undefined,
emitter: [Object],
'$options': true },
isNew: false,
errors: undefined,
_doc:
{ sentAt: 2018-01-03T06:58:53.188Z,
isRead: false,
_id: 5a4c7f2d8b49fc260c396f55,
sender: 5a4b77767251b44cd870219f,
reciever: 5a4b780a7251b44cd87021a1,
text: 'qwe',
__v: 0 },
'$init': true,
isRead: true },
......
it repeats many times.
I suppose it (InternalCache { strictMode: true...) relates to message that is taken from foundMessageThread. And it reveals its metadata(in my term) while assigning. Because:
MessageThread.findById(req.body._id)
.populate({ path: "messages" })
.exec((err, foundMessageThread) => {
var filtered = foundMessageThread.messages.map(message=>{
console.log("message", message)
return Object.assign({}, message, {isRead: true});
})
console.log("filtered", filtered);
});
console.log shows
{ sentAt: 2018-01-03T06:58:53.188Z,
isRead: false,
_id: 5a4c7f2d8b49fc260c396f55,
sender: 5a4b77767251b44cd870219f,
reciever: 5a4b780a7251b44cd87021a1,
text: 'qwe',
__v: 0 },
....
My question:
Is it normal behavior?
If it is how to fix it? Because "metadata" prevents objects being assigned.
P.S. I've tried:
MessageThread.findById(req.body._id)
.populate({ path: "messages" })
.exec((err, foundMessageThread) => {
var filtered = foundMessageThread.messages.map(message=>{
return **Object.assign({}, message._doc, {isRead: true})**;
})
console.log("filtered", filtered);
});

This is a normal behaviour with mongoose. Objects returned by mongoose wrap the actual data, so as to add behaviours (methods) to it.
You can get to the actual data object by using toObject method, for eg, message.toObject().
However there are properties like __v, which are used by mongoose for house keeping purposes. If you don't want them, you can modify the toObject method like this
messageSchema.set('toObject', {
versionKey: false,
transform: (doc, ret) => {
delete ret.__v;
return ret;
},
});

You can also use .lean() method with mongoose request. This allows to get less cumbersome response and process it easyly:
try {
const assets = await myModel.Assets
.find({ isActive: true }, { __v: 0, _id: 0 })
.lean()
.exec()
// do something
}
catch(error) {
throw error
}

It appears that the _doc property of mongoose objects should be referenced if you want to either assign to or from those objects. In fact, I was unable to set additional properties in a normal manner like so mongoDoc.newProp = propValue;. It was not altering the original object.
For assignment, instead of:
Object.assign(mongoDoc, {a: 1, b: 2});
You'd want:
Object.assign(mongoDoc._doc, {a: 1, b: 2});
Or if you're assigning the mongoDoc properties to another object, you'd want
Object.assign({a: 1, b: 2}, mongoDoc._doc);

// add .lean() in your query like below
const documents = await DocumentModel.find().lean();
// at debug now ,
// now you will get your result;

Related

Node.js/MongoDB - Returned document seems to revert back to a promise, even after I've awaited it

I'm doing what feels like a simple query, and this is a two part question. The first is whether there's a better way to do it, given my data structure, and the second is, if not, why am I seeing this behavior in my current implementation?
I am building a quizzing app - Node.js backend with MongoDB. I have a "questions" collection - here's a sample document from that:
{
"_id": ObjectId("62742bb0dbf1a0577864bbeb"),
"category": "World History",
"results": [],
"text": "**What volcano** erupted in 79AD, burying the cities of Pompeii and Herculaneum?",
"answer": "Mt. Vesuvius",
"owner": ObjectId("605271517fce7249cc8eb436"),
"__v": 0
}
I have a "quizzes" collection, where documents can have an array of questions from the questions collection:
{
"_id":ObjectId("62800d4d1d7c821198121193"),
"type": "std",
"questions": [{
"id": ObjectId("627034e92dda695b884a632e"),
"value": 2
},
{
"id": ObjectId("627160ec79517a79180f1e41"),
"value": 2
},
{
"id": ObjectId("62742bb0dbf1a0577864bbeb"),
"value": 4,
}
],
"title": "test standard quiz",
"description": "this is a test standard quiz\n\nthis is a second line.",
"owner": ObjectId("605271517fce7249cc8eb436"),
"__v": 0
}
At least in theory, this should allow users to create questions, and add them to as many quizzes as they want with different point values, if they wish. Doing an aggregation led to some trouble, as the ObjectIDs for the questions is embedded in an object, since they need to have a point value tied to them as well. Here's my kludgy way of joining the two collections:
const getQuizDetails = async ()=> {
const Quiz = require('./models/quizModel');
const Question = require('./models/questionsModel');
const owner = '605271517fce7249cc8eb436'; //just for testing, I'm hard-coding the owner's ID here
const results = await Quiz.find({
owner,
type: 'std',
});
console.log(results); //This gives an array with the document(s) as expected.
const vals = results.map(r=> {
return r.questions.map(q=> {
return q.value;
}
}
console.log(vals); //This gives an array of arrays, containing the values of each question in each of the results above (e.g. [[2,2,4]] if we just returned the sample document above)
let qs;
for (var i = 0; i < results.length; i++) {
qs = await Promise.all(
results[i].questions.map((q) => {
return Question.findById(q.id);
})
);
q2 = qs.map((q, j) => {
return {
...q,
value: vals[i][j],
};
});
console.log(qs); //This gives an array of questions, with the text and answer of each one, which is what I want.
console.log(q2); //This gives something unexpected.
}
}
Above, when I log the variable qs, I get an array with the question details, with the question text, category, and answer:
{
category: 'Pop Music',
results: [],
_id: 627160ec79517a79180f1e41,
text: `Dua Lipa's 2021 hit "Cold Heart" features **what other artist**? His song "Sacrifice" provides both the title and many of the lyrics of "Cold Heart".`,
answer: 'Elton John',
owner: 605271517fce7249cc8eb436,
__v: 0
},
{
category: 'World History',
results: [],
_id: 62742bb0dbf1a0577864bbeb,
text: '**What volcano** erupted in 79AD, burying the cities of Pompeii and Herculaneum?',
answer: 'Mt. Vesuvius',
owner: 605271517fce7249cc8eb436,
__v: 0
},
{
category: 'Art',
results: [],
_id: 627435e1bd9f6b4bcc057761,
text: 'Who painted the Mona Lisa?',
answer: 'Leonardo da Vinci',
owner: 605271517fce7249cc8eb436,
__v: 0
},
I would expect, since I have an array of objects (and not promises anymore...are they?), that mapping it with another array (of numbers) would be straightforward. However, this is not the case. When I log q2, I get an array of objects similar to this:
{
'$__': InternalCache {
strictMode: true,
selected: {},
shardval: undefined,
saveError: undefined,
validationError: undefined,
adhocPaths: undefined,
removing: undefined,
inserting: undefined,
saving: undefined,
version: undefined,
getters: {},
_id: 627ad0c89041f24a60469288,
populate: undefined,
populated: undefined,
wasPopulated: false,
scope: undefined,
activePaths: [StateMachine],
pathsToScopes: {},
cachedRequired: {},
session: undefined,
'$setCalled': Set(0) {},
ownerDocument: undefined,
fullPath: undefined,
emitter: [EventEmitter],
'$options': [Object]
},
isNew: false,
errors: undefined,
'$locals': {},
'$op': null,
_doc: {
category: 'Geography',
results: [],
_id: 627ad0c89041f24a60469288,
text: 'What is the capital of Georgia?',
answer: 'Tblisi',
owner: 605271517fce7249cc8eb436,
__v: 0
},
'$init': true,
value: 10
}
All of the information that I need is there (the question information plus the point value), but I have a lot of other stuff as well - if I were to guess, it's the structure of a promise or something, but there shouldn't be any unresolved promises here - I've awaited them all, and they logged to the console as resolved objects. What's going on here?

How do I a display an array value based on a search id in mongoDB?

Below is my code to find the review from the reviews array in a restaurant object and display the review if the _id matches:
Restaurant Object:
{
_id: new ObjectId("61723c7378b6d3a5a02d908e"),
name: 'The Blue Hotel',
location: 'Noon city, New York',
phoneNumber: '122-536-7890',
website: 'http://www.bluehotel.com',
priceRange: '$$$',
cuisines: [ 'Mexican', 'Italian' ],
overallRating: 0,
serviceOptions: { dineIn: true, takeOut: true, delivery: true },
reviews: [
{
_id: new ObjectId("61736a0f65b9931b9e428790"),
title: 'dom',
reviewer: 'firuu',
rating: 4,
dateOfReview: '25/1/2002',
review: ' bruh'
}
]
}
async get(reviewId) {
const restaurantsCollection = await restaurants();
reviewId = ObjectId(reviewId)
const r = await restaurantsCollection.find({reviews: {$elemMatch: {_id: reviewId}}})
return r
},
index.js:
a = await reviews.get("61736a0f65b9931b9e428790")
console.log(a)
The output I get is:
FindCursor {
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
[Symbol(kCapture)]: false,
[Symbol(topology)]: Topology {
More of this as I am missing some functionality somewhere.
How can I get the output which is the returned review based on the review id as input?
"It says r.toArray is not a function" - You show us that you got a find cursor, Indeed FindCursor has a method called toArray.
method/cursor.toArray/
The find() method returns a FindCursor that manages the results of your query. You can iterate through the matching documents using one of the following cursor methods:
next()
toArray()
forEach()
There is many way you can iterate of cursor,
Using async iterator:
for await (let doc of collection.find({})) {
console.log(doc);
}
toArray
let docs = await collection.find({}).toArray();
And much more! they support callbacks, node stream api etc... But this two method is modern.

I only want only name and tag, but now I am getting model also as you can see in output

I am trying to connect my node with my mongodb.i have no issue in that. Here i am getting my value as well as model in output, but I only like to have my name and tag in terminal, so is there a way.
i am doing it in window OS. so is it an window setting issue. so please let me know what is the way to get tags and name.
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/playground')
.then(()=>console.log('Connected to MongoDB...'))
.catch(err =>console.error('Could not connect to MongoDB...',err));
const courseSchema = new mongoose.Schema({
name : String,
author : String,
tags : [String],
date : {type : Date, default : Date.now},
isPublished : Boolean
});
const Course = mongoose.model('Course',courseSchema);
async function createCourse(){
const course = new Course({
name : 'Angular.js Course',
author: 'Mosh',
tags : ['Angular','Frontend'],
isPublished : true
});
const result = await course.save();
// console.log(result);
}
async function getCourses(){
const courses = await Course
.find({ author: 'Kunal', isPublished : true})
.limit(10)
.sort({name : 1})
.select({name : 1, tags : 1});
console.log(courses);
}
getCourses();
output
Connected to MongoDB...
[
model {
'$__': InternalCache {
strictMode: true,
selected: [Object],
shardval: undefined,
saveError: undefined,
validationError: undefined,
adhocPaths: undefined,
removing: undefined,
inserting: undefined,
version: undefined,
getters: {},
_id: 5fc6f9ba8059e13514a7de87,
populate: undefined,
populated: undefined,
wasPopulated: false,
scope: undefined,
activePaths: [StateMachine],
pathsToScopes: {},
ownerDocument: undefined,
fullPath: undefined,
emitter: [EventEmitter],
'$options': true
},
isNew: false,
errors: undefined,
_doc: {
tags: [Array],
_id: 5fc6f9ba8059e13514a7de87,
name: 'node.js Course'
},
'$init': true
}
]
i think you need to add .lean() to achieve what you want :
async function getCourses(){
const courses = await Course
.find({ author: 'Kunal', isPublished : true})
.limit(10)
.sort({name : 1})
.select({name : 1, tags : 1}).lean();
console.log(courses);
}
The lean() function tells mongoose to not hydrate query results. In
other words, the results of your queries will be the same plain
JavaScript objects that you would get from using the Node. js MongoDB
driver directly, with none of the mongoose magic.

Result of sequelize findByPk

I want to find an itme with the id is: 1
const student = await this.db.Student.findByPk(1)
when I get the result then console it(console.log(student))
student {
dataValues: { id: 1, name: 'Darush', family: 'Hamidi' },
_previousDataValues: { id: 1, name: 'Darush', family: 'Hamidi' },
_changed: Set(0) {},
_options: {
isNewRecord: false,
_schema: null,
_schemaDelimiter: '',
raw: true,
attributes: [ 'id', 'name', 'family' ]
},
isNewRecord: false
}
then send the student to browser result will be (res.send(student))?
{
"id": 1,
"name": "Darush",
"family": "Hamidi"
}
why we have a difference ?
I set rwa to true: It works Perfectly
const student = await this.db.Student.findByPk(1,{ raw: true })
The difference appears because using findByPk method (and all similar ones) you get a Sequelize model's instance and not a plain object and when you pass this model instance to res.send it is serialized into plain object with model attributes only.
If you wish to get a plain object from a model instance call get({ plain: true }) and then there will be no difference.
const plainStudentObj = student.get({ plain: true })
res.send(plainStudentObj)

How to get data in sequelize using Noje js

This is code i have used, fetched the all data in database, but i have not getting in value. I'm new for sequelize.
Project.findAll({ raw: true}).then(function (users) {
console.log(users);
console.log(users.dataValues);
}).catch(function (err) {
console.log('Oops! something went wrong, : ', err);
});
This is Output:
This is console.log(users);
[ DAO {
dataValues:
{ idProject: 1,
projectName: 'Symfony',
isActive: '1',
createdAt: 2018-10-23T06:32:43.000Z,
modifiedAt: 2018-10-23T06:32:43.000Z },
_previousDataValues:
{ idProject: 1,
projectName: 'Symfony',
isActive: '1',
createdAt: 2018-10-23T06:32:43.000Z,
modifiedAt: 2018-10-23T06:32:43.000Z },
options: { isNewRecord: false, isDirty: false, raw: true },
hasPrimaryKeys: true,
selectedValues:
RowDataPacket {
idProject: 1,
projectName: 'Symfony',
isActive: '1',
createdAt: 2018-10-23T06:32:43.000Z,
modifiedAt: 2018-10-23T06:32:43.000Z },
__eagerlyLoadedAssociations: [],
isNewRecord: false }.....
This is console.log(users.dataValues);
undefined
How is it possible?
When you use findAll, it returns an array, as you can see here in the documentation:
http://docs.sequelizejs.com/class/lib/model.js~Model.html#static-method-findAll
so you should iterate over this array, like so:
Project.findAll({ raw: true})
.then(projects => {
projects.forEach(project => {
console.log(project);
console.log('project name', project.projectName);
})
}).catch(function (err) {
console.log('Oops! something went wrong: ', err);
});
Optionally you could use Async/Await for a cleaner code:
try {
const projects = await Project.findAll({ raw: true});
projects.forEach(project => {
console.log('project name ', project.projectName);
})
} catch(err) {
console.log('Oops! something went wrong: ', err);
}

Resources