How to reference a Mongoose Schema that hasn't been registered yet - node.js

This question is similar to: this question
Working with Mongoose, I have something like the following code. (I've prefixed the files with numbers, so I can load them in the order they appear in the 'models' directory, but still control the loading order.)
In 100-employee.server.model.js:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var EmployeeSchema = new Schema({
name: {
type: String,
default: ''
},
company: {
type: Schema.ObjectId,
ref: 'Company'
},
subordinates: [EmployeeSchema],
});
mongoose.model('Employee', EmplyeeSchema);
Then, in 200-company-server.js, I have:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var CompanySchema = new Schema({
name: {
type: String,
default: ''
},
CEO: {
type: Schema.ObjectId,
ref: 'Employee'
}
});
mongoose.model('Company', CompanySchema);
Obviously, this doesn't work, since Company is referenced before it is registered. And, loading these in the opposite order doesn't work for the same reason. What I need, is a logical data structure like:
{
name: 'Acme, Inc',
CEO: {
name: 'Karen',
subordinates: [{
name: 'Bob',
subordinates: [{
name: 'Lisa',
subordinates: []
},
name: 'Jerry',
subordinates: []
}]
}]
}
}
(I think I got all of my brackets in place. I just typed that JSON to illustrate the need.)
I could just use an ObjectId for 'company' in EmployeeSchema, but it doesn't fix the problem. I still get a complaint that Company hasn't been registered.
Someone will ask for the use case, so here it is:
I have a bunch of companies.I have a hierarchy of employees of a company. And, I've got a bunch of companies. For each company, I need to know the CEO, without having to search all of my employees for the one with no parent, that has an ObjectId ref Company, but that still runs into the same problem.
Any suggestions?

OK, I've come up with an answer, or at least a workaround. Doesn't feel very elegant. In fact I actively dislike it, but it works. Would love to see a better solution. In 100-company-server.js, define the parent entity first, like so:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var CompanySchema = new Schema({
name: {
type: String,
default: ''
},
CEO: {
name: { // This is the company name, so probably a bad example
type: String,
default: ''
}, {
shoeSize: Number
}, etc ...
}
});
mongoose.model('Company', CompanySchema);
The key thing to notice here is that instead of making CEO an ObjectId ref 'Employee' I supplied an object that uses the same properties as my Employee schema. Depending on how you want to use it, you may have to coerce that into an Employee object in the controller. Or, there might be a clever way to use a virtual to do the same thing (http://mongoosejs.com/docs/guide.html) But, the virtual would just have to be defined AFTER the EmployeeSchema and model.
In 200-employee.server.js, something like the following:
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
CompanySchema = mongoose.model('Company').schema; // so you can use this
// after your Employee
// is defined
var EmployeeSchema = new Schema({
name: {
type: String,
default: ''
},
company: {
type: Schema.ObjectId,
ref: 'Company'
},
subordinates: [], // Just an empty array. You fill it with Employees in the controller. You could use a virtual to do the same thing, probably.
});
mongoose.model('Employee', EmployeeSchema);
So, it's kind of ugly, but it works.
Key points:
In defining CompanySchema, you don't use an ObjectId ref 'Employee'.
Instead, you define an object that looks like a Company object.
Since this object doesn't have an _id, you have to work around that in the
controller. In my case, this works fine. But, for many/most use cases, this
probably would not. You'd have to work around this with clever virtuals
and/or the controller.
Use a simple array, without type, to store your children (suborinates in
this example.
The array works just fine, and returns objects just as if you used an array
of ObjectId ref Employee.
Making the Company refer to an Employee (which hasn't been defined yet), you
have to convert a generic object into an Employee object. But, there are
many ways to do that.
There is, undoubtedly, a better way to do this.

Related

Mongoose schema - nested objects with shared properties

I'm new to both MongoDB and Mongoose and trying to figure out the best way to handle creating my schema. I am more used to relational databases, but am hoping this will work with Mongo.
I am creating objects that "extend" other objects. For example, I have a person object that I want to use as the starting point for a parent or grandparent object. Both the parent and grandparent may have additional values beyond those that a base person has (I'm just including one example in each)...
const personSchema = new mongoose.Schema({
name: String,
birthDate: Date,
deathDate: Date,
});
const parentSchema = new mongoose.Schema({
//all the stuff a person has + below:
children: [personSchemas?] //[array of persons, important... parents can also be children,
multiple parents will share the same child]
parentingStyle: String,
})
const grandParentSchema = new mongoose.Schema({
// stuff that a parent has plus
grandparentingStyle: String,
})
I think the following post includes some answers that could be helpful to you:
Referencing another schema in Mongoose
Other than that, I might also suggest you'd define parents and grandparents as roles perhaps in this way:
var mongoose = require('mongoose');
const PersonSchema = new mongoose.Schema({
person_id: {
type: mongoose.Schema.Types.ObjectId
},
name: String,
birthDate: Date,
deathDate: Date,
//the following is if you want later to fetch persons by role
role: {
type: string,
enum: ["parent", "grandParent","child"],
}
});
//Then you could do it this way
const ParentSchema = new mongoose.Schema({
//all the stuff a person has + below:
children: [personSchemas],
parentingStyle: String
})
//Or this way
const ParentSchema = new mongoose.Schema({
//all the stuff a person has + below:
children: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Person'
}
parentingStyle: String,
})
const GrandParentSchema = new mongoose.Schema({
// same as for parents (ether way)
// stuff that a parent has plus
grandparentingStyle: String
})
In non-relational databases, it really depends on what you'd like to do with the data later on. The "joints" (in comaprison to relational DB) are much simpler to create, either by referencing an id or fetching a whole array (like you did) or simply by making more elaborate queries later on.

Populate Mongoose Document from multiple collections

I am looking for a way to populate documents beyond a document or the ref parameter.
const LibarySchema = new Schema({
books:[{
book: { type: Schema.Types.ObjectId},
bookType: { type: String }
}, { _id: false}]
});
const BookType1Schema = new Schema({
bookType: {
type: String,
default: 'lecture'
},
});
const BookType2Schema = new Schema({
bookType: {
type: String,
default: 'assingment'
},
});
This is an example of a similar project model, I am working on with 3 models. The first model hold all the information of the library books while the other models are the type of books. So during insertion of new book to the library array the bookType is added to know the collection to look for.
So my question is, is there anyway during population to check for the bookType and choose the right collection to find the book.
Also i am open for suggestion on the model.
you can just simply have a single schema for the thing you asks. Don't make unnecessary schema and make you collections bulky.. It will not be good when querying with tons of data when the system grows ...
Don't use separate schemas for storing just the types just make a single library book schema and use that to find the book you wan to find

mongoose: Insert a subdocument NOT an array

I am struggling to insert a document inside another document. I've looked at all the entries like this but they aren't quite what I am looking for.
Here is the scenario:
I have a common document that has its own schema. Lets call it a related record:
(function(){
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var relatedRecordSchema = new Schema({
params: {
recordId: Schema.Types.ObjectId,
recordType: String,
recordTitle: String
},
metadata: {
dateCreated: {type: Date, default: Date.now}
}
},{ _id : false });
mongoose.model('RelatedRecord', relatedRecordSchema);
})();
I have no trouble inserting this in an ARRAY inside document that require it. I.e its configured this way:
//Embedded
relationships: {
following: [mongoose.model('RelatedRecord').schema],
followers: [mongoose.model('RelatedRecord').schema],
blocked: [mongoose.model('RelatedRecord').schema]
}
This works perfectly.
The scenario that does not work is where there is a single related record, lets say the source of a notification:
var notificationSchema = new Schema({
params: {
title: String,
imageUrl: String,
source: mongoose.model('RelatedRecord').schema
},
metadata: {
dateCreated: { type: Date, default: Date.now },
dateViewed: Date
}
});
So when I am creating the notification I try and assign the previously prepared RelatedRecord
returnObj.params.source = relatedRecord;
The record appears during a debug to be inserted (it is inside a _docs branch but far deeper than I would expect) but when the object is saved (returnObj.save()) the save routine is abandoned without error, meaning it does not enter into the callback at all.
So it looks to me that i'm confusing mongoose as the dot assignment is forcing the subdoc into the wrong location.
So the question is simple:
How do I set that subdocument?
What the question isn't:
No I don't want to populate or advice on how you would solve this problem differently. We have sensible reasons for doing things how we are doing them.
Cheers
b
As Hiren S correctly pointed out:
1) Sub-Docs = array, always. Its in the first line in the docs :|
2) By setting the type to mixed, assignment of the object worked.
I'm a dumdum.

Mongoose error: nesting Schemas

I have a question about nesting Mongoose schema.
Here is a simple code snippet
var aSchema = new Schema({bar: String});
var bSchema = new Schema({a: aSchema, foo: String});
var cSchema = new Schema({as: [aSchema], foo:String});
This will throw TypeError on bSchema: TypeError: Undefined type at 's' Did you try nesting Schemas? You can only nest using refs or arrays., but works fine for cSchema.
Just want to ask why bSchema does not work. Cannot find explanation in Mongoose doc. Thanks.
MongoDB is not a relational database. This can cause confusion for some who are used to the RDBS model (I still get tripped up occasionally...but I'm really not a DB guy).
Oftentimes, you'll find it beneficial to reference other documents in your Mongo entities. Mongoose schemas provide a very simple and effective way to do this that feels very relational.
When defining a schema that will store a reference to a different type of document, you define the relevant property as an object with a type and a ref. Typically when defining schema properties, you can simply say: a: Number; however, Mongoose provides many different options for a schema property other than type:
a: {
type: Number,
required: true
}
Setting required: true will prevent us from saving a document where the a property is not present.
Once you understand how to define your schemas with object definitions, you can leverage Mongoose's population mechanic:
a: {
type: Mongoose.Schema.ObjectId,
ref: 'a'
}
This tells Mongoose to store the ObjectId (a Mongoose-specific identifier) of a specific a document as the a property of our schema. Still following me?
When setting this property on a Mongoose document, you can simply say: doc.a = myA. When you go to save doc, Mongoose will automagically make the conversion and only store the ID in your database.
When retrieving a document that references another schema, you'll need to populate. I won't go into that, but its pretty simple - check out the documentation.
I was facing this issue as I was completely new to the MongoDB. Later I found we need to use the relationship as below with the help of Mongoose.
Below is my country schema.
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var ObjectId = Schema.Types.ObjectId;
var CountrySchema = new Schema({
name: { type: String, required: true },
activeStatus: Boolean,
createdOn: Date,
updatedOn: Date
});
And I can use this schema in my State schema as follows.
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var ObjectId = Schema.Types.ObjectId;
var StateSchema = new Schema({
name: { type: String, required: true },
country: {type: ObjectId, ref: "Country"},
activeStatus: Boolean,
createdOn: Date,
updatedOn: Date
});
Here I am using pointing to my other schema with the help of ref.

Use mongoose populate to "join" matching subrecords

Using node.js, mongoose (3.5+), mongodb. Have got two collections in the DB:
var AuthorSchema = new mongoose.Schema({
name: { type: String },
});
var StorySchema = new mongoose.Schema({
title: { type: String },
author: { type: type: Schema.Types.ObjectId },
});
What I would like to do is retrieve an author and populate it with a subcollection (say, "stories") that is looked up from Story and match the author. Yes, much like a SQL join.
All of the examples out there work on the AuthorSchema having an array of objectids that reference StorySchema objects - that works fine. But I want to go the opposite direction; partly due to minimizing insert/updates. If I follow the example, adding a new store requires adding a new Story document and updating the Author. I want to just insert a new Story that references the Author.
I suspect that populate() is the right way to go, but can't get it to work. I'm doing something like this:
Author.find({name: 'Asimov').populate({
path: 'stories',
model: 'Story',
match: {'author': this['_id']},
}).exec(function(err, authors) {
console.log(authors);
})
But this doesn't return any stories member in the returned authors. Is this not a populate() solution? Do I really need to structure the schemas differently? Or is there some other feature of mongoose/mongo that would do what I'm looking for.
In the story schema, do this:
author: { type: type: Schema.Types.ObjectId, ref:'Author' }, //or whatever the model name is
then you can run
Story.find({}).populate('author').exec(function(err,stories) {...});

Resources