Mongoose random documents non sequential - node.js

I have a node.js project and I need to get 8 random documents which are not sequential from my mongoDB database using Mongoose.
My Schema:
var mongoose = require('mongoose');
var random = require('mongoose-simple-random');
var schema = new mongoose.Schema({
title: String,
width:String,
height:String,
});
var Images = mongoose.model('Images', schema);
Images.count().exec(function (err, count) {
// Get a random entry
var random = Math.floor(Math.random() * count)
// Again query all users but only fetch one offset by our random #
Images.find({}).limit(8).skip(random).exec(
function (err, result) {
// Tada! random user
console.log(result)
//res.send(results);
})
})
module.exports = {
Images: Images
};
When calling the function in my route file (Main.js):
var Images = require('../models/images.js');
app.get('/homepage', function(req, res){
var rand = Math.floor(Math.random() * 10000);
Images.find({}).limit(8).skip(rand).exec(function(err, docs){
res.render('homepage', {images: docs});
});
});
How would I call the 'find' function in my model from my main.js route file?

You could use the following to get unique items with $sample but grouping by _id to remove possible duplicates in the random result :
db.images.aggregate([{
$sample: { size: 100 }
}, {
$group: {
_id: "$_id",
document: { $push: "$$ROOT" }
}
}, {
$limit: itemCount
}, {
$unwind: "$document"
}])
For the structure of your code, you could define a static method getRandomItems, storing your mongoose object in express app.db and calling the mongoose object from your router with req.app.db :
model.js
'use strict';
exports = module.exports = function(app, mongoose) {
var schema = new mongoose.Schema({
title: String,
width: String,
height: String,
});
schema.statics.getRandomItems = function(itemCount, cb) {
this.aggregate([{
$sample: { size: 100 }
}, {
$group: {
_id: "$_id",
document: { $push: "$$ROOT" }
}
}, {
$limit: itemCount
}, {
$unwind: "$document"
}], cb);
};
app.db.model('Images', schema);
};
app.js
'use strict';
var mongoose = require('mongoose'),
express = require('express');
var app = express();
app.db = mongoose.createConnection("mongodb://localhost/testDB");
// config data models
require('./models')(app, mongoose);
require('./routes')(app);
app.listen(8080, function() {
});
routes.js
'use strict';
exports = module.exports = function(app) {
// BboxAPI
app.get("/random", function(req, res) {
req.app.db.models.Images.getRandomItems(8, function(err, result) {
if (err) {
console.log(err);
res.status(500).json(err);
} else {
res.status(200).json(result);
}
});
});
};

Related

saving ref of another document with mongoose and nodejs

i am new using mongodb and i am practicing with ref and populate... but i have a silly problem.
i am receiving from my client a object like this.
{
"name": "Jhon"
"books": [
{
"title": "whatever",
"pages": 300
},
{
"title": "otherBook",
"pages": 450
}
]
}
So i have two schemas, authorSchema and booksSchema... so what i pretend is save the books and take the _id of each book to save the author with it.
My code in nodejs
authorCtrl.saveAuthor = (req, res) => {
var booksId= []
for (i = 0; i < req.body.books.length; i++) {
booksModel.create(req.body.books[i], function (err, book) {
booksId.push(book._id)
})
}
var author= {
name: req.body.name,
books: booksId
}
console.log(author) // here i check and books array is empty,
authorModel.create(author).then((authorSaved) => {
res.json(authorSaved)
}).catch(err => {
res.json(err)
})
}
i know it is an asynchronous problem... but how can i do it??.. or what is the best practice to ref collections?
/////EDIT//////
Here are my schemas
Authors Schema
const mongoose = require('mongoose')
const { Schema } = mongoose;
const authorsSchema = new Schema({
name: { type: String },
books: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'books'
}]
})
module.exports = mongoose.model('authors', authorsSchema);
Books Schema
const mongoose = require('mongoose')
const { Schema } = mongoose;
const booksSchema = new Schema({
title: { type: String },
pages: { type: Number }
})
module.exports = mongoose.model('books', booksSchema);
Authors Schema:
const mongoose = require('mongoose')
const { Schema } = mongoose;
const authorsSchema = new Schema({
name: String,
books: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Book'
}]
})
module.exports = mongoose.model('Author', authorsSchema);
Books Schema:
const mongoose = require('mongoose')
const { Schema } = mongoose;
const booksSchema = new Schema({
title: String,
pages: Number,
})
module.exports = mongoose.model('Book', booksSchema);
NodeJS Code:
const author = {
name: req.body.name
}
AuthorModel.create(author, (error, createdAuthor)=>{
//handle error
BookModel.insertMany(req.body.books, function (err, createdbooks) {
// handle error
createdAuthor.books.push(createdbooks);
createdAuthor.save();
})
}
Try this,
authorCtrl.saveAuthor = (req, res) => {
var booksId= [];
for (var i = req.body.books.length - 1; i >= 0; i--) {
booksModel.create(req.body.books[i], (err, book) => {
booksId.push(book._id);
if(i == 0) { // This if condition executes at the end of the for loop.
var author= {
name: req.body.name,
books: booksId
};
console.log(author);
authorModel.create(author).then((authorSaved) => {
res.json(authorSaved);
}).catch(err => {
res.json(err);
});
}
});
}
}
Hope it helps...
You can do it with Javascript promises as below:
var booksId = [];
var promises = [];
req.body.books.forEach(element => {
promises.push(insertBook(element));
});
Promise.all(promises)
.then(function(data){
/* do stuff when success */
console.log('##GREAT##',booksId);
/*** INSERT ARRAY OF BOOK IDs INTO authorModel***/
})
.catch(function(err){
/* error handling */
});
function insertBook(element){
return new Promise(function(resolve, reject){
var book = new booksModel({
title: element.title,
page: element.page
});
book.save(function(err,data){
if(err){
console.log(err);
reject(err)
}else{
console.log('#success');
booksId.push(data._id)
resolve();
}
});
});
}

Empty response object in MongoDb

I am trying to get all documents in mongo collection but instead i am getting empty response. my database name is taskDb which has a collection named item in which all the documents are stored. I think maybe there is some problem with schema but mongo is schema less db so i am not able to find the solution.
index.js
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var cors = require('cors');
app.use(bodyParser.json());
app.use(cors());
Items = require('./items.js');
mongoose.connect('mongodb://localhost/taskDb');
var db = mongoose.connection;
app.get("/",(req, res)=> {
res.send('Visit /api/*****');
});
app.get("/api/items",(req, res)=> {
Items.getItems(function(err, items){
if(err){
throw err;
}
console.log(res.json(items));
res.json(items);
});
});
// app.get("/api/matches",(req, res)=> {
// Matches.getMatches(function(err, matches){
// if(err){
// throw err;
// }
// res.json(matches);
// });
// });
// app.get("/api/deliveries/:playerName",(req, res)=> {
// Deliveries.getPlayerStats(req.params.playerName ,function(err, deliveries){
// if(err){
// throw err;
// }
// res.json(deliveries);
// });
// });
app.listen(3005,()=>{
console.log('Listening on port 3005...');
});
item.js
var mongoose = require('mongoose');
var itemSchema = mongoose.Schema({
_ID:{
type: String,
required: true
},
ITEM:{
type: String,
required: true
},
KEY:{
type: Number,
required: true
},
STATUS:{
type: String,
required: true
}
});
var Item = module.exports = mongoose.model('item', itemSchema);
// module.exports.getPlayerStats = function (playerName, callback) {
// Deliveries.aggregate([{$project:{_id: 1,batsman: 1 ,batsman_runs: 1, dismissal:{
// $cond: [ { $eq: ["$player_dismissed", playerName ] }, 1, 0]
// }}},{ $match: { batsman: playerName } },
// { $group: {_id: "$batsman", total_runs: { $sum: "$batsman_runs" },total_dismissal: { $sum: "$dismissal"}}}
// ], callback);
// }
module.exports.getItems = function (callback, limit) {
Item.find(callback).limit(limit);
};
There are two issues I see in getItems function:
Condition for find() is not specified. In case you want to read all records, you can specify it as empty object.
Limit parameter is not being passed from your request handler to getItems function. You would either need to default it to some number or handle the scenario where limit won't be passed.
Modifying getItems() to something like below should work:
module.exports.getItems = function (callback, limit) {
Item.find({}, callback).limit(limit || 20); // Default limit to a feasible number
};
Also, you can pass limit to getItems() function from request handler if you want to override default:
app.get("/api/items",(req, res)=> {
Items.getItems(function(err, items){
if(err){
throw err;
}
console.log(res.json(items));
return res.json(items);
}, 50); // Pass limit
});

Store mongoose Query result inside another mongoose query

I'm fairly new to nodeJs and mongodb. I was having some problems regarding querying mongoose objects. I have 2 models
User model :
var mongoose = require('mongoose');
var bcrypt = require('bcrypt');
var gravatar = require('gravatar');
var Schema = mongoose.Schema;
var SendSchema = require('./Send').schema;
var TravelSchema = require('./Travel').schema;
var UserSchema = new Schema({
name: String,
email:{type: String, required: true, unique:true},
phone: {type: String, required: true, unique:true},
password: {type:String,required:true},
token: String,
is_admin : Boolean,
sendings : [SendSchema],
travels : [TravelSchema],
created_at : Date,
updated_at : Date,
image_url: String
})
UserSchema.pre('save',function(next){
var user = this;
if (this.isModified('password')||this.isNew){
bcrypt.genSalt(10,function(err,salt){
if(err){
return next(err);
}
bcrypt.hash(user.password,salt,function(err,hash){
if(err){
return next(err);
}
user.password = hash;
next();
});
});
} else {
return next();
}
});
UserSchema.pre('save', function(next) {
var currentDate = new Date();
this.updated_at = currentDate;
if (!this.created_at)
this.created_at = currentDate;
next();
});
UserSchema.methods.comparePassword = function (pw,cb) {
bcrypt.compare(pw,this.password,function(err,isMatch){
if(err){
return cb(err);
}
cb(null,isMatch);
});
};
module.exports = mongoose.model('User',UserSchema);
and Travel model :
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var TravelSchema = new Schema({
travelling_from:String,
travelling_to:String,
amount:String,
date:Date,
created_at: Date,
updated_at: Date,
traveller : {type:Schema.Types.ObjectId ,ref:'User'}
});
TravelSchema.pre('save', function(next) {
var currentDate = new Date();
this.updated_at = currentDate;
if (!this.created_at)
this.created_at = currentDate;
next();
});
module.exports = mongoose.model('Travel',TravelSchema);
now using express routes I'm querying the mongoose models like this:
router.post('/travellers',passport.authenticate('jwt',{session:false}), function(req, res, next) {
var pickup_location = req.body.pickup_location;
var delivery_location = req.body.delivery_location;
var date = req.body.date;
var sender = req.user._id;
var senders = [];
var travellers =[];
Travel.find({'date':date},function (err,travels) {
if(err) console.error(err.message);;
async.forEach(travels,function (travel,callback) {
User.findById(travel.traveller,function (err,user) {
if(err) throw err;
data = {
name:user.name,
email:user.email,
phone:user.phone,
image_url:user.image_url,
type:'traveller'
};
console.log(data);
travellers.push(data);
callback();
});
},function (err) {
if(err) console.error(err.message);;
});
});
console.log(travellers);
res.json(travellers);
});
When I try to access the traveller array after in the res.json() the query is complete I get an empty response whereas when I console.log() the data It prints correctly during the query, can someone help me out through this new asynchronous paradigm, I've been banging my head for 2 days now.
Add the async.series API which will run one function at a time, wait for it to call its task callback, and finally when all tasks are complete it will run callback (the final callback).
For example:
router.post('/travellers',
passport.authenticate('jwt', { "session": false }), function(req, res, next) {
var pickup_location = req.body.pickup_location;
var delivery_location = req.body.delivery_location;
var date = req.body.date;
var sender = req.user._id;
var locals = {
travellers: [],
senders: []
};
async.series([
// Load travels first
function(callback) {
Travel.find({ "date": date }, function (err, travels) {
if (err) return callback(err);
locals.travels = travels;
callback();
});
},
// Load users (won't be called before task 1's "task callback" has been called)
function(callback) {
async.forEach(locals.travels, function (travel, callback) {
User.findById(travel.traveller, function (err, user) {
if (err) return callback(err);
data = {
"name": user.name,
"email": user.email,
"phone": user.phone,
"image_url": user.image_url,
"type": "traveller"
};
console.log(data);
local.travellers.push(data);
callback();
});
}, function (err) {
if (err) return callback(err);
callback();
});
}
], function(err) { /* This function gets called after
the two tasks have called their "task callbacks" */
if (err) return next(err);
//Here locals will be populated with `travellers` and `senders`
//Just like in the previous example
console.log(locals);
console.log(locals.travellers);
res.json(locals.travellers);
});
});
An alternative is to use the $lookup operator in the aggregation framework where you can run an aggregation operation like the following:
router.post('/travellers',
passport.authenticate('jwt', {session: false }), function(req, res, next) {
var pickup_location = req.body.pickup_location;
var delivery_location = req.body.delivery_location;
var date = req.body.date;
Travel.aggregate([
{ "$match": { "date": date } },
{
"$lookup": {
"from": "users",
"localField": "traveller",
"foreignField": "_id",
"as": "traveller"
}
},
{ "$unwind": "$traveller" },
{
"$group": {
"_id": null,
"travellers": {
"$push": {
"name": "$traveller.name",
"email": "$traveller.email",
"phone": "$traveller.phone",
"image_url": "$traveller.image_url",
"type": "traveller"
}
}
}
}
], function(err, results) {
if (err) return next(err);
console.log(results);
console.log(results[0].travellers);
res.json(locals[0].travellers);
});
});

mongoose call model method from outside of express app

I have mongoose model file like this
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var testSchema = new Schema({
name: { type: String },
username: { type: String },
provider: { type: String },
accessToken: { type: String },
testId: { type: String }
});
/**Indexing*/
testSchema.index({ testId: 1, accessToken: 1 });
testSchema.statics = {
get: function (id, callback) {
this.findOne({'testId': id}, function(error, items){
callback(error, items);
});
},
create: function (data, callback) {
var test = new this(data);
test.save(callback);
}
};
var test = mongoose.model('test', testSchema);
/** export schema */
module.exports = {
Test: test
};
it is working Good with an express app. But I would like to use this model to view and insert data from command line. So, here is my approch which is not working
var Test = require('./app/model/test').Test;
Test.get({'testId': 1},function(err,res){
if(!err){
console.log(res);
}else{
console.log(err);
}
I see two problems:
you're not calling mongoose.connect() anywhere, so it's not connecting to the database
it looks like you should pass the id as an argument to get(); now you're passing it a query. Try this: Test.get('1', ...)

Mongoose remove subdocuments by id method

I have two models:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ProjectSchema = new Schema({
title: { type: String },
images: [{
type: Schema.Types.ObjectId,
ref: 'Image'
}]
});
module.exports = mongoose.model('Project', ProjectSchema);
and
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ImageSchema = new Schema({
fileName: { type: String },
fileSize: { type: Number }
});
module.exports = mongoose.model('Image', ImageSchema);
Existing projects are filled with images as follows:
Project.findById(req.params.project_id, function(err, project) {
if (err) { res.status(400).send(err); }
var image = new Image({
fileName: req.file.name,
fileSize: req.file.size
});
image.save(function(err) {
if (err) { res.status(400).send(err); }
project.images.push(image);
project.save();
);
});
There are no problems in getting images from the project:
Project.findById(req.params.project_id)
.populate('images')
.exec(function(err, project) {
if (err) { res.status(400).send(err); }
res.status(200).json(project.images);
});
i try removing an image from a story, using Mongoose documentation:
http://mongoosejs.com/docs/subdocs.html
http://mongoosejs.com/docs/api.html#types_documentarray_MongooseDocumentArray.id
Project
.findById(req.params.project_id)
.populate('images')
.exec(function(err, project) {
if (err) { res.status(400).send(err); }
project.images.id(req.params.image_id).remove();
project.save();
});
But i keep getting errors:
/api-server/app/admin/images/index.js:170
project.images.id(req.params.image_id).remove();
^
TypeError: project.images.id is not a function
I searched here for solutions, but i only got some things on $pull from 2013.
Is the .id() method broken, or am i doing something wrong.
As i'm fairly new to mongoose, are there ways to do this better?
You just need to delete the image from the database. I hope the following code helps you.
Project
.findById(req.params.project_id)
.exec(function(err, project) {
if (err) { res.status(400).send(err); }
project.save();
Image.remove({"_id":project.images._id},function(){})
});
You can delete subdocuments by using findByIdAndUpdate and $pull.
Seting options to {new: true} overwrites the existing document
var fieldsToRemove= {
$pull: {
images: {
_id: req.params.type
}
}
};
var options = { new: true };
Project.findByIdAndUpdate(req.params.project_id, fieldsToRemove, options,
function(err, project) {...
it will remove the subdocument with specified _id

Resources