Adding child document to existing mongodb document - mongodb

Essentially I am just trying to add a new sub-document to my existing mongodb document that has the following schema
/models/server/destination.js
// this is the "destination" model for mongoose
var mongoose = require('mongoose')
var Adventure = require('../models/adventure')
// this is the schema that every entry will get when a new trip is made.
var tripSchema = mongoose.Schema({
name: { type: String, required: true },
city: { type: String, required: true },
dateStart: { type: Date, required: true },
dateFinish: { type: Date, required: true },
adventures: [Adventure]
})
// module.exports makes this model available to other file
module.exports = mongoose.model('Destination', tripSchema)
/server/models/adventure.js
var mongoose = require('mongoose')
var adventure = mongoose.Schema({
site: String,
rating: String,
photo: String,
website: String,
date: Date
})
module.exports = mongoose.model('Adventure', adventure)
REST route to post to adventures
app.post('/api/destinations/:id/addAdventures', destinationsController.addAdventures)
/server/controllers/controller.js
module.exports.addAdventures = function(req, res) {
var id = req.params.id;
Destination.findOne({ _id: id }, function(err, result) {
var adventure = new Adventure(req.body)
var destination = result
destination.adventures.push(adventure)
destination.save(function(err, advresult) {
console.log('push worked')
res.json(advresult);
})
})
}
When I take the adventure out of the destination.adventures.push() the code does not break, but when I insert adventures I get an error
/Travellog/node_modules/mongoose/lib/types/array.js:117
return this._schema.caster.cast(value, this._parent, false);
^ TypeError: undefined is not a function
at Array.MongooseArray.mixin._cast (/Travellog/node_modules/mongoose/lib/types/array.js:117:32)
at Array.MongooseArray.mixin._mapCast (/Travellog/node_modules/mongoose/lib/types/array.js:286:17)
at Object.map (native)
at Array.MongooseArray.mixin.push (/Travellog/node_modules/mongoose/lib/types/array.js:299:25)
at Query.<anonymous> (/Travellog/server/controllers/destinations-controller.js:28:28)
at /Travellog/node_modules/mongoose/node_modules/kareem/index.js:177:19
at /Travellog/node_modules/mongoose/node_modules/kareem/index.js:109:16
at process._tickCallback (node.js:355:11)

The error you are getting is as a result of embedding the Adventure model instead of the schema. You need to add the Adventure schema in the destination schema definition the Adventure model's schema property:
// this is the "destination" model for mongoose
var mongoose = require('mongoose');
var AdventureSchema = require('../models/adventure').schema; /* <- access the schema via its Model.schema property */
var tripSchema = mongoose.Schema({
name: { type: String, required: true },
city: { type: String, required: true },
dateStart: { type: Date, required: true },
dateFinish: { type: Date, required: true },
adventures: [AdventureSchema]
});

Related

Mongoose Populate with Express, not working in production (Heroku)

This is a MERN app, hosted on github, and it works perfectly on localhost. Unfortunately, it does not work on Heroku.
The issue is the API request, it must return an object and populate an array of OIDs (see Department Model). API request is working. I'm getting the data from MLab, but it doesn't populate... instead returns: "surveys":[]
API File
router.get('/department_data/:d_oid', function(req, res) {
Department.findOne({_id: req.params.d_oid}).populate("surveys").exec(function(err,doc){
if(err) throw(err)
res.send(doc)
})
});
Department Model
**Department Model**
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
// Create the survey schema
var departmentSchema = new Schema({
department_name: {
type: String,
trim: true,
required: true
},
surveys: [{
type: Schema.Types.ObjectId,
ref: 'Surveys'
}],
participants: [{
type: String
}],
create_date: {
type: Date,
default: Date.now
},
created_by: {
type: Schema.Types.ObjectId,
ref: 'Created_By'
},
});
departmentSchema.index({ department_name: 1, created_by: 1}, {unique: true});
const Department = mongoose.model('Departments', departmentSchema);
module.exports = Department;
Survey Model
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
// Create the survey schema
var surveySchema = new Schema({
survey_name: {
type: String,
trim: true,
required: true
},
questions: [{
type: Schema.Types.ObjectId,
ref: 'Questions'
}],
created_date: {
type: Date,
default: Date.now
}
});
const Survey = mongoose.model('Surveys', surveySchema);
module.exports = Survey;
Solved.
The problem was in the database: the ref OIDs got scrambled with a previous update, so when it was trying to populate, Mongoose couldn't find any matching OIDs.
Solution: we had to purge and re-seed. When the correct OID references exist, this code works as expected in localhost & Heroku.

Mongoose Populate a field

I have two mongoose schema
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var itemSchema = new Schema({
name: {type: String, required: true, max: 25, trim: true},
price: {type: Number, required: true, trim: true, default: 0},
tax: {
type: Schema.Types.ObjectId,
ref: "Store"
}
});
module.exports = mongoose.model('Item', itemSchema);
The second Schema
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var storeSchema = new Schema({
name: {type: String, required: true, trim: true},
taxes: [
{
name: String,
rate: Number
}
]
});
module.exports = mongoose.model('Store', storeSchema);
What I want to do is populate the itemSchema tax object with the storeSchema taxes array of object. every time I pushed a new tax object to the taxes array mongoose created an ObjectId. I stored that ObjectId in my itemSchema Tax. I want to use that _id to retrieve the store taxes that matches the itemSchema _id.
I have tried this so far, but I get no in the tax attribute.
Item.find().populate("tax", "taxes").exec(function (err, docs) {
if (err) return console.error(err);
console.log(items);
});
Try this query
Item.find().populate({
path: 'tax',
select: 'taxes'
}).exec(function (err, docs) {
if (err) {
console.error(err);
} else {
console.log(docs);
}
});
Item.find().populate(path:"tax", model: )
Mention your item model file name... don't use "" or '' for the file name, simply add the file name.
Use Item.find().populate("Store", "taxes") instead of Item.find().populate("tax", "taxes").

Define array of objects based on a mongoose schema

I have the following schema in mongoose that I would like to have as a nested array in another schema:
var NestedSchema = new Schema({
name: { type: String, required: true }
});
Some other schema that needs and array of the nested schema.
var EventSchema = new Schema({
name: { type: String, required: true, unique: true },
fields: [NestedSchema]
});
Which works just fine. However now I want to run some validation against that array.
var validators = // some validators
var EventSchema = new Schema({
name: { type: String, required: true, unique: true },
fields: [{ type: 'NestedSchema', required: true, validate: validators }]
});
Of course type: 'NestedSchema' does not work, it was a shoot in the dark. Does mongoose allow you to have an array of object based on schema
Using:
MongoDB shell version: 3.2.12,
Mongoose : 4.4.7
I managed to have an array based on schema that way:
var NestedSchema = new Schema({
name: { type: String, required: true }
});
var EventSchema = new Schema({
name: { type: String, required: true, unique: true },
fields: [{ type: [NestedSchema], required: true }]
});
Did not try validation, but I believe it will work as required works well. :)

Mongoose and ExpressJS - modular architecture?

I want to have each file for each model - i.e. user and country.
Currently, I solved this the following way:
person.js
/*jslint node: true */
'use strict';
var mongoose = require('./mongoose'),
Schema = mongoose.Schema;
var userSchema = new Schema({
name: String,
username: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
admin: Boolean,
location: String,
meta: {
age: Number,
website: String
},
created_at: Date,
updated_at: Date,
country: {
type: Schema.Types.ObjectId,
ref: 'Country'
}
});
userSchema.methods.toString = function() {
return `I am ${this.username} and I'm from ${this.country.name}`;
}
module.exports = mongoose.model('Person', userSchema);
country.js
/*jslint node: true */
'use strict';
var mongoose = require('./mongoose'),
Schema = mongoose.Schema;
var countrySchema = new Schema({
name: {
type: String,
required: true,
unique: true
},
created_at: Date,
updated_at: Date,
people: [{
type: Schema.Types.ObjectId,
ref: 'Person'
}]
});
var Country = mongoose.model('Country', countrySchema);
module.exports = Country;
So my question is as follows: how does the mongoose reference two tables? For example, in Person schema, I reference the country by providing ref: 'Country', but I never included the model in the Person.js file. Am I doing something wrong here?
UPDATE:
Taking the below answer into the consideration, I now have 3 files:
database.js
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/people');
mongoose.set('debug', true);
module.exports = mongoose;
person.js
/*jslint node: true */
'use strict';
var mongoose = require('./database'),
Schema = mongoose.Schema;
var userSchema = new Schema({
name: String,
username: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
admin: Boolean,
location: String,
meta: {
age: Number,
website: String
},
created_at: Date,
updated_at: Date,
country: {
type: Schema.Types.ObjectId,
ref: 'Country'
}
});
userSchema.methods.toString = function() {
return `I am ${this.username} and I'm from ${this.country.name}`;
}
mongoose.model('Person', userSchema);
country.js
/*jslint node: true */
'use strict';
var mongoose = require('./mongoose'),
Schema = mongoose.Schema;
var countrySchema = new Schema({
name: {
type: String,
required: true,
unique: true
},
created_at: Date,
updated_at: Date,
people: [{
type: Schema.Types.ObjectId,
ref: 'Person'
}]
});
mongoose.model('Country', countrySchema);
Now when I include all these 3 files into a test file, e.g.:
var mongoose = require('./database'),
User = mongoose.model('Person'),
Country = mongoose.model('Country');
I get the following error: MissingSchemaError: Schema hasn't been registered for model "Person". I get the same error for Country if I delete the User variable. It appears as if mongoose is trying to define the model, not retrieve it. What seems to be the problem?
UPDATE 2:
OK, when I include now these documents, I do the following:
require('./person');
require('./country');
var User = require('mongoose').model('Person'),
Country = require('mongoose').model('Country');

Does Mongoose Actually Validate the Existence of An Object Id?

I like the validation that comes with Mongoose. We are trying to figure out whether we want to use it, and put up with the overhead. Does anyone know if providing a reference to the parent collection when creating a mongoose schema, (in the child schema, specify the object id of the parent object as a field,) does this then mean that every time you try to save the document it checks the parent collection for the existence of the refereneced object id?
I'm doing it with middleware, performing a search of the element on validation:
ExampleSchema = new mongoose.Schema({
parentId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Example'
}
});
ExampleModel = mongoose.model('Example', ExampleSchema);
ExampleSchema.path('parentId').validate(function (value, respond) {
ExampleModel.findOne({_id: value}, function (err, doc) {
if (err || !doc) {
respond(false);
} else {
respond(true);
}
});
}, 'Example non existent');
I'm using mongoose-id-validator. Works good
var mongoose = require('mongoose');
var idValidator = require('mongoose-id-validator');
var ReferencedModel = new mongoose.Schema({name: String});
var MySchema = new mongoose.Schema({
referencedObj : { type: mongoose.Schema.Types.ObjectId, ref: 'ReferencedModel'},
referencedObjArray: [{ type: mongoose.Schema.Types.ObjectId, ref: 'ReferencedModel' }]
});
MySchema.plugin(idValidator);
No, an ObjectId field that's defined in your schema as a reference to another collection is not checked as existing in the referenced collection on a save. You could do it in Mongoose middleware, if needed.
I found this thread very helpful and this is what I came up with:
This Middleware (I think its one anyway please let me know if not) I wrote checks the referenced model for the id provided in the field.
const mongoose = require('mongoose');
module.exports = (value, respond, modelName) => {
return modelName
.countDocuments({ _id: value })
.exec()
.then(function(count) {
return count > 0;
})
.catch(function(err) {
throw err;
});
};
Example model:
const mongoose = require('mongoose');
const uniqueValidator = require('mongoose-unique-validator');
const Schema = mongoose.Schema;
const User = require('./User');
const Cart = require('./Cart');
const refIsValid = require('../middleware/refIsValid');
const orderSchema = new Schema({
name: { type: String, default: Date.now, unique: true },
customerRef: { type: Schema.Types.ObjectId, required: true },
cartRef: { type: Schema.Types.ObjectId, ref: 'Cart', required: true },
total: { type: Number, default: 0 },
city: { type: String, required: true },
street: { type: String, required: true },
deliveryDate: { type: Date, required: true },
dateCreated: { type: Date, default: Date.now() },
ccLastDigits: { type: String, required: true },
});
orderSchema.path('customerRef').validate((value, respond) => {
return refIsValid(value, respond, User);
}, 'Invalid customerRef.');
orderSchema.path('cartRef').validate((value, respond) => {
return refIsValid(value, respond, Cart);
}, 'Invalid cartRef.');
orderSchema.path('ccLastDigits').validate(function(field) {
return field && field.length === 4;
}, 'Invalid ccLastDigits: must be 4 characters');
orderSchema.plugin(uniqueValidator);
module.exports = mongoose.model('order', orderSchema);
I'm a very new dev so any feedback is greatly valued!
You can try https://www.npmjs.com/package/lackey-mongoose-ref-validator (I'm the developer)
It also prevents deletion if the reference is used on another document.
var mongooseRefValidator = require('lackey-mongoose-ref-validator');
mongoSchema.plugin(mongooseRefValidator, {
onDeleteRestrict: ['tags']
});
It's an early version, so some bugs are expected. Just fill in a ticket if you find any.
I know this is an old thread but I had the same problem and I came up with a more "modern" solution.
I'm not an expert myself, hope I'm not misleading anyone, but this seems to work:
for example, in a simple "notes" schema, which contains a user field:
const noteSchema = new Schema({
user: { type: Schema.Types.ObjectId, ref: 'User' },
text: String
});
here's the middleware that checks if the userId exists:
noteSchema.path('user').validate(async (value) => {
return await User.findById(value);
}, 'User does not exist');