Mongoose - save after lean - mongodb

I'm fetching an object from mongo using mongoose but i want to use lean as there are attributes that aren't in mongoose model (strict:false)
After modifying that object i can't save it back
let order = await Order.findOne({_id: req.body.id}).populate({path: 'o_s', match: {removed: false}}).lean()
order.save() //not working as it's not mongoose model any more
Order.update({_id:order._id},{order}).exec(function(){
return res.json({status: true, message: 'Order Updated', order: order});
}) //not giving error but not updating too
any suggestions ?

In order to update the doc you retrieved from the db, you have to remove the _id property.
Consider this contrived example:
#!/usr/bin/env node
'use strict';
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');
const conn = mongoose.connection;
const Schema = mongoose.Schema;
const schema = new Schema({
name: String
});
schema.set('strict', false);
const Test = mongoose.model('test', schema);
const test = new Test({
name: 'Henry',
nick: 'Billy'
});
async function run() {
await conn.dropDatabase();
await test.save();
let doc = await Test.findOne({}).lean();
const id = doc._id;
delete doc._id;
doc.name = 'Billy';
doc.nick = 'The Kid';
await Test.update({ _id: id }, doc);
let updated = await Test.findOne({});
console.log(updated);
return conn.close();
}
run();
output:
{ _id: 5ad34679bc95f172c26f3382,
name: 'Billy',
nick: 'The Kid',
__v: 0 }
note that you lose auto versioning in this scenario, if this is important to you, you'll need to manually increment the version property.

i suggest to doesn't use lean() because it has some performance isues that i realized.
otherwise you can use JSON.parse(JSON.stringify(order)) and for some reasean it's a better approach. especially wen you want to iterate over the result ( in my case it was two times faster for iteration).

Related

Why are db collections created for Mongoose 6 subdocs?

I'm looking at upgrading my projects mongoose version from 5.10.19 to latest (6.5.1). I'm noticing that I have a lot more collections in my database than I did before. I made a simple example to test this out and when I run it on mongoose 5, I only see the collection "mains" but mongoose 6 creates "mains" and "subs". I'd expect that the subdocument models would not have their own collection like mongoose 5 behaves.
import { connect, model, Schema } from 'mongoose';
const mongoUrl = 'mongodb://localhost:27017/test';
(async () => {
const subSchema: Schema = new Schema({ color: String, yes: Boolean });
const mainSchema: Schema = new Schema({ name: String, sub: subSchema });
const MainModel = model('Main', mainSchema);
model('Sub', subSchema);
await connect(mongoUrl, { ssl: true, sslValidate: false });
console.log(`Successfully connected to mongodb: "${mongoUrl}"`);
await MainModel.create({ name: 'One', sub: { color: 'Yellow', yes: true } });
})()
.then(() => {
console.log('\nSuccess');
process.exit();
})
.catch(() => {
console.log('\nFailure');
process.exit();
});
Is there a mongoose setting I'm missing that's causing this to happen?
Also, on Node 12.20.12.
It's probably because of this change:
autoCreate is true by default unless readPreference is secondary or secondaryPreferred, which means Mongoose will attempt to create every model's underlying collection before creating indexes.
So turning off autoCreate (and possibly autoIndex) on subSchema should fix it. Alternatively, just don't create a model of it (only the schema).

Find using two foreign keys in mongodb

Inter_stu schema
const inter_stu_Schema = new mongoose.Schema({
result:{
type:String,
enum:['Pass','Fail','on_hold','not_attempted'],
default:'Fail'
},
sid:{
type:Schema.Types.ObjectId,
ref:'Student'
},
iid:{
type:Schema.Types.ObjectId,
ref:'Interview'
}
});
Problem statement
Above is the interview_student schema.. Now I want to fetch result of a student with a particular sid and iid, what should be the mongoose query for this ?
data inside inter_stus collection
data inside inter_stu collection
My Attempt
let sid = req.query.sid;
let iid = req.query.iid;
let student = await Student.findById(sid);
let interview = await Interview.findById(iid);
sid = mongoose.Types.ObjectId(sid);
iid = mongoose.Types.ObjectId(iid);
console.log(typeof(sid)+" "+iid)
const filter = {
'sid': {_id:sid._id},
'iid': {_id:iid._id}
};
let inter_stu = await Inter_stu.find(filter);
console.log(inter_stu)
if(student && interview && inter_stu){
data={
name:student.name,
company:interview.company,
date:interview.date,
status:inter_stu.result
}
console.log(data)
Output of my attempt
object 630501877794df5f6780121f
[]
{
name: 'student 2',
company: 'Fintech',
date: 2012-08-10T00:00:00.000Z,
status: undefined
}
while same query works in mongoDB compass
query executed in mongoDB compass
What should be the right query ?
You can create a new MongoDB ObjectId like this using mongoose:
sid = new mongoose.mongo.ObjectId(sid);
iid = new mongoose.mongo.ObjectId(iid);
const filter = {
sid,
iid
};

Mongoose adding url slug to the returned JSON object

Sorry in advance if my formatting is off! I'm building a project using MongoDB, Mongoose, and Express. Right now I'm trying to use Mongoose to read a document from the MongoDB database. For some reason it's prepending the word "slug" to the document I'm fetching from the database.
The result I'm getting is this:
[{"slug":"","title":"test","id":"62002ba44b05edb74c1a9cd8"}]
When the result I should be getting is this: [{"title":"test","id":"62002ba44b05edb74c1a9cd8"}]
I'm thinking there's an unexpected side effect from one of the libraries I'm using but I can't figure out what's causing it. It's like this before I call res.render("Test", testRes) so it might even be coming from the database like this somehow? I've been stumped on this for hours now.
const express = require('express');
const router = express.Router();
const mongoose = require('mongoose');
const Test = mongoose.model('Test');
const sanitize = require('mongo-sanitize');
router.get('/test/:testSlug', async function(req, res) {
const testSlug = req.params.testSlug;
const testSearchParam = {
slug: sanitize(testSlug)
};
console.log("search param", testSearchParam.slug);
const testRes = await Test.find(
{
title: testSearchParam.slug
}
);
res.render("Test", testRes);
});
module.exports = router;
and here is my schema for the Test data format:
const mongoose = require('mongoose');
const URLSlugs = require('mongoose-url-slugs');
const { toJSON } = require('../plugins');
const TestSchema = new mongoose.Schema({
title: {
type: String,
required: true
}},
{
collection: 'test'
}
);
TestSchema.plugin(URLSlugs('title'));
TestSchema.plugin(toJSON);
/**
* #typedef Test
*/
const Test = mongoose.model('Test', TestSchema);
module.exports = Test;
Since you are using mongoose-url-slugs, the package has a default option to create a slug field in mongoose schema.
addField (Default: True) - Add slug field to mongoose schema.
See here: https://www.npmjs.com/package/mongoose-url-slugs#options-and-defaults

How to avoid duplicates while looping through a sorted mongodb cursor

I try to avoid having duplicates returned by a mongodb cursor when write operations occurs while looping through documents.
The following code will loop for ever as we modifiy the updatedAt date while reading the collection.
const mongoose = require('mongoose');
mongoose.Promise = require('bluebird');
const Model = mongoose.model(
'Cat',
mongoose.Schema({ name: String }, { timestamps: true }).index({ updatedAt: 1 })
);
mongoose
.connect('mongodb://localhost/test', { useMongoClient: true })
.then(() => {
const docs = [];
for (var i = 0; i < 2000; i++) {
docs.push({ name: i });
}
return Model.insertMany(docs)
})
.then(() => {
const c = Model.find({}).sort({ updatedAt: 1 }).cursor();
return c.eachAsync((doc) => {
doc.markModified('name');
console.log(doc.name);
return doc.save();
});
});
I tried to use cursor.snapshot() and it works well but it cannot be used on a sorted query.
The only alternative I found is to send a count request first then apply a limit to the cursor without using snapshot at all. This seems to work as mongo seems to stack up modified document FIFO style.
The problem is that document can be inserted between the count and the cursor queries which leads to incomplete data returned by the cursor.
How can I loop through a cursor, using a sorted query without ending up with duplicates documents?

How to access a preexisting collection with Mongoose?

I have a large collection of 300 question objects in a database test. I can interact with this collection easily through MongoDB's interactive shell; however, when I try to get the collection through Mongoose in an express.js application I get an empty array.
My question is, how can I access this already existing dataset instead of recreating it in express? Here's some code:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/test');
mongoose.model('question', new Schema({ url: String, text: String, id: Number }));
var questions = mongoose.model('question');
questions.find({}, function(err, data) { console.log(err, data, data.length); });
This outputs:
null [] 0
Mongoose added the ability to specify the collection name under the schema, or as the third argument when declaring the model. Otherwise it will use the pluralized version given by the name you map to the model.
Try something like the following, either schema-mapped:
new Schema({ url: String, text: String, id: Number},
{ collection : 'question' }); // collection name
or model mapped:
mongoose.model('Question',
new Schema({ url: String, text: String, id: Number}),
'question'); // collection name
Here's an abstraction of Will Nathan's answer if anyone just wants an easy copy-paste add-in function:
function find (name, query, cb) {
mongoose.connection.db.collection(name, function (err, collection) {
collection.find(query).toArray(cb);
});
}
simply do find(collection_name, query, callback); to be given the result.
for example, if I have a document { a : 1 } in a collection 'foo' and I want to list its properties, I do this:
find('foo', {a : 1}, function (err, docs) {
console.dir(docs);
});
//output: [ { _id: 4e22118fb83406f66a159da5, a: 1 } ]
You can do something like this, than you you'll access the native mongodb functions inside mongoose:
var mongoose = require("mongoose");
mongoose.connect('mongodb://localhost/local');
var connection = mongoose.connection;
connection.on('error', console.error.bind(console, 'connection error:'));
connection.once('open', function () {
connection.db.collection("YourCollectionName", function(err, collection){
collection.find({}).toArray(function(err, data){
console.log(data); // it will print your collection data
})
});
});
Update 2022
If you get an MongoInvalidArgumentError: The callback form of this helper has been removed. error message, here's the new syntax using async/await:
const mongoose = require("mongoose");
mongoose.connect('mongodb://localhost/productsDB');
const connection = mongoose.connection;
connection.on('error', console.error.bind(console, 'connection error:'));
connection.once('open', async function () {
const collection = connection.db.collection("Products");
collection.find({}).toArray(function(err, data){
console.log(data); // it will print your collection data
});
});
I had the same problem and was able to run a schema-less query using an existing Mongoose connection with the code below. I've added a simple constraint 'a=b' to show where you would add such a constraint:
var action = function (err, collection) {
// Locate all the entries using find
collection.find({'a':'b'}).toArray(function(err, results) {
/* whatever you want to do with the results in node such as the following
res.render('home', {
'title': 'MyTitle',
'data': results
});
*/
});
};
mongoose.connection.db.collection('question', action);
Are you sure you've connected to the db? (I ask because I don't see a port specified)
try:
mongoose.connection.on("open", function(){
console.log("mongodb is connected!!");
});
Also, you can do a "show collections" in mongo shell to see the collections within your db - maybe try adding a record via mongoose and see where it ends up?
From the look of your connection string, you should see the record in the "test" db.
Hope it helps!
Something else that was not obvious, to me at least, was that the when using Mongoose's third parameter to avoid replacing the actual collection with a new one with the same name, the new Schema(...) is actually only a placeholder, and doesn't interfere with the exisitng schema so
var User = mongoose.model('User', new Schema({ url: String, text: String, id: Number}, { collection : 'users' })); // collection name;
User.find({}, function(err, data) { console.log(err, data, data.length);});
works fine and returns all fields - even if the actual (remote) Schema contains none of these fields. Mongoose will still want it as new Schema(...), and a variable almost certainly won't hack it.
Go to MongoDB website, Login > Connect > Connect Application > Copy > Paste in 'database_url' > Collections > Copy/Paste in 'collection' .
var mongoose = require("mongoose");
mongoose.connect(' database_url ');
var conn = mongoose.connection;
conn.on('error', console.error.bind(console, 'connection error:'));
conn.once('open', function () {
conn.db.collection(" collection ", function(err, collection){
collection.find({}).toArray(function(err, data){
console.log(data); // data printed in console
})
});
});
I tried all the answers but nothing worked out, finally got the answer hoe to do it.
var mongoose = require('mongoose');
mongoose.connect('mongodb://0.0.0.0:27017/local');
// let model = require('./test1');
setTimeout(async () => {
let coll = mongoose.connection.db.collection(<Your collection name in plural form>);
// let data = await coll.find({}, {limit:2}).toArray();
// let data = await coll.find({name:"Vishal"}, {limit:2}).toArray();
// let data = await coll.find({name:"Vishal"}, {projection:{player:1, _id:0}}).toArray();
let data = await coll.find({}, {limit:3, sort:{name:-1}}).toArray();
console.log(data);
}, 2000);
I have also mentioned some of the criteria to filter out. Delete and update can also be done by this.
Thanks.
Make sure you're connecting to the right database as well as the right collection within the database.
You can include the name of the database in the connection string.
notice databasename in the following connection string:
var mongoose = require('mongoose');
const connectionString = 'mongodb+srv://username:password#hosturl.net/databasename';
mongoose.connect(connectionString);