Mongoose Model Object behaves strangely - javascript

I am using mongoose to get person data from database. This is the code i use:
return new Promise((resolve, reject) => {
Person.findOne({}, (err, result) => {
if(err) {
reject(err);
} else {
console.log(result);
console.log(result.firstname);
console.log(result.githubLink);
resolve(result);
}
});
});
This is output from console.log(result)
{ _id: 593c35e6ed9581db3ef85d75,
firstname: 'MyName',
lastname: 'MyLastName',
jobtitle: 'Web Developer',
email: 'foo#example.com',
githubLink: 'https://github.com/myGithub' }
And this is result from console.log(result.firstname); and console.log(result.githubLink);
MyName
undefined
Is this promise somehow messing up with this result? It's really weird because logging only the result shows my github link and logging the link says undefined.

If you have fields present in your database object that are not actually present in the Schema defined for the model, then they will still "log" but you cannot access the values of the property normally.
In most cases you really want to define the item properly in your schema:
githubLink: String
Or you can access properties you deliberately do not want to define using the .get() method:
result.get('githubLink')

Related

Objects instances retrieved by findOne do not have any methods

Whenever I retrieve an object from my mongo database, it doesn’t have any methods and I am trying to figure out (1) why and (2) a solution.
To summarise, the following sums up the problem:
(1) I can create an object and its methods work:
const newUser = new User(email, hashedPassword);
console.log(newUser.test); // correct - it’s not undefined
(2) I can insert the instance (with its methods) into the database (via saveToDb):
const insertedUser = await collection.insertOne(this);
(3) I can retrieve it from the database (via findByEmail):
const user = await collection.findOne({ email });
(4) But it doesn’t have any methods anymore:
if (!user) return; // temporary code
console.log(user); // correctly displays object keys and values
console.log('user.test', user.test); // undefined - why?
Why does this happen? I’ve read a few other posts about it, but they all seem to use mongoose, which I do not use in my app (maybe I should use it?). This post did not get an answer, which I think is a similar issue.
Any insight would be appreciated, thank you.
Incase needed to see, here’s the class:
export class User {
email: string;
hashedPassword: any;
dateCreated: Date;
constructor(email: string, hashedPassword: any) {
this.email = email; // make email.toLowerCase();
this.hashedPassword = hashedPassword;
this.dateCreated = new Date();
}
async saveToDb() {
try {
const collection = getCollection(USERS_COLLECTION_NAME);
const sanitisedEmail = this.email.toLowerCase();
const insertedUser = await collection.insertOne(this);
console.log('THIS HAS BEEN INSERTED TO DB:', this);
console.log('this.test:', this.test); // works
this.test();
const token = jwt.sign(insertedUser, sanitisedEmail, {
expiresIn: 60 * 24,
});
return token;
} catch (err) {
throw err;
}
}
test() {
console.log('test() within class Fn');
return 5;
}
static staticTest() {
console.log('staticTest() within class Fn');
return 6;
}
signToken() {
const token = jwt.sign(this, this.email, {
expiresIn: 60 * 24,
});
return token;
}
static async fetchAll() {
try {
const collection = getCollection(USERS_COLLECTION_NAME);
const users = await collection.find().toArray();
return users;
} catch (err) {
throw err;
}
}
static async findByEmail(email: string) {
try {
const collection = getCollection(USERS_COLLECTION_NAME);
const user = await collection.findOne({ email });
if (!user) return; // temporary code
console.log('FOUND THIS USER: ', user); // works
console.log('user.test', user.test); // undefined - key problem lies here...
return user;
} catch (err) {
throw err;
}
}
}
The objects you get back via query methods, such as findOne, will be plain objects. They are not instances of your class, as these objects were sent and saved as JSON in the database, and this does not include class information, prototypes, nor methods.
So what you can do, is change the prototype of the object you get back. Or better, create a new instance of your class with Object.create and inject the properties with Object.assign:
const user = Object.assign(
Object.create(User.prototype),
await collection.findOne({ email })
);
Now your user object will again have access to the prototype methods.
For completeness sake, I'll also mention the alternative:
const user = await collection.findOne({ email });
Object.setPrototypeOf(user, User.prototype);
But read the disclaimer provided by Mozilla Contributors on setPrototypeOf:
Changing the [[Prototype]] of an object is, by the nature of how modern JavaScript engines optimize property accesses, currently a very slow operation in every browser and JavaScript engine. In addition, the effects of altering inheritance are subtle and far-flung, and are not limited to the time spent in the Object.setPrototypeOf(...) statement, but may extend to any code that has access to any object whose [[Prototype]] has been altered.

Not able to log a fetch's result

I'm currently working with nodejs, I created a server side function that returns and prints data from a database.
app.get('/renderMainDashboard', (req,res)=>{ //DASHBOARD DATA
con.connect(err => {
if (!err){
con.query("SELECT * FROM owners", (err, data, fields) =>{
console.log(data); //IT LOGS THE DATA INTO DE VS TERMINAL
return data;
})
}
});
});
I need this function to be called from the client side, so there is a class that makes the fetch inside the constructor:
export default class{
constructor() {
this.title = "Dashboard";
fetch('http://localhost:5600/renderMainDashboard') //DEFAULT GET ()
.then(response => response.json())
.then(finalResponse => {console.log('Datos recibidos desde el server', finalResponse);});
//DOESN'T LOG 'Datos recibidos...' TO WEB CONSOLE
//.then(console.log('Response from then statement'); //IT DOES THE LOG
}
//----
}
The function actually works, when I try to do the fetch it is still working but I need to log the response. As you may see, there is a then statement with a console.log('Datos recibidos...') but it is not working. Any idea of what I may be doing wrong?
Actual output of the DB:
[
TextRow {
id: 1,
firstName: 'Andres',
lastName: 'Gonzalez',
email: 'androsogt#gmail.com',
personalKey: 'androso+1234-',
phoneNumber: '35006115'
},
TextRow {
id: 2,
firstName: 'Pedro',
lastName: 'Contreras',
email: 'sirpedro#gmail.com',
personalKey: 'holamundo',
phoneNumber: '41508886'
},
TextRow {
id: 3,
firstName: 'Yuhana',
lastName: 'Melgar',
email: 'melgar.keyla#gmail.com',
personalKey: 'COD2002',
phoneNumber: '37578639'
}
]
You don't end your request by sending a response. See this example:
app.get('/', function (req, res) {
res.send('hello world')
})
You should use res.send() method to end your request and send back data.
Why it doesn't log? Because response.json() returns a rejected promise as there's no response (timeout) and hence, second .then doesn't get called.
Your express get /renderMainDashboard handler is not sending anything back to the client.
Try replacing:
return data;
with:
res.status(200).json(data); // provided that data is a valid JSON object

Changing result of the findOne moongose

i'm working in a node project and need help.
I have callback function, that executes the method findOne, have o result, but I can't change a property of the result.
Ex. Data
{
name: "John",
age: 30,
}
Ex. Callback Function.
this._userRepository.findOne({}, (err, user) => {
if (err) {
return callback({ code: 404, message: "User not found" }, []);
}
user.name = "edson";
return callback("", user);
});
But user name not change in the return.
Ex. Result
{
name: "John",
age: 30,
}
Expected outcome
{
name: "edson",
age: 30,
}
You need to use user.save() before returning anything as given below,
user.name = "edson";
user.save();
return callback("", user);
OR use findOneAndUpdate() as given below
const filter = {};
const update = { name : "edson"};
this._userRepository.findOneAndUpdate(filter, update, {upsert: true}, function(err, doc) {
if (err) return res.send(500, {error: err});
return res.send('Succesfully saved.');
});
Refer https://mongoosejs.com/docs/tutorials/findoneandupdate.html for further details
are you actually trying to update the value of the user in your database? If so, I think you would need to call user.save()
Try wrapping your code in an async function, then running the following code
await this._userRepository.findOneAndUpdate({}, {$set: { name: 'edson' }});
The findOneAndUpdate method takes a filter and an object wrapped in a $set key. This method is a lot quicker.

Syntax for chaining mongoose promises

I am relatively new to using Promises and MongoDB / Mongoose, and am trying to chain a flow through several database queries into an efficient and reliable function.
I want to know if my final function is a good and reliable way of achieving what I want, or if there are any issues or any improvements that can be made.
The process is as follows:
1) Check whether or not the user already exists
usersSchema.findOne({
email: email
}).then(res => {
if(res==null){
// user does not exist
}
}).catch(err => {});
2) Add the new user to the database
var new_user = new usersSchema({ email: email });
new_user.save().then(res => {
// new user id is res._id
}).catch(err => {});
3) Assign a free promotional code to the user
codeSchema.findOneAndUpdate({
used: false,
user_id: true
},{
used: true,
user_id: mongoose.Types.ObjectId(res._id)
}).then(res => {
// user's code is res.code
}).catch(err => {});
Obviously, each query needs to execute in sequence, so after a lot of research and experimentation into how to do this I have combined the queries into the following function, which seems to be working fine so far:
function signup(email){
// check email isn't already signed up
return usersSchema.findOne({
email: email
}).then(res => {
if(res==null){
// add to schema
var new_user = new usersSchema({ email: email });
// insert new user
return new_user.save().then(res => {
var result = parse_result(res);
// assign a code
return codesSchema.findOneAndUpdate({
used: false,
user_id: true
},{
used: true,
user_id: mongoose.Types.ObjectId(result._id),
});
});
}else{
return 'The user already exists';
}
});
}
signup('test#test.com').then(res => {
console.log('success, your code is '+res.code);
}).catch(err => {
console.log(err);
});
I'm still trying to get my head around exactly how and why this works - the function is returning a promise, and each nested promise is returning a promise.
My main concerns are that there is a lot of nesting going on (is there perhaps a way to do this by chaining .then() callbacks instead of nesting everything?) and that the nested promises don't appear to have error catching, although as the signup() function itself is a promise this seems to catch all the errors.
Is anyone knowledgeable on the subject able to confirm whether my process looks good and reliable or not? Thanks!
To avoid indentation-hell, if you return a value from the function passed to a Promise's .then()-method, you can chain multiple .then() as a neat and tidy pipeline. Note that you can also return a Promise that has pending status, and then next function in line will execute when it has resolved.
function signup (email) {
return usersSchema.findOne({
email: email
}).then(res => {
if (res) throw 'The user already exists'
var new_user = new usersSchema({ email: email })
return new_user.save()
}).then(res => {
var result = parse_result(res)
return codesSchema.findOneAndUpdate({
used: false,
user_id: true
},{
used: true,
user_id: mongoose.Types.ObjectId(result._id)
})
})
}
Even better, if you have the possibility to use async/await (Node v7.6 or above), your code can look like normal blocking code:
async function signup (email) {
let user = await usersSchema.findOne({ email: email })
if (user) throw 'The user already exists'
let new_user = await new usersSchema({ email: email }).save()
let result = parse_result(new_user)
return codesSchema.findOneAndUpdate({
used: false,
user_id: true
},{
used: true,
user_id: mongoose.Types.ObjectId(result._id)
})
}
Your original function call code works on both without changes.
you code can be improve in this way
function signup(email){
// check email isn't already signed up
return usersSchema.findOne({
email: email
}).then(res => {
if(res==null){ // add to schema
var new_user = new usersSchema({ email: email });
// insert new user
return new_user.save()
}else{
return Promise.reject(new Error('The user already exists'));
}
})
.then(res => {
var result = parse_result(res);
// assign a code
return codesSchema.findOneAndUpdate({used: false,user_id: true},{used: true,user_id: mongoose.Types.ObjectId(result._id),});
});
}
signup('test#test.com').then(res => {
console.log('success, your code is '+res.code);
}).catch(err => {
console.log(err);
});

MySQL query returns a string as a result of JSON_OBJECT()

I wrote a query that gives me posts from a table and also returns an info about each post's author:
SELECT post.id, post.text, post.datetime, JSON_OBJECT(
'username', user.username,
'firstName', user.firstName,
'firstName', user.lastName) as author
FROM post
INNER JOIN user ON post.authorId = user.id;
But in response the author field is a string:
author: "{"username": "#", "firstName": null}"
datetime: "2017-05-02T20:23:23.000Z"
id: 10
text: "5555"
I tried to fix that using CAST but anyway author is a string:
CAST(JSON_OBJECT(
'username', user.username,
'firstName', user.firstName,
'firstName', user.lastName) as JSON) as author
Why is it happened and how to fix that?
UPD:
I send the data from server using Node.js and Express:
app.get('/posts', (req, res, next) => {
getPosts().then((posts) => {
res.setHeader('Content-Type', 'application/json');
res.send(posts);
})
.catch(next);
});
// ...
getPosts() {
return new Promise((resolve, reject) => {
const query = `
SELECT post.id, post.text, post.datetime, JSON_OBJECT(
'username', user.username,
'firstName', user.firstName,
'firstName', user.lastName) as author
FROM post
INNER JOIN user ON post.authorId = user.id;`;
this.connection.query(query, (err, result) => {
if(err) {
return reject(new Error("An error occured getting the posts: " + err));
}
console.log(result) // prints author as a string
resolve(result || []);
});
});
}
Result of console.log:
{
id: 1,
text: 'hello, world!',
datetime: 2017-05-02T15:08:34.000Z,
author: '{"username": "#", "firstName": null}'
}
I also tried here change res.send(posts) to res.json(posts) but it's doesn't help.
My function from client that touch server for the posts:
export const getPosts = () => {
customFetch(apiUrl + '/posts')
.then(response => response.json())
.then(json => json)
};
I think it's fine for MySQL to return a string, as the JSON_OBJECT() function is already doing its job by producing a string that represents a well formatted JSON.
You can convert that string to a JSON object in javascript with
var obj = JSON.parse(yourString);
Edit (about JSON and Javascript objects)
First of all, in case you didn't know, JSON stands for JavaScript Object Notation: that means that it's a textual way of representing JavaScript objects.
From MySQL point of view, you're already solving this problem inside the SELECT statement, because what the query is returning is a valid JSON.
The fact is that then that data is transmitted to Javascript (Node), but Javascript internal representation of an object is not the same as its textual representation (the JSON); this means you have to "cast" it, so that the string gets converted to the object.
The mechanism you'd need in order to avoid this cast would require MySQL to know how Javascript represents an object, and then using such knowledge to return the bytecode of your object. This is called serialization, and I'm afraid it's way beyond the purpose of a dbms like MySQL.
Hope this clarifies your doubts a bit...
Used JSON typeCast in the DB config file
connection: {
..
host: process.env.DB_HOST,
user: process.env.DB_USER,
..
typeCast: function (field, next) {
if (field.type == 'JSON') {
return (JSON.parse(field.string()));
}
return next();
},
..
}
What about returning the whole thing as a JSON_OBJECT and doing JSON.Parse on it once
SELECT JSON_OBJECT("id", post.id, "text", post.text, "datetime", post.datetime, "author", JSON_OBJECT(
'username', user.username,
'firstName', user.firstName,
'firstName', user.lastName))
FROM post
INNER JOIN user ON post.authorId = user.id;
That eliminates your need to loop

Categories