Is it possible to have a Mongoose Schema that resembles the following:
var categorySchema = new Schema({
name : String
});
var childSchema = new Schema({
name : String,
category : {
type : Schema.Types.ObjectId,
ref : 'parent.categories'
}
});
var parentSchema = new Schema({
categories : [categorySchema],
children : [childSchema]
});
Basically a child can only have a category that is contained by its parent. Is what I am trying to do possible? If not what is the cleanest way to do this?
If there is only one field name in categorySchema, maybe you could just put it into parentSchema without population as below,
var childSchema = new Schema({
name : String,
category : {
name: String
}
});
var parentSchema = new Schema({
categories : [{name: String}],
children : [childSchema]
});
When try to insert new child into parent, you could query the parent firstly, then iterate categories to get existing one and add it to children, save the parent as last, sample codes as below
Parent.find({_id: parent._id})
.exec(function(err, p) {
if (err) throw err;
var p = new Child({name: 'tt'});
p.categories.forEach(function(c) {
if (c /*find the match one*/) {
p.category = c; // assign the existing category to children
}
});
// save this parent
p.save(function(err) {...});
});
If there are many fields in categorySchema, maybe define it as individual schema could be one option, in case of there are many categories in Parent to make parent collection too large.
var categorySchema = new Schema({
name : String,
// other fields....
});
var Category = mongoose.model('Category', categorySchema);
var childSchema = new Schema({
name : String,
category : {type : Schema.Types.ObjectId, ref : 'Category'}
});
var parentSchema = new Schema({
categories : [{type : Schema.Types.ObjectId, ref : 'Category'}],
children : [childSchema]
});
The same logic when try to add new children to parent document as above.
Related
I have a mongoose schema which has an array I want to reference a document
that is nested inside that array from another schema. How can I go about it.
This is my schema
var userSchema = mongoose.Schema({
username: String,
first_name : String,
last_name : String,
exam_test_id :{
type : Schema.Types.ObjectId,
ref : 'departmentSchema.members'
}
});
var departmentSchema = mongoose.Schema({
name : String,
description : String,
members :[{
commencement_date : Date,
post : String,
function : String
}]
})
I am confused whether that ref is appropriate, if it is not right is there another
way I can go about it while still have a nested sub document
I would like to implement some validation logic within a child document, that validation is related to the state of a field of its parent. My code looks like:
const childSchema = function (parent) {
return new Schema({
name: {
type: String,
set: function (v) {
const prefix = (parent.isFlashed) ? 'with' : 'without'
return `${prefix} ${v}`
}
},
version: String
})
}
const parentSchema = new Schema({
isFlashed: Boolean,
firmware: childSchema(this)
})
I'm wondering why my code doesn't work and how can I check the value of a property of the parent schema inside my child schema.
Thanks in advance
You don't need to define your child schema as a function that returns a new Schema. You just reference the child schema in the parent.
const ChildSchema = new Schema({
name: {
type: String,
set: function(v) {
const prefix = this.parent().isFlashed ? 'with' : 'without';
return `${prefix} ${v}`;
}
}
});
const ParentSchema = new Schema({
isFlashed: Boolean,
firmware: ChildSchema
});
You'll notice that I reference the parent object as a function of this: this.parent().
When you're creating a new parent object with a child, you just use a nested object that matches the format of the child schema.
const Parent = mongoose.model('Parent', ParentSchema);
const parent = new Parent({isFlashed: false, firmware: {name: "ChildName"}});
I have two Schemas, and I want to be able to access both of them from the other one.. I am trying to do something like this:
//email.js
var mongoose = require('mongoose')
,Schema = mongoose.Schema
, FoodItemSchema = require('../models/fooditem.js')
, UserSchema = require('../models/user.js').schema
, User = require('../models/user.js').model
console.log(require('../models/user.js'));
var emailSchema = new Schema({
From : String,
Subject : FoodItemSchema,
Body : String,
Date: Date,
FoodItems : [FoodItemSchema],
Owner : { type : Schema.Types.ObjectId , ref: "User" }
});
module.exports = {
model: mongoose.model('Email', emailSchema),
schema : emailSchema
}
//user.js
var mongoose = require('mongoose')
,Schema = mongoose.Schema
, Email = require('../models/email.js').model
, EmailSchema = require('../models/email.js').schema
console.log(require('../models/email.js'));
var userSchema = new Schema({
googleID : String,
accessToken : String,
email : String,
openId: Number,
phoneNumber: String,
SentEmails : [EmailSchema]
// Logs : [{type: Schema.ObjectId, ref: 'events'}]
});
module.exports = {
model : mongoose.model('User', userSchema),
schema : userSchema
}
The first console.log() prints empty string and the second one prints as expected. I feel like I am trying to get the variables in the other schema even before they were created. Is there a common workaround for this? Or should I avoid double dependencies in my design?
Yes, you can create cross-references in Mongoose. But there is no way to create cyclic dependencies in Node.js. Though, you don't need to, because there is no need to require user schema in order to create a reference:
var mongoose = require('mongoose')
, Schema = mongoose.Schema
, FoodItemSchema = require('../models/fooditem.js');
var emailSchema = new Schema({
From: String,
Subject: FoodItemSchema,
Body: String,
Date: Date,
FoodItems: [FoodItemSchema],
Owner: { type: Schema.Types.ObjectId , ref: 'User' }
});
module.exports = {
model: mongoose.model('Email', emailSchema),
schema: emailSchema
}
You can define Schema Add statements to describe common attributes:
var mongoose = require('mongoose')
, Schema = mongoose.Schema;
module.exports = exports = function productCodePlugin(schema, options) {
schema.add({productCode:{
productCode : {type : String},
description : {type : String},
allowed : {type : Boolean}
}});
};
then require the add statement into multiple schema definition files.
var mongoose = require('mongoose')
, Schema = mongoose.Schema
, ObjectId = Schema.ObjectId
, productCodePlugin = require('./productCodePlugin');
var ProductCodeSchema = new Schema({
});
ProductCodeSchema.plugin(productCodePlugin);
I have two Schemas, a Team and a Match. I want to use the Team Schema to identify the Teams in the Match Schema. So far, here is my Team and Match JS files. I want to link the Team Schema to my Match Schema so that I can identify the home or away team simply, and so that I am storing in the Match Schema an actual Team object.
This way I can refer to the home team for example as Match.Teams.home.name = England (this is just an example of course)
Team.js
'use strict';
var util = require('util');
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var validatePresenceOf = function(value){
return value && value.length;
};
var getId = function(){
return new Date().getTime();
};
/**
* The Team schema. we will use timestamp as the unique key for each team
*/
var Team = new Schema({
'key' : {
unique : true,
type : Number,
default: getId
},
'name' : { type : String,
validate : [validatePresenceOf, 'Team name is required'],
index : { unique : true }
}
});
module.exports = mongoose.model('Team', Team);
And here is what I am trying to do with Match.js
'use strict';
var util = require('util');
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var TeamSchema = require('mongoose').model('Team');
var validatePresenceOf = function(value){
return value && value.length;
};
var toLower = function(string){
return string.toLowerCase();
};
var getId = function(){
return new Date().getTime();
};
/**
* The Match schema. Use timestamp as the unique key for each Match
*/
var Match = new Schema({
'key' : {
unique : true,
type : Number,
default: getId
},
'hometeam' : TeamSchema,
'awayteam' : TeamSchema
});
module.exports = mongoose.model('Match', Match);
Your solution: Use the actual schema, rather than a model that uses the schema:
module.exports = mongoose.model('Team', Team);
To
module.exports = {
model: mongoose.model('Team', Team),
schema: Team
};
and then var definition = require('path/to/js'); that, and use definition.schema instead of a model directly
You don't want to nest schemas.
Try Population in Mongoose: http://mongoosejs.com/docs/populate.html
That will solve your problem.
Try using Schema.Types.ObjectId in Match.js :
hometeam: { type: Schema.Types.ObjectId, ref: 'Team' }
awayteam: { type: Schema.Types.ObjectId, ref: 'Team' }
How can I add a schema to another schema? This doesn't seem to be valid:
var UserSchema = new Schema({
name : String,
app_key : String,
app_secret : String
})
var TaskSchema = new Schema({
name : String,
lastPerformed : Date,
folder : String,
user : UserSchema
})
I checked the website and it shows how to declare it for an array but not for single.
Thanks
There are a few ways to do this. The simplest is just this:
var TaskSchema = new Schema({
name : String,
lastPerformed : Date,
folder : String,
user : Schema.ObjectId
});
Then you just have to make sure your app is writing that id and using it in queries to fetch "related" data as necessary.
This is fine when searching tasks by user id, but more cumbersome when querying the user by task id:
// Get tasks with user id
Task.find({user: user_id}, function(err, tasks) {...});
// Get user from task id
Task.findById(id, function(err, task) {
User.findById(task.user, function(err, user) {
// do stuff with user
}
}
Another way is to take advantage of Mongoose's populate feature to simplify your queries. To get this, you could do the following:
var UserSchema = new Schema({
name : String,
app_key : String,
app_secret : String,
tasks : [{type: Schema.ObjectId, ref: 'Task'}] // assuming you name your model Task
});
var TaskSchema = new Schema({
name : String,
lastPerformed : Date,
folder : String,
user : {type: Schema.ObjectId, ref: 'User'} // assuming you name your model User
});
With this, your query for all users, including arrays of their tasks might be:
User.find({}).populate('tasks').run(function(err, users) {
// do something
});
Of course, this means maintaining the ids in both places. If that bothers you, it may be best to stick to the first method and just get used to writing more complex (but still simple enough) queries.
As of version 4.2.0, mongoose supports single subdocuments.
From the docs:
var childSchema = new Schema({ name: 'string' });
var parentSchema = new Schema({
// Array of subdocuments
children: [childSchema],
// Single nested subdocuments. Caveat: single nested subdocs only work
// in mongoose >= 4.2.0
child: childSchema
});
What about this simple solution?
var TaskSchema = new Schema({
name : String,
lastPerformed : Date,
folder : String,
user : {
name : String,
app_key : String,
app_secret : String
}
})