Node Mongoose - saving embedded documents on API once receiving mongoId - node.js

I would like to know the best approach to solve the current scenario.
I've got a node API which uses mongoose and bluebird. And some Android clients will post "movement" entities to it.
(Question at the end).
Let's say movement-model.js exports the Schema, and looks like this:
"use strict";
const mongoose = require('mongoose');
const _movementSchema = {
movementId: { type: Number, requried: true },
relMovementId: Number,
_party: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'Party' }
}
module.exports = mongoose.Schema(_movementSchema, {collection: 'movement'});
And related exported Schema on party-model.js is as follows:
"use strict";
const mongoose = require('mongoose');
const _partySchema = {
active: { type: Boolean, default: true },
name: { type: String, trim: true, required: true },
logo: { type: Buffer },
coordenates: { lat: Number, long: Number },
startOn: { type: Date, required: true },
endOn: { type: Date, required: true }
}
module.exports = mongoose.Schema(_partySchema, {collection: 'party'});
Android client would send the JSON with ObjectId and not full populated object. So when the POST comes, I'm using it directly (i.e: let _movement = req.body;) and on the movement-dao.js I've got the createNew method and I'm exporting the Model:
"use strict";
const mongoose = require('mongoose');
const Promise = require('bluebird');
mongoose.Promise = Promise;
const movementSchema = require('../model/movement-model');
movementSchema.statics.createNew = (movement) => {
return new Promise((resolve, reject) => {
if (!_.isObject(movement)) {
return reject(new TypeError('Movement is not a valid object.'));
}
let _something = new Movement(movement);
_something.save((err, saved) => {
err ? reject(err)
: resolve(saved);
});
});
}
const Movement = mongoose.model('Movement', movementSchema);
module.exports = Movement;
What I want to accomplish is to: save the movement collection with the _party as the full party document is at the moment of the save, I mean an embedded document of a copy of the Party document, which will not be affected by the updates done to the Party document in the future.
While I cannot change the Android Client, so I will still be getting only the ObjectId from it.
JSON example of what Android client will post: {"movementId":1, "relMovementId":4138, "_party":"58dbfe26194cfc5a9ec9b7c5"}
I'm confused now, and not sure if due to the way Android is posting the JSON, I need two schemas; one for the object received (i.e: with ObjectId and ref to Party) and a second one for the object persisted (i.e: with the schema referenced _party: Party.Schema) or if I could do something simpler as some populate prior to save... or what.

For the sake of closing this up:
I've implemented one of the approaches I had in mind while writing the question. Movement schema changed so that: _party: Party.Schema
When I get a POST to create a new movement I do a getById and use the result of that exec to populate the value as an embedded doc.

Related

How to do field validations in a mongoose schema based on other fields in the same schema?

Let's say we have :
const mealSchema = Schema({
_id: Schema.Types.ObjectId,
title: { type: string, required: true },
sauce: { type: string }
});
How can we make sauce mandatory if title === "Pasta" ?
The validation needs to work on update too.
I know that a workaround would be
Find
update manually
Then save
But the risk is that if I add a new attribute (let's say "price"), I forget to update it manually too in the workaround.
Document validators
Mongoose has several built-in validators.
All SchemaTypes have the built-in required validator. The required validator uses the SchemaType's checkRequired() function to determine if the value satisfies the required validator.
Numbers have min and max validators.
Strings have enum, match, minlength, and maxlength validators.
For your case you could do something like this
const mealSchema = Schema({
_id: Schema.Types.ObjectId,
title: { type: string, required: true },
sauce: {
type: string,
required: function() {
return this.title === "pasta"? true:false ;
}
}
});
If the built-in validators aren't enough, you can define custom validators to suit your needs.
Custom validation is declared by passing a validation function. You can find detailed instructions on how to do this in the SchemaType#validate().
Update Validators
this refers to the document being validated when using document validation. However, when running update validators, the document being updated may not be in the server's memory, so by default the value of this is not defined. So, What's the solution?
The context option lets you set the value of this in update validators to the underlying query.
In your case, we can do something like this:
const mealSchema = Schema({
_id: Schema.Types.ObjectId,
title: { type: string, required: true },
sauce: { type: string, required: true }
});
mealSchema.path('sauce').validate(function(value) {
// When running update validators with
// the `context` option set to 'query',
// `this` refers to the query object.
if (this.getUpdate().$set.title==="pasta") {
return true
}else{
return false;
}
});
const meal = db.model('Meal', mealSchema);
const update = { title:'pasta', sauce:false};
// Note the context option
const opts = { runValidators: true, context: 'query' };
meal.updateOne({}, update, opts, function(error) { assert.ok(error.errors['title']); });
Not sure if this answers your question. Hope this adds some value to your final solution.
Haven't tested it, pls suggest an edit if this solution needs an upgrade.
Hope this helps.

How to create mongoose model for GridFS collection?

So I am trying to create a mongoose model for GridFS collection but to no success.
let bucket;
(async () => {
try {
await mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true });
const { db } = mongoose.connection;
bucket = new mongoose.mongo.GridFSBucket(db, { bucketName: 'tracks' });
}
catch(err) {
console.log(err);
}
})();
Here is my course schema and model:
const courseSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
tracks: [{
type: mongoose.Types.ObjectId,
ref: 'tracks.files'
}],
});
const Course = mongoose.model('Course', courseSchema);
And here is my tracks schema and model:
const trackSchema = new mongoose.Schema({
length: { type: Number },
chunkSize: { type: Number },
uploadDate: { type: Date },
filename: { type: String, trim: true, searchable: true },
md5: { type: String, trim: true, searchable: true },
}, { collection: 'tracks.files', id: false });
const Track = mongoose.model('Track', trackSchema);
And I am getting this error:
MongooseError [MissingSchemaError]: Schema hasn't been registered for model "tracks.files".
when I run this:
Course.findById('5d5ea99e54fb1b0a389db64a').populate('tracks').exec()
.then(test => console.log(test))
.catch(err => console.log(err));
There is absolutely zero documentation on these stuff and I am going insane over it. Am I the first person to ever save 16 MB+ files in Mongodb? Why is it so difficult to implement this? Can anyone please guide me to the right direction.
Late response but try replacing ref: 'tracks.files' with ref: 'trackSchema'.
The ref field is referenced within populate('trackSchema') and must be a reference to another Schema. Check saving-refs out if you want to learn more about populating fields and references in Mongoose.
I also wouldn't recommend creating schemas of any sort for implementing GridFS, I'd let Mongo take care of this as it may lead to file corruption or missing/outdated documents if implemented incorrectly.
As for the lack of official documentation for GridFS, especially GridFS with Mongoose, I used Mongo's native GridFsBucket class (as of my knowledge, no still maintained official Mongoose-GridFS API exist), the best docs I used when attempting this myself was gridfs. With a tutorial here streaming.
To use Mongo's native GridFSBucket with Mongoose just grab its mongo property and Mongoose's connection instance from its connection property.
const mongoose = require(mongoose);
var gridFSBucket = new mongoose.mongo.GridFSBucket(mongoose.connection.db, {
bucketName: 'images'
});

MongoDB : Missing the key _id in the item

I have been given some code to modify. It is a Node.js app using Mongoose to interact with a MongoDb instance. In Mongoose several schemas were already set up and I've added a few. Among those are these two schemas which break apart a previously existing schema (which was working fine with small data):
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var MapConvertedProjectSchema = new Schema(
{
project_id : {
type: String,
default: ""
},
dataset_id : {
type: String,
default: ""
},
properties:{
type: {},
default: {}
}
});
MapConvertedProjectSchema.pre('save', function(next) {
next();
});
mongoose.model('MapConvertedProject', MapConvertedProjectSchema);
and
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var MapConvertedLayerSchema = new Schema(
{
parent_id:
{
type: mongoose.Schema.Types.ObjectId
},
class:
{
type: String,
default: 'MapLayer',
trim: true
},
properties:
{
type: {},
default: {}
}
});
//Hook a pre save method to clean date
MapConvertedLayerSchema.pre('save', function(next) {
next();
});
mongoose.model('MapConvertedLayer', MapConvertedLayerSchema);
I use the MapConvertedLayer schema like so:
var mongoose = require('mongoose');
var LayerConverted = mongoose.model('MapConvertedLayer');
var newLayer = new LayerConverted();
//newLayer._id is automatically populated with a value
//... add other properties
newLayer.save(function(err)
{
if(err)
{
//...
}
});
This works without any issues that I can discern. However if I try similar code with MapConvertedProject I get an error:
var mongoose = require('mongoose');
var ProjectConverted = mongoose.model('MapConvertedProject');
var map_converted = new ProjectConverted();
//map_converted._id is undefined
//I tried adding the comment below to create an _id manually, but it didn't make a difference when I tried to save
//map_converted._id = mongoose.Types.ObjectId();
console.log("Project Converted ID: " + map_converted._id);
//... fill out the other properties on the schema
map_converted.save(function(err)
{
if(err)
{
//...
}
});
The save generates this error:
ValidationException: One or more parameter values were invalid: Missing the key _id in the item
Does anyone know what is causing this?
I figured this out. There was another place in the code that had a dynamoose model with the same name that was messing things up. I was able to remove all references to dynamoose since it doesn't appear to be used anymore and that cleared up this issue.

Storing a copy of a document embedded in another document in MongoDB via Mongoose

We have a requirement to store a copy of a Mongo document, as an embedded subdocument in another document. It should have a reference to the original document. The copied document needs to be a deep copy, like a snapshot of the original.
The original document's schema (defined with Mongoose) is not fixed -
it currently uses a type of inheritance to allow different additions to the Schema depending on "type".
Is there a way to such a flexible embedded schema within a Mongoose model?
Is it something that needs to be injected at runtime, when we can know
the schema?
The models / schemas we have currently look like this:
///UserList Schema: - this should contain a deep copy of a List
user: {
type: ObjectId,
ref: 'User'
},
list: {
/* Not sure if this is a how we should store the reference
type: ObjectId,
ref: 'List'
*/
listId: ObjectId,
name: {
type: String,
required: true
},
items: [{
type: ObjectId,
ref: 'Item'
}]
}
///List Schema:
name: {
type: String,
required: true
},
items: [{
type: ObjectId,
ref: 'Item'
}],
createdBy: {
type: ObjectId,
ref: 'User'
}
The code we currently have uses inheritance to allow different item types. I realise this technique may not be the best way to achieve the flexibility we require and is not the focus of my question.
///Item Model + Schema
var mongoose = require('mongoose'),
nodeutils = require('util'),
Schema = mongoose.Schema,
ObjectId = Schema.Types.ObjectId;
function ItemSchema() {
var self = this;
Schema.apply(this, arguments);
self.add({
question: {
type: String,
required: true
}
});
self.methods.toDiscriminator = function(type) {
var Item = mongoose.model('Item');
this.__proto__ = new Item.discriminators[type](this);
return this;
};
}
nodeutils.inherits(ItemSchema, Schema);
module.exports = ItemSchema;
I think you just need to create an empty {} object for the document in your parent mongoose schema. This way you´ll be able to store any object with a hardcopy of all it´s data.
parentobj : {
name: Sring,
nestedObj: {}
}
I think at this point, what you´ll need is to mark your nested objet as modified before you save it. Here is an example of my mongoose code.
exports.update = function(req, res) {
User.findById(req.params.id, function (err, eluser) {
if (err) { return handleError(res, err); }
if(!eluser) { return res.send(404); }
var updated = _.merge(eluser, req.body);
//This makes NESTEDDATA OBJECT to be saved
updated.markModified('nestedData');
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(200, eluser);
});
});
};
In addition, if you need an array of different documents in nestedDocument, the right way is this one:
parentobj : {
name: Sring,
nestedObjs: [Schema.Types.Mixed]
}
Please check Mongoose Schema Types carefully
EDIT
As you said, I´ll add you final solution as including ItemSchema in the nestedObj array definition to clarifythe type of the object to a determined one..
var ItemSchema = new Schema({
item1: String,
item2: String
});
var parentobj = new Schema({
name: Sring,
nestedObj: [ItemSchema]
});
EDIT 2:
Remember adding new Items to the nestedArray, must be done with nestedArray.push(item)
regards!!

Mongoose ODM - failing to validate

I'm trying to perform validation without saving. The API documentation shows that there's a validate method, but it doesn't seem to be working for me.
Here's my schema file:
var mongoose = require("mongoose");
var schema = new mongoose.Schema({
mainHeading: {
type: Boolean,
required: true,
default: false
},
content: {
type: String,
required: true,
default: "This is the heading"
}
});
var moduleheading = mongoose.model('moduleheading', schema);
module.exports = {
moduleheading: moduleheading
}
..and then in my controller:
var moduleheading = require("../models/modules/heading").moduleheading; //load the heading module model
var ModuleHeadingo = new moduleheading({
mainHeadin: true,
conten: "This appears to have validated.."
});
ModuleHeadingo.validate(function(err){
if(err) {
console.log(err);
}
else {
console.log('module heading validation passed');
}
});
You may notice that the parameters I'm passing in are called 'mainHeadin' and 'conten' instead of 'mainHeading' and 'content'. However, even when I do the call to validate() it never returns an error.
I'm obviously using validate incorrectly - any tips? The mongoose documentation is really lacking!
Thanks in advance.
Your validation will never fail because you've created default attributes for both mainHeading and content in your schema. In other words, if you don't set either of those properties, Mongoose will default them to false and "This is the heading" respectively - i.e. they will always be defined.
Once you remove the default property, you'll find that Document#validate will work as you initially expected. Try the following for your schema:
var schema = new mongoose.Schema({
mainHeading: {
type: Boolean,
required: true
},
content: {
type: String,
required: true
}
});

Resources