Model.find().toArray() claiming to not have .toArray() method - node.js

I am very new to Node.js and MongoDB and am trying to piece together my own blogging application. I have a problem trying to query through my 'Blog' model for ones with a specific username. When I try to run:
var userBlogs = function(username) {
ub = Blog.find({author: username}).toArray();
ub = ub.reverse();
};
I get an error:
TypeError: Object #<Query> has no method 'toArray'
I know globals are bad but I've just been trying to get it to work. The Mongo documentation claims that a cursor is returned which can have the toArray() method called on it. I have no idea why it won't work.
Here is my schema/model creation:
var blogSchema = mongoose.Schema({
title: {type:String, required: true},
author: String,
content: {type:String, required: true},
timestamp: String
});
var Blog = mongoose.model('Blog', blogSchema);
Here are the /login and /readblog requests
app.get('/readblog', ensureAuthenticated, function(req, res) {
res.render('readblog', {user: req.user, blogs: ub})
})
app.get('/login', function(req, res){
res.render('login', { user: req.user, message: req.session.messages });
});
app.post('/login',
passport.authenticate('local', { failureRedirect: '/login'}),
function(req, res) {
userBlogs(req.user.username);
res.redirect('/');
});
});
The end result is supposed to work with this Jade:
extends layout
block content
if blogs
for blog in blogs
h2= blog[title]
h4= blog[author]
p= blog[content]
h4= blog[timestamp]
a(href="/writeblog") Write a new blog
How can I get the query to output an array, or even work as an object?

The toArray function exists on the Cursor class from the Native MongoDB NodeJS driver (reference). The find method in MongooseJS returns a Query object (reference). There are a few ways you can do searches and return results.
As there are no synchronous calls in the NodeJS driver for MongoDB, you'll need to use an asynchronous pattern in all cases. Examples for MongoDB, which are often in JavaScript using the MongoDB Console imply that the native driver also supports similar functionality, which it does not.
var userBlogs = function(username, callback) {
Blog.find().where("author", username).
exec(function(err, blogs) {
// docs contains an array of MongooseJS Documents
// so you can return that...
// reverse does an in-place modification, so there's no reason
// to assign to something else ...
blogs.reverse();
callback(err, blogs);
});
};
Then, to call it:
userBlogs(req.user.username, function(err, blogs) {
if (err) {
/* panic! there was an error fetching the list of blogs */
return;
}
// do something with the blogs here ...
res.redirect('/');
});
You could also do sorting on a field (like a blog post date for example):
Blog.find().where("author", username).
sort("-postDate").exec(/* your callback function */);
The above code would sort in descending order based on a field called postDate (alternate syntax: sort({ postDate: -1}).

Try something along the lines of:
Blog.find({}).lean().exec(function (err, blogs) {
// ... do something awesome...
}

You should utilize the callback of find:
var userBlogs = function(username, next) {
Blog.find({author: username}, function(err, blogs) {
if (err) {
...
} else {
next(blogs)
}
})
}
Now you can get your blogs calling this function:
userBlogs(username, function(blogs) {
...
})

Related

Using two collection in MongoDB with Mongoose

I'm new to NodeJS, and I'm using the following:
Express
Mongoose
passport
HI I have two collections in MongoDB, they both have nothing to do with each other, they're called:
Post
and
User
In a get route for /home in an IF statement I am trying to query information from both collections and render them in an EJS page. My initial thoughts were create find for one model store in a var, then use it in another function when rendering the page.
Here is the relevant code:
app.get("/home", function(req, res){
console.log(req.user._id);
if (req.isAuthenticated()){
// //*Find current logged in user details
User.find({_id: req.user._id}, function(err, users){
var currentUserName = req.user.name
});
//*end
Post.find({}, function(err, posts){
res.render("home", {
startingContent: homeStartingContent,
posts: posts,
currentUser: currentUserName
});
});
} else {
res.redirect("login")
}
});
At the start of my js file, i have declared
var currentUserName
But its not displaying the value in the home.ejs for:
<p><%= currentUser %></p>
Its just blank.
What am I doing wrong here? I thought using var would make it a global variable and then can be used in another function? Other posts have suggested a aggregate, but these collections are completely seperate and have nothing to do with each other.
You have defined currentUserName inside User.find(){...} function and it is accessible only within that function. You need to define the variable that can be block-scoped, outside User.find(){...} function. Check the code snippet below:
app.get("/home", function(req, res){
console.log(req.user._id);
if (req.isAuthenticated()){
let currentUserName;
// //*Find current logged in user details
User.find({_id: req.user._id}, function(err, users){
currentUserName = req.user.name
});
//*end
Post.find({}, function(err, posts){
res.render("home", {
startingContent: homeStartingContent,
posts: posts,
currentUser: currentUserName
});
});
} else {
res.redirect("login")
}
});
Javascript best practices tells that it is better to use let instead of var. Variables declared by var keyword are scoped to the immediate function body i.e. User.find(){...} while let variables are scoped to the immediate enclosing block denoted by { }, i.e. if(req.isAuthenticated()){...}.
It won't work, because every variable has scope. Scope means access to use that var. In this case currentUserName has scope or can be used only in User.find() section. If you want to use it in Post.find(), then do it by declaring it in main function as below.
app.get("/home", function(req, res){
console.log(req.user._id);
let currentUserName;
if (req.isAuthenticated()){
User.find({_id: req.user._id}, function(err, users){
currentUserName = req.user.name
});
Post.find({}, function(err, posts){
res.render("home", {
startingContent: homeStartingContent,
posts: posts,
currentUser: currentUserName
});
});
} else {
res.redirect("login")
}
});
It will work as you require by declaring currentUserName in main function, so it will be used in whole main app.get() function

How can i print only current user in HBS template

I want to print the currently logged in user, by getting the username from my database.
In my model, the username property holds the following schema:
{
type: String,
required: true
}
index.js:
const users = require('../model/db');
router.get('/profiles/instructor', function (req, res, next) {
users.find({}, 'username', (err, doc)=>{
if(err){
console.log('err while finding on instructor at index.js => ' + err)
}else{
console.log(doc)
res.render('./profiles/instructor', {
title: 'Courstak | Instructor Profile',
name: doc
// etc......
})
ex.hbs:
<h5>{{name}}</h5>
The issue that I am having is when I open my website, it shows all of the users within the database, not just the currently logged in user:
Screenshot of error:
You mentioned in the comments of your OP that you're using the passport-local strategy, and it would appear that it very nicely connects up with express in the way you would expect, so req.user should have the information you are looking for, which would make your code like so (assuming you're following the documentation to properly use passport-local):
router.get('/profiles/instructor', function (req, res, next) {
res.render('./profiles/instructor', {
title: 'Courstak | Instructor Profile',
name: req.user.username
});
});
What I would recommend also is using some middleware such as connect-ensure-login to ensure that the user is logged in and has a valid session. You can add the middleware above like so:
var connectEnsureLogin = require('connect-ensure-login')
router.get('/profiles/instructor',connectEnsureLogin.ensureLoggedIn(), function (req, res, next) {
// ...
}

Mongoose toArray() undefined [duplicate]

I am very new to Node.js and MongoDB and am trying to piece together my own blogging application. I have a problem trying to query through my 'Blog' model for ones with a specific username. When I try to run:
var userBlogs = function(username) {
ub = Blog.find({author: username}).toArray();
ub = ub.reverse();
};
I get an error:
TypeError: Object #<Query> has no method 'toArray'
I know globals are bad but I've just been trying to get it to work. The Mongo documentation claims that a cursor is returned which can have the toArray() method called on it. I have no idea why it won't work.
Here is my schema/model creation:
var blogSchema = mongoose.Schema({
title: {type:String, required: true},
author: String,
content: {type:String, required: true},
timestamp: String
});
var Blog = mongoose.model('Blog', blogSchema);
Here are the /login and /readblog requests
app.get('/readblog', ensureAuthenticated, function(req, res) {
res.render('readblog', {user: req.user, blogs: ub})
})
app.get('/login', function(req, res){
res.render('login', { user: req.user, message: req.session.messages });
});
app.post('/login',
passport.authenticate('local', { failureRedirect: '/login'}),
function(req, res) {
userBlogs(req.user.username);
res.redirect('/');
});
});
The end result is supposed to work with this Jade:
extends layout
block content
if blogs
for blog in blogs
h2= blog[title]
h4= blog[author]
p= blog[content]
h4= blog[timestamp]
a(href="/writeblog") Write a new blog
How can I get the query to output an array, or even work as an object?
The toArray function exists on the Cursor class from the Native MongoDB NodeJS driver (reference). The find method in MongooseJS returns a Query object (reference). There are a few ways you can do searches and return results.
As there are no synchronous calls in the NodeJS driver for MongoDB, you'll need to use an asynchronous pattern in all cases. Examples for MongoDB, which are often in JavaScript using the MongoDB Console imply that the native driver also supports similar functionality, which it does not.
var userBlogs = function(username, callback) {
Blog.find().where("author", username).
exec(function(err, blogs) {
// docs contains an array of MongooseJS Documents
// so you can return that...
// reverse does an in-place modification, so there's no reason
// to assign to something else ...
blogs.reverse();
callback(err, blogs);
});
};
Then, to call it:
userBlogs(req.user.username, function(err, blogs) {
if (err) {
/* panic! there was an error fetching the list of blogs */
return;
}
// do something with the blogs here ...
res.redirect('/');
});
You could also do sorting on a field (like a blog post date for example):
Blog.find().where("author", username).
sort("-postDate").exec(/* your callback function */);
The above code would sort in descending order based on a field called postDate (alternate syntax: sort({ postDate: -1}).
Try something along the lines of:
Blog.find({}).lean().exec(function (err, blogs) {
// ... do something awesome...
}
You should utilize the callback of find:
var userBlogs = function(username, next) {
Blog.find({author: username}, function(err, blogs) {
if (err) {
...
} else {
next(blogs)
}
})
}
Now you can get your blogs calling this function:
userBlogs(username, function(blogs) {
...
})

nodejs + mongoose - how to use forEach in nodejs

I'm still learning about node.js and mongodb. I'm trying to write simple app with nodejs and mongoose. My mongoose schema:
var todoSchema = new Schema({
task: String,
description: String,
date: Date,
status: String,
checklist: Boolean,
pic: String
});
I have collection named todos I'm trying to get the content of todos using this code:
apiRoutes.route('/todos/detail')
.get(function(req, res){
Todo.distinct( "pic" ).each(function(doc){
Todo.find({"pic": doc.pic}, function(err, todo){
if (err)
res.send(err);
var finalResult = [];
finalResult.push(todo);
res.send(finalResult);
});
});
});
But I got this error:
Object #<Query> has no method 'each'
Any idea to solve this? Really appreciate for the help.
From what I gather in your question, you don't necessarily need the loop since with the distinct pics array you are iterating over, you are using it to query the collection for each pic, which is
essentially equivalent to just querying the whole collection as sending the resulting array of documents returned from the query:
apiRoutes.route('/todos/detail').get(function(req, res){
Todo.find({"pic": { "$exists": true }}, function(err, todos){
if (err) res.send(err);
res.send(todos);
});
});
Unless you want to get a distinct list of pics, get the todo items with those pics you could try the following approach:
apiRoutes.route('/todos/detail').get(function(req, res){
Todo.find().distinct('pic', function(error, pics) {
// pics is an array of all pics
Todo.find({"pic": { "$in": pics } }, function(err, todos){
if (err) res.send(err);
res.send(todos);
});
});
});
For starting you should try with .forEach() instead of .each() first :)
Here you can see the forEach doc.

Need to send response after forEach is done

I'm working with NodeJS + Mongoose and I'm trying to populate an array of objects and then send it to the client, but I can't do it, response is always empty because it is sent before forEach ends.
router.get('/', isAuthenticated, function(req, res) {
Order.find({ seller: req.session.passport.user }, function(err, orders) {
//handle error
var response = [];
orders.forEach(function(doc) {
doc.populate('customer', function(err, order) {
//handle error
response.push(order);
});
});
res.json(response);
});
});
Is there any way to send it after the loop has finished?
Basically, you could use any solution for async control flow management like async or promises (see laggingreflex's answer for details), but I would recommend you to use specialized Mongoose methods to populate the whole array in one MongoDB query.
The most straightforward solution is to use Query#populate method to get already populated documents:
Order.find({
seller: req.session.passport.user
}).populate('customer').exec(function(err, orders) {
//handle error
res.json(orders);
});
But if, for some reason, you can't use this method, you could call Model.populate method yourself to populate an array of already fetched docs:
Order.populate(orders, [{
path: 'customer'
}], function(err, populated) {
// ...
});
One solution is to use Promises.
var Promise = require('bluebird');
Promise.promisifyAll(Order);
router.get('/', isAuthenticated, function(req, res) {
Order.findAsync({ seller: req.session.passport.user })
.then(function(orders) {
return Promise.all(orders.map(function(doc){
return Promise.promisify(doc.populate).bind(doc)('customer');
}));
}).then(function(orders){
// You might also wanna convert them to JSON
orders = orders.map(function(doc){ return doc.toJSON() });
res.json(orders);
}).catch(function(err){
//handle error
});
});
BlueBird's .promisifyAll creates an …Async version of all functions of an object, which saves you an extra step in configuring the initial promise. So instead of Order.find I used Order.findAsync in above example

Resources