In mongoose you are able to declare a schema for a subdocument as per:
var childSchema = new mongoose.Schema({ name: String, age: String });
var parentSchema = new mongoose.Schema({
children: [childSchema]
});
var Parent = mongoose.model('Parent', parentSchema);
var Child = mongoose.model('Child', childSchema);
var child = new Child({name:'Joe', age: '21'});
var parent = new Parent();
parent.children = [child];
parent.save(function(err, saved) {
if(err) console.error(err);
});
Seems the subdocument type needs to be an array. I need to be able to save the subdocument as a single instance, not an array ie:
var childSchema = new mongoose.Schema({ name: String, age: String });
var parentSchema = new mongoose.Schema({
children: childSchema // not an array
});
var Parent = mongoose.model('Parent', parentSchema);
var Child = mongoose.model('Child', childSchema);
var child = new Child({name:'Joe', age: '21'});
var parent = new Parent();
parent.children = child; // not an array
parent.save(function(err, saved) {
if(err) console.error(err);
});
So not an array or a ref, just a single instance subdocument. Is this possible? If not should I use:
var childInstance = child.toObject();
Sometimes it is hard to see obvious. You do not need another schema to achieve what you want. You can simply define your sub document within your parent schema like this:
var parentSchema = new mongoose.Schema({
child: { 'name' : String, 'age' : Number } // not an array, just a sub document
});
var Parent = mongoose.model('Parent', parentSchema);
var parent = new Parent();
parent.child.name = "Joe";
parent.child.age = 13;
parent.save(function(err, saved) {
if(err) console.error(err);
});
OOPS! Edit:
I totally misread your question.. So you want to store a single subdocument? Then why you had the property named as children..
You can use:
var ParentSchema = new schema({
name : String,
child: Object
});
// etc.
john.child = new Child({name: 'kiki'});
// which is actually the same as: john.child = {name:'kiki'};
john.save();
This way, mongoose converts the document into a simple object and stores it. But i don't see the use of doing it this way? It doesn't have the benefit of a Schema and is used a default object. Not putting it in an array also blocks you from adding more nested items.
Old:
Instead of injecting the child schema's directly to the parent schema, you need to link them first (and you want to store them separately right?).
So we get two collections: parents and children (=Parent && Child). All documents of collection children are linked to a specific parents collection. And one parent document has zero, one or multiple children documents linked.
In this way you maintain the ability to modify the schema (like attaching methods or statics) and keep the documents neatly separated AND you can have the 'child' effect you wanted:
// get mongoose.
var mongoose = require('mongoose');
// connect to your local pc on database myDB.
mongoose.connect('mongodb://localhost:27017/myDB');
// parent schema.
var parentSchema = new mongoose.Schema({
name : String,
// the ObjectId's of the children links the two schema's.
children : [{type:mongoose.Schema.Types.ObjectId, ref:'Child'}]
});
// child schema.
var childSchema = new mongoose.Schema({
name : String,
// the parent's ObjectId links to the owner.
parent : {type:mongoose.Schema.Types.ObjectId, ref:'Parent'}
});
// model the schema's.
var Child = mongoose.model('Child', childSchema),
Parent = mongoose.model('Parent', parentSchema);
// create a 'parent'.
// we are not assigning the children yet.
var john = new Parent({name:'John'});
// create two children and save them. Link them to john.
var child1 = new Child({name:'Mimi', parent:john._id}),
child2 = new Child({name:'Kiki', parent:john._id});
// push child to children property of john.
john.children.push(child1);
john.children.push(child2);
// save everything.
john.save();
child1.save();
child2.save();
This will return the following:
/**
Children:
[ { name: 'Mimi',
parent: 537258f63eb92b3201b65e56,
_id: 537258f63eb92b3201b65e57,
__v: 0 },
{ name: 'Kiki',
parent: 537258f63eb92b3201b65e56,
_id: 537258f63eb92b3201b65e58,
__v: 0 } ]
Parent:
[ { name: 'John',
_id: 537258f63eb92b3201b65e56,
__v: 0,
children: [ 537258f63eb92b3201b65e57, 537258f63eb92b3201b65e58 ] } ]
*/
You can also make a static function to collection parents: addChild(child, callback), so the code looks more clean (javaĆsh style).
pseudo-code:
// add custom static method.
parentSchema.statics.addChild = function(child, callback) {
// implementation.
}
// the way you call this method:
parentModel.addChild(new Child.etc..);
Hope this helps and good luck (:
if the relationship of 2 collections is 1 to 1.
you can use 'ref'
var personSchema = Schema({
_id : Number,
name : String,
age : Number,
stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
var storySchema = Schema({
_creator : { type: Number, ref: 'Person' },
title : String,
fans : [{ type: Number, ref: 'Person' }]
});
source: http://mongoosejs.com/docs/populate.html
Related
I have a variation on the question asked and solved by #Guilherme here but my recursive embedded documents are within another schema like this;
var mongoose = require('mongoose');
var CollectPointSchema = new mongoose.Schema({
name: {type: String},
collectPoints: [ this ]
});
var GroupSchema = new mongoose.Schema({
label: {type: String},
points: [CollectionPointSchema]
});
const Group = mongoose.model("Group", GroupSchema);
I'd like to modify the solution that Guilherme proposed here but not sure how to go about it.
The main problem was that the child folders were not being populated with the name: field. I think because that field is not in the top level of the schema. So as a work-around I have added the name: field to the parent schema like this;
var GroupSchema = new mongoose.Schema({
label: {type: String},
name: {type: String},
points: [CollectionPointSchema]
});
and I also needed to change the order of Guilherme's solution from this;
var FolderModel = mongoose.model('folders', FolderSchema);
FolderSchema.pre('save', function(next) {
if (this.isNew) {
recursive_reference(FolderModel, this, "folders")
}
next();
});
to this;
FolderSchema.pre('save', function(next) {
if (this.isNew) {
recursive_reference(FolderModel, this, "folders")
}
next();
});
var FolderModel = mongoose.model('folders', FolderSchema);
The result is that I have an unused field at the parent level, but it works.
I have a mongoDB database which is generated using a script that uses only the node.js mongoDB driver without mongoose. Later on, in the application, I want to use mongoose to load a document and have a reference be populated automatically; however, this only ever returns null.
Imagine a task which contains sub-items which each have a title and an assigned person. The assigned person, in this case, is the reference I want to have populated, so the reference lives in an object inside an array in the task schema.
The following code (requiring npm install mongodb mongoose) reproduces the problem (watch out, it destroys a local database named test if you have one already):
const mongodb = require('mongodb');
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
(async () => {
// Step 1: Insert data. This is done using the mongodb driver without mongoose.
const db = await mongodb.MongoClient.connect('mongodb://localhost/test');
await db.dropDatabase();
await db.collection('persons').insertOne({ name: 'Joe' });
const joe = await db.collection('persons').findOne({ name: 'Joe' });
await db.collection('tasks').insertOne({ items: [{ title: 'Test', person: joe._id }] });
await db.close();
// ================
// Step 2: Create the schemas and models.
const PersonSchema = new Schema({
name: String,
});
const Person = mongoose.model('Person', PersonSchema);
const TaskSchema = new Schema({
items: [{
title: String,
person: { type: Schema.Types.ObjectId, ref: 'Person' },
}],
});
const Task = mongoose.model('Task', TaskSchema);
// ================
// Step 3: Try to query the task and have it populated.
mongoose.connect('mongodb://localhost/test');
mongoose.Promise = Promise;
const myTask = await Task.findOne({}).populate('items.person');
// :-( Unfortunately this prints only
// { _id: "594283a5957e327d4896d135", items: [ { title: 'Test', person: null } ] }
console.log(JSON.stringify(myTask, null, 4));
mongoose.connection.close();
})();
The expected output would be
{ _id: "594283a5957e327d4896d135", items: [ { title: 'Test', person: { _id: "594283a5957e327d4896d134", name: "Joe" } } ] }
I have verified that the two _ids actually match using mongo shell:
> db.persons.find({})
{ "_id" : ObjectId("594283a5957e327d4896d134"), "name" : "Joe" }
> db.tasks.find({})
{ "_id" : ObjectId("594283a5957e327d4896d135"), "items" : [ { "title" : "Test", "person" : ObjectId("594283a5957e327d4896d134") } ] }
What am I doing wrong when attempting to populate person? I am using mongoose 4.10.6 and mongodb 2.2.28.
The answer to this problem lies in the fact that the collection name mongoose automatically infers from the model Person is people and not persons.
The problem can be solved either by writing to the people collection in the first part or by forcing mongoose to use the collection name persons:
const Person = mongoose.model('Person', PersonSchema, 'persons');
mongoose plans to remove pluralization in the collection name anyway, see #1350 on Github.
I am trying to make an API point that would do the following. I submit an Object ID in the path. The record with that ID is found. Then, the program looks into a certain field of this object. The field contains an ObjectID for another entry in the database. At last, I need to pull up that record and increment a certain field in it.
In short, I have a child->parent relationship between certain records and would like the ability of incrementing a certain field within the parent record by submitting the child's id to the API point.
Here is the code I had that did the basic child increment. How can I go about doing it for the parent?
router.get('/today/parent/up/:id', function(req, res){
var collection = db.get('Activity');
collection.update({
_id: req.params.id
},
{
$inc: {
"repetitions.today": 1,
"repetitions.total": 1
}
}, function(err, activity){
if (err) throw err;
res.json(activity);
});
})
First use mongo references, heres documenttion:
https://docs.mongodb.com/manual/reference/database-references/
here's mongoose documentation
http://mongoosejs.com/docs/2.7.x/docs/populate.html
Basically You need to do this:
var mongoose = require('mongoose')
, Schema = mongoose.Schema
var PersonSchema = new Schema({
name : String
, age : Number
, stories : [{ type: Schema.ObjectId, ref: 'Story' }]
});
var StorySchema = new Schema({
_creator : { type: Schema.ObjectId, ref: 'Person' }
, title : String
, fans : [{ type: Schema.ObjectId, ref: 'Person' }]
});
var Story = mongoose.model('Story', StorySchema);
var Person = mongoose.model('Person', PersonSchema);
Then you could use .populate() method, and then you could extract your populated model and make changes and save them with .save(), but remember to use it in populated model, not the parent one. For ex. You've got author which contains reference to books, so you make request
author.findOne({'name': 'King'}).populate('books').exec((err, king) => {
let book0 = king.books[0];
book0.title = 'I need to change this one';
book0.save((err, data) => {
console.log('saved referenced object')
}
})
I am posting this question and the answer in hopes it will help someone else (or if there's a better answer).
How to I create virtuals for Mongoose nested schemas when in array form?
Here are the schemas:
var Variation = new Schema({
label: {
type: String
}
});
var Product = new Schema({
title: {
type: String
}
variations: {
type: [Variation]
}
});
How I would like a virtual on variations. It seems that if the sub doc is not an array then we can simply do this:
Product.virtual('variations.name')...
But that only works for non arrays.
The key is to define the virtual as part of the subschema rather than parent and it must be done before the subschema is assigned to parent. Access to the parent object can be done via this.parent():
var Variation = new Schema({
label: {
type: String
}
});
// Virtual must be defined before the subschema is assigned to parent schema
Variation.virtual("name").get(function() {
// Parent is accessible
var parent = this.parent();
return parent.title + ' ' + this.label;
});
var Product = new Schema({
title: {
type: String
}
variations: {
type: [Variation]
}
});
Using node.js, mongodb on mongoHQ and mongoose. I'm setting a schema for Categories. I would like to use the document ObjectId as my categoryId.
var mongoose = require('mongoose');
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var Schema_Category = new Schema({
categoryId : ObjectId,
title : String,
sortIndex : String
});
I then run
var Category = mongoose.model('Schema_Category');
var category = new Category();
category.title = "Bicycles";
category.sortIndex = "3";
category.save(function(err) {
if (err) { throw err; }
console.log('saved');
mongoose.disconnect();
});
Notice that I don't provide a value for categoryId. I assumed mongoose will use the schema to generate it but the document has the usual "_id" and not "categoryId". What am I doing wrong?
Unlike traditional RBDMs, mongoDB doesn't allow you to define any random field as the primary key, the _id field MUST exist for all standard documents.
For this reason, it doesn't make sense to create a separate uuid field.
In mongoose, the ObjectId type is used not to create a new uuid, rather it is mostly used to reference other documents.
Here is an example:
var mongoose = require('mongoose');
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var Schema_Product = new Schema({
categoryId : ObjectId, // a product references a category _id with type ObjectId
title : String,
price : Number
});
As you can see, it wouldn't make much sense to populate categoryId with a ObjectId.
However, if you do want a nicely named uuid field, mongoose provides virtual properties that allow you to proxy (reference) a field.
Check it out:
var mongoose = require('mongoose');
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var Schema_Category = new Schema({
title : String,
sortIndex : String
});
Schema_Category.virtual('categoryId').get(function() {
return this._id;
});
So now, whenever you call category.categoryId, mongoose just returns the _id instead.
You can also create a "set" method so that you can set virtual properties, check out this link
for more info
I was looking for a different answer for the question title, so maybe other people will be too.
To set type as an ObjectId (so you may reference author as the author of book, for example), you may do like:
const Book = mongoose.model('Book', {
author: {
type: mongoose.Schema.Types.ObjectId, // here you set the author ID
// from the Author colection,
// so you can reference it
required: true
},
title: {
type: String,
required: true
}
});
My solution on using ObjectId
// usermodel.js
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const ObjectId = Schema.Types.ObjectId
let UserSchema = new Schema({
username: {
type: String
},
events: [{
type: ObjectId,
ref: 'Event' // Reference to some EventSchema
}]
})
UserSchema.set('autoIndex', true)
module.exports = mongoose.model('User', UserSchema)
Using mongoose's populate method
// controller.js
const mongoose = require('mongoose')
const User = require('./usermodel.js')
let query = User.findOne({ name: "Person" })
query.exec((err, user) => {
if (err) {
console.log(err)
}
user.events = events
// user.events is now an array of events
})
The solution provided by #dex worked for me. But I want to add something else that also worked for me: Use
let UserSchema = new Schema({
username: {
type: String
},
events: [{
type: ObjectId,
ref: 'Event' // Reference to some EventSchema
}]
})
if what you want to create is an Array reference. But if what you want is an Object reference, which is what I think you might be looking for anyway, remove the brackets from the value prop, like this:
let UserSchema = new Schema({
username: {
type: String
},
events: {
type: ObjectId,
ref: 'Event' // Reference to some EventSchema
}
})
Look at the 2 snippets well. In the second case, the value prop of key events does not have brackets over the object def.
You can directly define the ObjectId
var Schema = new mongoose.Schema({
categoryId : mongoose.Schema.Types.ObjectId,
title : String,
sortIndex : String
})
Note: You need to import the mongoose module
Another possible way is to transform your _id to something you like.
Here's an example with a Page-Document that I implemented for a project:
interface PageAttrs {
label: string
// ...
}
const pageSchema = new mongoose.Schema<PageDoc>(
{
label: {
type: String,
required: true
}
// ...
},
{
toJSON: {
transform(doc, ret) {
// modify ret directly
ret.id = ret._id
delete ret._id
}
}
}
)
pageSchema.statics.build = (attrs: PageAttrs) => {
return new Page({
label: attrs.label,
// ...
})
}
const Page = mongoose.model<PageDoc, PageModel>('Page', pageSchema)
Now you can directly access the property 'id', e.g. in a unit test like so:
it('implements optimistic concurrency', async () => {
const page = Page.build({
label: 'Root Page'
// ...
})
await page.save()
const firstInstance = await Page.findById(page.id)
const secondInstance = await Page.findById(page.id)
firstInstance!.set({ label: 'Main Page' })
secondInstance!.set({ label: 'Home Page' })
await firstInstance!.save()
try {
await secondInstance!.save()
} catch (err) {
console.error('Error:', err)
return
}
throw new Error('Should not reach this point')
})