Mongoose - access populate field from within schema - node.js

I have a schema which references another model. Something like
var bookSchema = new Schema({
title: String,
series: { type: Schema.Types.ObjectId, ref: 'Series' }
});
Now I have a function in the book schema which needs access to that series. I want to do something like
bookSchema.methods.fullTitle = function() {
return [this.series.title, this.title].join(" - ");
}
but obviously that doesn't work.
How can I do this?

You need to populate the referenced model prior to being able to access its properties. Using your existing setup, you could do something similar to the following:
bookSchema.methods.fullTitle = function() {
this.populate('series', function(err, result) {
return [result.series.title, result.title].join(" - ");
});
}
See Mongoose Population for more details on this.
http://mongoosejs.com/docs/populate.html

Related

Include non related collection in model result

I'm new to MongoDb and Mongoose and this might sound silly but I'm a bit confused about how things work.
I have two unrelated models: page model and team model that looks something like this:
// page.js
const mongoose = require('mongoose');
const schema = new mongoose.schema({
name: String,
body: {
title: String,
},
});
const Page = mongoose.model('Page', schema);
export default Page;
and
// team.js
const mongoose = require('mongoose');
const schema = new mongoose.schema({
name: String,
position: Number,
});
const Team = mongoose.model('Team', schema);
export default Team;
What I want to do is when I find one page (Page.findOne({...})) to include all teams. The result will look like this:
{
_id: 'some_id',
name: 'some name',
body: {
title: 'A title',
teams: [
{ name: 'Team1', position: 1 },
{ name: 'Team2', position: 2 },
// ...
{ name: 'Team3', position: 3 },
],
},
}
I looked at populate but this requires refs to other model.
Looked at virtuals but from what I understand this should work only with instance properties.
What will be the best approach to achieve this without adding relations between the two models?
This is I'm currently doing:
const pageResult = await Page.findOne({});
let page = pageResult.toObject();
page.body.team = await Team.find({});
well with out referencing, the only way to do that is to manually query Page model and findOne() what is the doc you want and then inside the callback of that findOne(), you will have to get Teams you desire with the value of Page.body.title value.
But its very easy to use Ref and populate using mongoose but if this is the way you really want to go knock yourself out mate ... :) cheers ...
Page.findOne({_id:req.body.id},(err,page)=>{
if(!err){
team.find({},(err,teams)=>{ // this will give you an array of teams
if(!err){
page.body.teams = teams; // this line set teams array from this callback to previous findOne()'s page obj
//so that you will finally create the object you want
}else{
throw err;
}
});
}else{
}
});
since you have only 2 fields in team model I think you won't be needing to use projections

Deep populate self referencing schema in mongoose

I have an self referencing employee schema in mongoose.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var Employee = new Schema({
name: String,
description: {
type: String,
default: 'No description'
},
manager: {
type: Schema.Types.ObjectId,
ref: 'Employee',
default: null
},
reportee: [{
type: Schema.Types.ObjectId,
ref: 'Employee'
}]
});
An employee can a manager & can have several reportee. If manager is null then the employee is treated as top level employee.
I need to created hierarchy based on this model. I am struggling to generate desired output.
So far I have tried to use popluate() & mongoose-deep-populate module but I am unable to get the desired output. I wonder its becuase I have a self referencing model. Or may be I am not using these two options properly.
This is what I have tried with deep-populate module. It seems to be populating reportee model but not repotree of reportee model. In short its only populating 1 level of records.
Employee.deepPopulate(employees, 'reportee.reportee.reportee.reportee.reportee', function (err, _employee) {
employees.forEach(function (employee) {
});
});
Please suggest how can I retrive all the employee hierarchy ?
To answer my own question, I am using mongoose-deep-populate library.
To use it we need to install the module:
npm install mongoose-deep-populate
//Register the plugin
var deepPopulate = require('mongoose-deep-populate');
Employee.plugin(deepPopulate);
And then use this code:
Employee.deepPopulate(employees, 'reportee.reportee.reportee.reportee.reportee', function (err, _employee) {
employees.forEach(function (employee) {
});
});
This will load 5 levels of reportees as we have mentioned reportee.reportee.reportee.reportee.reportee

Storing a copy of a document embedded in another document in MongoDB via Mongoose

We have a requirement to store a copy of a Mongo document, as an embedded subdocument in another document. It should have a reference to the original document. The copied document needs to be a deep copy, like a snapshot of the original.
The original document's schema (defined with Mongoose) is not fixed -
it currently uses a type of inheritance to allow different additions to the Schema depending on "type".
Is there a way to such a flexible embedded schema within a Mongoose model?
Is it something that needs to be injected at runtime, when we can know
the schema?
The models / schemas we have currently look like this:
///UserList Schema: - this should contain a deep copy of a List
user: {
type: ObjectId,
ref: 'User'
},
list: {
/* Not sure if this is a how we should store the reference
type: ObjectId,
ref: 'List'
*/
listId: ObjectId,
name: {
type: String,
required: true
},
items: [{
type: ObjectId,
ref: 'Item'
}]
}
///List Schema:
name: {
type: String,
required: true
},
items: [{
type: ObjectId,
ref: 'Item'
}],
createdBy: {
type: ObjectId,
ref: 'User'
}
The code we currently have uses inheritance to allow different item types. I realise this technique may not be the best way to achieve the flexibility we require and is not the focus of my question.
///Item Model + Schema
var mongoose = require('mongoose'),
nodeutils = require('util'),
Schema = mongoose.Schema,
ObjectId = Schema.Types.ObjectId;
function ItemSchema() {
var self = this;
Schema.apply(this, arguments);
self.add({
question: {
type: String,
required: true
}
});
self.methods.toDiscriminator = function(type) {
var Item = mongoose.model('Item');
this.__proto__ = new Item.discriminators[type](this);
return this;
};
}
nodeutils.inherits(ItemSchema, Schema);
module.exports = ItemSchema;
I think you just need to create an empty {} object for the document in your parent mongoose schema. This way you´ll be able to store any object with a hardcopy of all it´s data.
parentobj : {
name: Sring,
nestedObj: {}
}
I think at this point, what you´ll need is to mark your nested objet as modified before you save it. Here is an example of my mongoose code.
exports.update = function(req, res) {
User.findById(req.params.id, function (err, eluser) {
if (err) { return handleError(res, err); }
if(!eluser) { return res.send(404); }
var updated = _.merge(eluser, req.body);
//This makes NESTEDDATA OBJECT to be saved
updated.markModified('nestedData');
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(200, eluser);
});
});
};
In addition, if you need an array of different documents in nestedDocument, the right way is this one:
parentobj : {
name: Sring,
nestedObjs: [Schema.Types.Mixed]
}
Please check Mongoose Schema Types carefully
EDIT
As you said, I´ll add you final solution as including ItemSchema in the nestedObj array definition to clarifythe type of the object to a determined one..
var ItemSchema = new Schema({
item1: String,
item2: String
});
var parentobj = new Schema({
name: Sring,
nestedObj: [ItemSchema]
});
EDIT 2:
Remember adding new Items to the nestedArray, must be done with nestedArray.push(item)
regards!!

When querying and populating subdocuments, how can I detect and handle subdocuments that were deleted?

I have a Data Model that contains an array of ObjectIDs for another Data Model.
var ProductSchema = new Schema({
images: {
type: [{
type: Schema.ObjectId,
ref: 'Image'
}],
default: []
},
});
When I query Products and populate Image records, how can I detect when some Image records have been deleted?
I guess you mean that some of the images in your array might have had it's object removed so you want to check for that. What you can do is to check this in the save hook, something like this:
ProductSchema
.pre('save', function(next) {
var Image = mongoose('Image');
this.images.forEach(function(image) {
Image.objects.findById(image, function(err, image) {
if (!image) {
// This would mean the object has been removed so
// the saved id is missing a reference...
...
}
})
})
next();
});

Dynamically create collection with Mongoose

I want to give users the ability to create collections in my Node app. I have really only seen example of hard coding in collections with mongoose. Anyone know if its possible to create collections dynamically with mongoose? If so an example would be very helpful.
Basically I want to be able to store data for different 'events' in different collections.
I.E.
Events:
event1,
event2,
...
eventN
Users can create there own custom event and store data in that collection. In the end each event might have hundreds/thousands of rows. I would like to give users the ability to perform CRUD operations on their events. Rather than store in one big collection I would like to store each events data in a different collection.
I don't really have an example of what I have tried as I have only created 'hard coded' collections with mongoose. I am not even sure I can create a new collection in mongoose that is dynamic based on a user request.
var mongoose = require('mongoose');
mongoose.connect('localhost', 'events');
var schema = mongoose.Schema({ name: 'string' });
var Event1 = mongoose.model('Event1', schema);
var event1= new Event1({ name: 'something' });
event1.save(function (err) {
if (err) // ...
console.log('meow');
});
Above works great if I hard code 'Event1' as a collection. Not sure I create a dynamic collection.
var mongoose = require('mongoose');
mongoose.connect('localhost', 'events');
...
var userDefinedEvent = //get this from a client side request
...
var schema = mongoose.Schema({ name: 'string' });
var userDefinedEvent = mongoose.model(userDefinedEvent, schema);
Can you do that?
I believe that this is a terrible idea to implement, but a question deserves an answer. You need to define a schema with a dynamic name that allows information of 'Any' type in it. A function to do this may be a little similar to this function:
var establishedModels = {};
function createModelForName(name) {
if (!(name in establishedModels)) {
var Any = new Schema({ any: Schema.Types.Mixed });
establishedModels[name] = mongoose.model(name, Any);
}
return establishedModels[name];
}
Now you can create models that allow information without any kind of restriction, including the name. I'm going to assume an object defined like this, {name: 'hello', content: {x: 1}}, which is provided by the 'user'. To save this, I can run the following code:
var stuff = {name: 'hello', content: {x: 1}}; // Define info.
var Model = createModelForName(name); // Create the model.
var model = Model(stuff.content); // Create a model instance.
model.save(function (err) { // Save
if (err) {
console.log(err);
}
});
Queries are very similar, fetch the model and then do a query:
var stuff = {name: 'hello', query: {x: {'$gt': 0}}}; // Define info.
var Model = createModelForName(name); // Create the model.
model.find(stuff.query, function (err, entries) {
// Do something with the matched entries.
});
You will have to implement code to protect your queries. You don't want the user to blow up your db.
From mongo docs here: data modeling
In certain situations, you might choose to store information in
several collections rather than in a single collection.
Consider a sample collection logs that stores log documents for
various environment and applications. The logs collection contains
documents of the following form:
{ log: "dev", ts: ..., info: ... } { log: "debug", ts: ..., info: ...}
If the total number of documents is low you may group documents into
collection by type. For logs, consider maintaining distinct log
collections, such as logs.dev and logs.debug. The logs.dev collection
would contain only the documents related to the dev environment.
Generally, having large number of collections has no significant
performance penalty and results in very good performance. Distinct
collections are very important for high-throughput batch processing.
Say I have 20 different events. Each event has 1 million entries... As such if this is all in one collection I will have to filter the collection by event for every CRUD op.
I would suggest you keep all events in the same collection, especially if event names depend on client code and are thus subject to change. Instead, index the name and user reference.
mongoose.Schema({
name: { type: String, index: true },
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', index: true }
});
Furthermore I think you came at the problem a bit backwards (but I might be mistaken). Are you finding events within the context of a user, or finding users within the context of an event name? I have a feeling it's the former, and you should be partitioning on user reference, not the event name in the first place.
If you do not need to find all events for a user and just need to deal with user and event name together you could go with a compound index:
schema.index({ user: 1, name: 1 });
If you are dealing with millions of documents, make sure to turn off auto index:
schema.set('autoIndex', false);
This post has interesting stuff about naming collections and using a specific schema as well:
How to access a preexisting collection with Mongoose?
You could try the following:
var createDB = function(name) {
var connection = mongoose.createConnection(
'mongodb://localhost:27017/' + name);
connection.on('open', function() {
connection.db.collectionNames(function(error) {
if (error) {
return console.log("error", error)
}
});
});
connection.on('error', function(error) {
return console.log("error", error)
});
}
It is important that you get the collections names with connection.db.collectionNames, otherwise the Database won't be created.
This method works best for me , This example creates dynamic collection for each users , each collection will hold only corresponding users information (login details), first declare the function dynamicModel in separate file : example model.js
/* model.js */
'use strict';
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
function dynamicModel(suffix) {
var addressSchema = new Schema(
{
"name" : {type: String, default: '',trim: true},
  "login_time" : {type: Date},
"location" : {type: String, default: '',trim: true},
}
);
return mongoose.model('user_' + suffix, addressSchema);
}
module.exports = dynamicModel;
In controller File example user.js,first function to create dynamic collection and second function to save data to a particular collection
/* user.js */
var mongoose = require('mongoose'),
function CreateModel(user_name){//function to create collection , user_name argument contains collection name
var Model = require(path.resolve('./model.js'))(user_name);
}
function save_user_info(user_name,data){//function to save user info , data argument contains user info
var UserModel = mongoose.model(user_name) ;
var usermodel = UserModel(data);
usermodel.save(function (err) {
if (err) {
console.log(err);
} else {
console.log("\nSaved");
}
});
}
yes we can do that .I have tried it and its working.
REFERENCE CODE:
app.post("/",function(req,res){
var Cat=req.body.catg;
const link= req.body.link;
const rating=req.body.rating;
Cat=mongoose.model(Cat,schema);
const item=new Cat({
name:link,
age:rating
});
item.save();
res.render("\index");
});
I tried Magesh varan Reference Code ,
and this code works for me
router.post("/auto-create-collection", (req, res) => {
var reqData = req.body; // {"username":"123","password":"321","collectionName":"user_data"}
let userName = reqData.username;
let passWord = reqData.password;
let collectionName = reqData.collectionName;
// create schema
var mySchema = new mongoose.Schema({
userName: String,
passWord: String,
});
// create model
var myModel = mongoose.model(collectionName, mySchema);
const storeData = new myModel({
userName: userName,
passWord: passWord,
});
storeData.save();
res.json(storeData);
});
Create a dynamic.model.ts access from some where to achieve this feature.
import mongoose, { Schema } from "mongoose";
export default function dynamicModelName(collectionName: any) {
var dynamicSchema = new Schema({ any: Schema.Types.Mixed }, { strict: false });
return mongoose.model(collectionName, dynamicSchema);
}
Create dynamic model
import dynamicModelName from "../models/dynamic.model"
var stuff = { name: 'hello', content: { x: 1 } };
var Model = await dynamicModelName('test2')
let response = await new Model(stuff).save();
return res.send(response);
Get the value from the dynamic model
var Model = dynamicModelName('test2');
let response = await Model.find();
return res.send(response);

Resources