Node, Mongoose, problems saving multiple-depths of nested schema - mongodb

I'm having trouble working out how to insert multiple-depths of nested schema in MongoDB, via Mongoose and node.js.
The example below is a bit contrived but should hopefully explain my problem. As for why each schema is defined as a full model but not used in the example, that's just because in my real-world problem, they are actual, usable models and I wanted this example to be realistic in case it's relevant.
So here are the example schema definitions in reverse order, ie. smallest Russian-doll first:
// Define pen model
var PenSchema = new Schema({
color: String // black, blue or red
});
var Pen = mongoose.model('Pen', PenSchema);
// Define ruler model
var RulerSchema = new Schema({
units: String // inches or millimetres
});
var Ruler = mongoose.model('Ruler', RulerSchema);
// --------
// Define drawing tools model
var DrawingToolsSchema = new Schema({
label: String,
pens: [Pen]
});
var DrawingTools = mongoose.model('DrawingTools', DrawingToolsSchema);
// Define measuring tools model
var MeasuringToolsSchema = new Schema({
label: String,
ruler: [Ruler]
});
var MeasuringTools = mongoose.model('MeasuringTools', MeasuringToolsSchema);
// --------
// Define stationery box model
// It has a label and two compartments - tools for drawing and measuring
var StationeryBoxSchema = new Schema({
label: String,
drawingTools: [DrawingToolsSchema],
measuringTools: [MeasuringToolsSchema]
});
var StationeryBox = mongoose.model('StationeryBox', StationeryBoxSchema);
Hopefully you can tell from this that there is a main model, StationeryBox, which has a label and contains two compartments for DrawingTools and MeasuringTools which are nested schema. They in turn have their own labels and contain nested schemas for Pens and Rulers. The problem I'm having is inserted the 2nd level nesting, ie. pens/rulers. So based on the mongoose docs, creating the top level model, and pushing in the first nested objects works fine, then trouble strikes. For example:
// To create my stationery box - this works
var stationery = new StationeryBox({ label: 'My Stationery Box' });
// To add the nested compartments - this works
stationery.drawingTools.push({ label: 'My Pens' });
stationery.measuringTools.push({ label: 'My Rulers' });
// But this is wrong as 'stationery.drawingTools.pens' is undefined
stationery.drawingTools.pens.push({ color: 'red' });
stationery.drawingTools.pens.push({ color: 'black' });
And if I go back one step and try to insert the pens at the same time as the drawing tools:
// Also wrong - presumably the second level of nesting is the problem
stationery.drawingTools.push({
label: 'My Pens',
pens: [ // These object represent second levels of nested schema
{ color: 'red' },
{ color: 'black' }
]
});
I know this isn't a super-realistic example, but it's a simplified example of a real-world system I'm building and this was a easiest way to illustrate that.
The actual saving happens after this of course and I've left that out, but do I need to add these next levels in the save callback perhaps?
If anyone can tell me where I'm going wrong on this or point me in the right direction I'll buy you a nice cake (imaginary cake only I'm afraid, unless you live near me).

You are extremely close, the problem is actually in your Schema definitions. It all comes down to the difference between a Schema object and a Model object. When specifying mongoose Schema with embedded documents, you can only point to other Schema.
var DrawingToolsSchema = new Schema({
label: String,
pens: [Pen] // uh-oh, broken! Pen is a Model.
});
However, you do have this correct for your first level of embedded documents defined in StationeryBoxSchema.
var StationeryBoxSchema = new Schema({
label: String,
drawingTools: [DrawingToolsSchema], // yes! DrawingToolsSchema is a Schema
measuringTools: [MeasuringToolsSchema] // this one too.
});
This difference accounts for all of your unexpected behaviour later on.

Related

Mongoose: Delete Item - First One always gets deleted

I am completely new to the fields of Mongoose and MongoDB.
I am currently trying trying to remove one element from my database.
This is my code so far:
My issueModel:
var mongoose = require('mongoose'); // loading module for mongoose
var db = mongoose.connect('mongodb://localhost/issuedb');
var issueSchema = new mongoose.Schema({
title: String,
description: String,
priority: String,
status: String
});
// Constructor Function:
var issueModel = mongoose.model('issues', issueSchema); // have to give the
name of the collection where the element should be stored + Schema
// Export this Construction Function for this Module:
module.exports = issueModel; // careful: module != model !
My post method for using the delete method:
// creating the router for deleting one item:
router.post('/delete/:id', (req, res) => {
console.log(req.params.id);
issueModel.remove({id: req.params.ObjectId})
.setOptions({ single: true }).exec(function (err, deleted) {})
.then(issues => res.render('issue', {issues: issues}));
The thing i would like to do here is using the object id - which is correctly stored in req.params.ObjectID according to my console.log, and deleting the corresponding object.
But currently , when i have got a table with about 3-4 entries, always the first one gets deleted. Why is that? I am really TOTALLY new and really tried searching a lot, but i could not find any solution until now. I am happy about any tips that would help me.
What am i doing wrong?
The ID in the URL and the Object.ID are the same! Why is the first object deleted then, not the second or the third?
I am hopeless right now.
I also read about the remove() option not being really used in todays time. But we were told at university to use this method right now.
I also tried findOneByID and delete methods i found in the mongoose database.
If you need any more code please let me know!
You can use one of the convenience methods for this: findByIdAndRemove:
issueModel.findByIdAndRemove(req.params.ObjectId, function(err) {
if (err) { ... failed }
});
This will remove a whole document matching the ID which I think its what you want, if you want to a remove property from a document that's a different query.
If you don't use one of the convenience methods which just take IDs (have ById in them), then you have to convert your ID from a string to an ObjectId:
const { ObjectId } = require('mongodb');
issueModel.remove({ id: ObjectId(req.params.ObjectId) }).setOptions({ single: true })

Mongoose Reference for simple code reference values?

I'm designing a MongoDB schema to save a fairly large/nested document. I'm planning on embedding as much as possible into a single document, but wasn't sure what to do with code/lookup values. For example, if we have a code table representing "priority", with the possible values being:
low
medium
high
Is this something I should use a Mongoose reference for, and create a simple document to hold priority, eg something like:
var PrioritySchema = new Schema({
description: String
});
This would then be referenced with something like the following:
var AnotherSchema = new Schema({
name: String,
active: Boolean,
priority: { type: String, ref: 'Priority' }
});
Or is this overkill? The thing I want to avoid is directly storing these "descriptions" in the main/overall model, then having a requirement change sometime in the future. For example, someone decides that instead of "medium", we need to call it "somewhat". In that situation, I assume I'd be stuck doing some sort of data migration?
you can do this :
var PrioritySchema = new Schema({
description: String
});
and this
var AnotherSchema = new Schema({
name: String,
active: Boolean,
priority: { PrioritySchema }
});
But if you want what you described further I would advise you to do this instead :
var AnotherSchema = new Schema({
name: String,
active: Boolean,
priority: { type: Schema.Types.ObjectId, ref: 'Priority' } // see this : Schema.Types.ObjectId != String
});
Let's make it simple if you need those values to be cross-documents you need to use reference. If the values are only existing because of the parent document then you can choose embing.
For more information read this :
http://mongoosejs.com/docs/2.7.x/docs/embedded-documents.html
FYI : I used to struggle a lot with this. If you follow the path of embing all nested sub-document you will face a lot of "What Why I can't do that :'(. At the end I choosed the referencing way I felt more confortable with it. embing != referencing.

Embedded document without Array?

I understand how to embed documents in Mongoose, and they seem fairly straightforward if storing as arrays, for which the use case is fairly obvious:
var CommentSchema = new Mongoose.Schema({...});
var BlogPostSchema = new Mongoose.Schema({
comments : [CommentSchema],
});
But, what I don't see how to do after looking over the documentation forward and backward, is how to store a single sub-document that doesn't need or want to be in an Array.
var UserSchema = new Mongoose.Schema({...});
var BlogPostSchema = new Mongoose.Schema({
author: ??? // 'UserSchema' and UserSchema do not work here.
});
Is there any way to make this work? I don't want to just store the ObjectId, but rather, store a complete copy of the User record, but don't need or want an array.
You cannot embed schemas in this way, with the reasoning that those child docs would be confused with full documents, see this bug thread, where it is stated:
the reason we haven't added this support in the past is b/c this leaves us wondering if all pre-hooks will be executed the same way for the pseudo-child document as well as it implies that we can call save() on that child.
The answer here is to share not the schema, but just the definition.
var userdef = { name: String };
var UserSchema = new Schema(userdef);
var BlogPostSchema = new Schema({author: userdef});
This would result in a nested user object, without actually nesting the Schema.
Just sharing information doesn't support validation bubbling. And you may need validation of UserSchema also.
Instead I recommend array length validation
author: {type:[UserSchema], validate: function (arr) { return arr.length == 1 }},
UPDATE:
In case anyone comes across this now, as of Mongoose 4.2.0 single embedded subdocuments exist! :)
http://mongoosejs.com/docs/subdocs.html#single-embedded

How to avoid duplicated selection model specification in ExtJS grids?

We have recently switched from ExtJS 3.2 to 3.4 and found that grids with check box selection model stop working. It turns out that such configuration is not allowed any more:
var gridConfig = {
xtype: 'grid',
store: myStore,
columns:[
new Ext.grid.CheckboxSelectionModel(),
{
id: 'Name',
header: 'Inland Carrier',
dataIndex: 'Name'
}],
sm: new Ext.grid.CheckboxSelectionModel({
checkOnly: true
})
};
Instead selection model object must be created once and then passed both to column collection and sm property.
The problem now is that we have a very long configuration object with multitude of grids. Previously selection model was specified locally as per the sample above. But now we have to allot a variable for each selection model object, invent unique name for it, and keep these variables far away from the place where they are used. It's extremely inconvenient.
Is it possible somehow to specify selection model in one place? Or maybe to create it in one property initializer and reference this object in the second place?
you can add sm to cm after initialization of grid.
ie:
var gridConfig = {
xtype: 'grid',
store: myStore,
columns:[{
id: 'Name',
header: 'Inland Carrier',
dataIndex: 'Name'
}],
sm: new Ext.grid.CheckboxSelectionModel({
checkOnly: true
})
};
var grid = new Ext.grid.GridPanel( gridConfig );
grid.getColumnModel().config.unshift( grid.getSelectionModel() );

How do you use Mongoose without defining a schema?

In previous versions of Mongoose (for node.js) there was an option to use it without defining a schema
var collection = mongoose.noSchema(db, "User");
But in the current version the "noSchema" function has been removed. My schemas are likely to change often and really don't fit in with a defined schema so is there a new way to use schema-less models in mongoose?
I think this is what are you looking for Mongoose Strict
option: strict
The strict option, (enabled by default), ensures that values added to our model instance that were not specified in our schema do not get saved to the db.
Note: Do not set to false unless you have good reason.
var thingSchema = new Schema({..}, { strict: false });
var Thing = mongoose.model('Thing', thingSchema);
var thing = new Thing({ iAmNotInTheSchema: true });
thing.save() // iAmNotInTheSchema is now saved to the db!!
Actually "Mixed" (Schema.Types.Mixed) mode appears to do exactly that in Mongoose...
it accepts a schema-less, freeform JS object - so whatever you can throw at it. It seems you have to trigger saves on that object manually afterwards, but it seems like a fair tradeoff.
Mixed
An "anything goes" SchemaType, its flexibility comes at a trade-off of
it being harder to maintain. Mixed is available either through
Schema.Types.Mixed or by passing an empty object literal. The
following are equivalent:
var Any = new Schema({ any: {} });
var Any = new Schema({ any: Schema.Types.Mixed });
Since it is a schema-less type, you can change the value to anything
else you like, but Mongoose loses the ability to auto detect and save
those changes. To "tell" Mongoose that the value of a Mixed type has
changed, call the .markModified(path) method of the document passing
the path to the Mixed type you just changed.
person.anything = { x: [3, 4, { y: "changed" }] };
person.markModified('anything');
person.save(); // anything will now get saved
Mongoose Schema Types
Hey Chris, take a look at Mongous. I was having the same issue with mongoose, as my Schemas change extremely frequently right now in development. Mongous allowed me to have the simplicity of Mongoose, while being able to loosely define and change my 'schemas'. I chose to simply build out standard JavaScript objects and store them in the database like so
function User(user){
this.name = user.name
, this.age = user.age
}
app.post('save/user', function(req,res,next){
var u = new User(req.body)
db('mydb.users').save(u)
res.send(200)
// that's it! You've saved a user
});
Far more simple than Mongoose, although I do believe you miss out on some cool middleware stuff like "pre". I didn't need any of that though. Hope this helps!!!
Here is the details description: [https://www.meanstack.site/2020/01/save-data-to-mongodb-without-defining.html][1]
const express = require('express')()
const mongoose = require('mongoose')
const bodyParser = require('body-parser')
const Schema = mongoose.Schema
express.post('/', async (req, res) => {
// strict false will allow you to save document which is coming from the req.body
const testCollectionSchema = new Schema({}, { strict: false })
const TestCollection = mongoose.model('test_collection', testCollectionSchema)
let body = req.body
const testCollectionData = new TestCollection(body)
await testCollectionData.save()
return res.send({
"msg": "Data Saved Successfully"
})
})
[1]: https://www.meanstack.site/2020/01/save-data-to-mongodb-without-defining.html
Note: The { strict: false } parameter will work for both create and update.
Its not possible anymore.
You can use Mongoose with the collections that have schema and the node driver or another mongo module for those schemaless ones.
https://groups.google.com/forum/#!msg/mongoose-orm/Bj9KTjI0NAQ/qSojYmoDwDYJ