Adding fields to model which derived from Mongoose schema - node.js

I have a Mongoose schema that looks like this:
ManifestSchema = new Schema({
entries: [{
order_id: String,
line_item: {}, // <-- resolved at run time
address: {},// <-- resolved at run time
added_at: Number,
stop: Number,
}]
}, {collection: 'manifests', strict: true });
and somewhere in the code I have this:
Q.ninvoke(Manifests.findById(req.params.id), 'exec')
.then(function(manifest)
{
// ... so many things, like resolving the address and the item information
entry.line_item = item;
entry.address = order.delivery.address;
})
The issue that I faced is that without defining address and line_item in the schema, when I resolved them at run time, they wouldn't returned to the user because they weren't in the schema...so I added them...which cause me another unwanted behavior: When I saved the object back, both address and line_item were saved with the manifest object, something that I would like to avoid.
Is there anyway to enable adding fields to the schema at run time, but yet, not saving them on the way back?
I was trying to use 'virtuals' in mongoose, but they really provide what I need because I don't create the model from a schema, but it rather returned from the database.

Call toObject() on your manifest Mongoose instance to create a plain JavaScript copy that you can add extra fields to for the user response without affecting the doc you need to save:
Q.ninvoke(Manifests.findById(req.params.id), 'exec')
.then(function(manifest)
{
var manifestResponse = manifest.toObject();
// ... so many things, like resolving the address and the item information
entry.line_item = item;
entry.address = order.delivery.address;
})

Related

Express Route Parameters creating duplicate custom paths

I am creating a to-do list app and I am trying to including custom routes using 'Express Route Parameters'. The program worked fine up until this point, but as soon as I tried to introduce these custom paths and log it to the console, or even add it to the database, the very first path gets added twice or logged twice. The paths added later are not duplicated, though.
app.get("/:customListName", function(req,res)
{
console.log(req.params.customListName);
/*const list = new List
({
name: customListName,
items: defaultItems
});
list.save();*/
});
For example, if the custom path added is called "home", i.e., "localhost:3000/home" and I am trying to console.log the name of the path, it will print "home" twice. Later, if I add paths like "work", "new" etc they are added (and printed) only once. Sometimes this error is also coming up:
BulkWriteError: E11000 duplicate key error collection: wolDB.items index: _id_ dup key: { _id: ObjectId('5ef4ad2110f45d54f143fa19') }
I have tried dropping the database and start afresh, drop indexes, even tried with a new database; but it seems the problem is not in the database because even without pushing it to the database, the problem persists. I tried coding the whole thing afresh, but the problem persists.
I have tried findOne() of mongoose also, but when I try to print whether the given route exists or not, for the first one it just prints "exists" twice.
List.findOne({name: customListName}, function(err, foundList)
{
if(!err)
{
if(!foundList)
console.log("Doesn't exist");
else
console.log("Exists");
}
});
Here's the GitHub link:
https://github.com/sebanti10/todolist.git
if you don't need the list and want to directly save to the database try using
List.create({
name: customListName,
items: defaultItems
})
Look at your schemas. Nowhere are you using new mongoose.Schema(). When you use mongoose.model("Item", itemsSchema) you need to pass in an actual schema, not just any object.
You need to change your so called schemes to something along the lines of:
const itemsSchema = new Schema({
name: String
});
Schema is available under mongoose, so you can either use mongoose.Schema directly or grab it const Schema = mongoose.Schema; or const { Schema } = mongoose;

Automatically manipulating argument for Mongoose Document constructor

Let's say I have have this model:
const employeeSchema = new Schema({
name: String,
age: Number,
employeeData: {
department: String,
position: String,
lastTraining: Date
}
});
const Employee = mongoose.model('employee', employeeSchema);
In the database, the only thing that is going to be saved is something that looks like this:
{
_id: ...
name: 'John Smith',
age: 40,
employeeCode: '.... '
}
What's going on is that by some business rules, the employeeData info, which is coming from the reqeust body, is going through some function that compiles out of it the employeeCode, and when saving to the database I just use to the employeeCode.
Right now, the way I am implementing this is using statics. So, I have in the model the follwing:
employeeSchema.statics.compileEmployeeCode = (doc) => {
if (!doc.employeeData) {
doc.employeeCode= compileCode(doc.employeeData);
delete doc.employeeData;
}
return doc;
}
And then, I need to remember, for each call that receives info from the client, to call this function before creating the document (an instance of the model):
const compiledDoc = Employee.compileEmployeeCode(req.body);
const employee = new Employee(comiledDoc);
My question is: is there a way to automatically invoke some function that compiles the code out of the data any time I create a document like that, so I won't need to remember to always call on the static method beforehand?
Middlaware is what you are looking for. You need to create a function that will set a pre-save hook on the schema (which will be triggered every time before saving a new document) and to plug this function into the schema.
function compileEmployeeCode (schema) {
schema.pre('save', next => {
if (this.employeeData) {
this.employeeCode= compileCode(this.employeeData);
delete this.employeeData;
next();
}
});
}
employeeSchema.plugin(compileEmployeeCode);
OK. It was really hard but I finally managed to find the solution. The trick is to use a setter on a specific path. Each field in the schema is of type SchemaType which can have a setter apply on it:
https://mongoosejs.com/docs/api.html#schematype_SchemaType-set
Anyway, if I want to make it possible for the request to enter an object that will be converted to some other format, say a string, I would need to define the schema like this:
const employeeSchema = new Schema({
name: String,
age: Number,
employeeCode: {
type: String,
set: setCodeFromObj,
alias: 'employeeData'
}
});
The setter function I'm using here looks something like this (I'm omitting here all the error handling and the like to keep this short:
function setCodeFromObj(v) {
const obj = {};
obj.department = v.department;
obj.position = v.position;
obj.lastTraining = v.lastTraing
// breaking the object to properties just to show that v actually includes them
return compileEmployeeCode(obj);
}
I used an alias to make the name visible to the user different from what is actually saved in the database. I could have also done that using virtuals or just design the system a bit differently to use up the same name.

Unable to get the value of particular key in mongoose if that key is not present in Schema

UserEventsInfo = new mongoose.Schema({
name: String,
username: String,
event_movie:[String],
event_tour:[String],
event_restaurant:[String],
event_lifetimeevents:[String]
},{strict : false});
I am able to insert new key-value pair other than defined in the schema
but when I try to read the value of that key. I can't. I am using the following code.
UserEventsDetails.find({username:username},function(err,docs){
if(!docs.length)
{
res.send('datanotavailable');
}
else{
res.send(docs[0][eventname]);
}
});
Here eventname is a variable.
When I add that key in the schema it returns the value i.e. work's fine.
Otherwise it is not returning any value.
Looks like there was an issue submitted like this to mongoose. Here is there response:
The benefit we see in a schemaless database is the ability for our data model to evolve as fast as our features require it, without a linear impact on performance and slower deployment cycles with needless migrations.
If you don't want your data to be normalized and validated prior to saving, then you don't need a tool like Mongoose, you can use the driver directly.
After a little digging there is a way to do this, but you will need to have a field with type Schema.Types.Mixed. So it would look like this:
var schema = new Schema({
mixed: Schema.Types.Mixed,
});
var Thing = mongoose.model('Thing', schema);
var m = new Thing;
m.mixed = { any: { thing: 'i want' } };
m.save(callback);
To do a find on a mixed this SO question answers that.
****EDIT
forgot to link the documentation of mixed types

Saving Schema-less Records with Mongoose?

So I've been trying to save CSP reports into Mongoose with a Mixed schema and have ran into a snag of sorts.
If I try to save anything using the "schema-less" way, it only saves the default _v and _id fields
ViolationSchema = new Schema({});
Violation = mongoose.model('CSPViolation', ViolationSchema);
... wait for POST ...
new Violation( req.body ).save( callback );
// { _id : <some_id>, _v : <some_hash> }
If I set a field in the schema to be Mixed and add a .markModified() to the field, it will save.
ViolationSchema = new Schema({ report : { type : Mixed } });
Violation = mongoose.model('CSPViolation', ViolationSchema);
... wait for POST ...
var v = new Violation( { report : req.body } );
v.markModified('report');
v.save( callback );
// report saved under v.report.<actual_report>
I thought about using native MongoDB-style collection.insert, however it doesn't look like the model has an insert method (nor the schema for that matter).
I suppose I could also go over each key in the report I'm saving and manually mark it as modified, but I'd like to avoid that just to store a report such as this.
Any ideas how I can blindly save a mixed schema type using Mongoose?
It looks like this can be done by setting { strict : false } on the schema. This ensures that Mongoose will save any fields that weren't declared in the original schema.
Normally this isn't something you would enable on 95% of your data, it just fits perfectly with what I'm trying to do currently.
Example
ViolationSchema = new Schema({ type: Mixed }, { strict : false });
Violation = mongoose.model('CSPViolation', ViolationSchema);
... wait for POST ...
new Violation( req.body ).save( callback );
// Saves with full data

Mongoose key/val set on instance not show in JSON or Console.. why?

I have some information on my mongoose models which is transient. For performance reasons I dont wish to store it against the model.. But I do want to be able to provide this information to clients that connect to my server and ask for it.
Here's a simple example:
var mongoose = require('mongoose'),
db = require('./dbconn').dbconn;
var PersonSchema = new mongoose.Schema({
name : String,
age : Number,
});
var Person = db.model('Person', PersonSchema);
var fred = new Person({ name: 'fred', age: 100 });
The Person schema has two attributes that I want to store (name, and age).. This works.. and we see in the console:
console.log(fred);
{ name: 'fred', age: 100, _id: 509edc9d8aafee8672000001 }
I do however have one attribute ("status") that rapidly changes and I dont want to store this in the database.. but I do want to track it dynamically and provide it to clients so I add it onto the instance as a key/val pair.
fred.status = "alive";
If we look at fred in the console again after adding the "alive" key/val pair we again see fred, but his status isnt shown:
{ name: 'fred', age: 100, _id: 509edc9d8aafee8672000001 }
Yet the key/val pair is definitely there.. we see that:
console.log(fred.status);
renders:
alive
The same is true of the JSON representation of the object that I'm sending to clients.. the "status" isnt included..
I dont understand why.. can anyone help?
Or, alternatively, is there a better approach for adding attributes to mongoose schemas that aren't persisted to the database?
Adding the following to your schema should do what you want:
PersonSchema.virtual('status').get(function() {
return this._status;
});
PersonSchema.virtual('status').set(function(status) {
return this._status = status;
});
PersonSchema.set('toObject', {
getters: true
});
This adds the virtual attribute status - it will not be persisted because it's a virtual. The last part is needed to make your console log output correctly. From the docs:
To have all virtuals show up in your console.log output, set the
toObject option to { getters: true }
Also note that you need to use an internal property name other than status (here I used _status). If you use the same name, you will enter an infinite recursive loop when executing a get.
Simply call .toObject() on the data object.
For you code will be like:
fred.toObject()
This has been very helpful. I had to struggle with this myself.
In my case, I was getting a document from mongoose. When I added a new key, the key was not visible to the object if I console.log it. When I searched for the key (console.log(data.status), I could see it in the log but not visible if I logged the entire object.
After reading this response thread, it worked.
For example, I got an object like this one from my MongoDB call:
`Model.find({}).then(result=> {
//console.log(result);// [{ name:'John Doe', email:'john#john.com'}];
//To add another key to the result, I had to change that result like this:
var d = result[0];
var newData = d.toJSON();
newData["status"] = "alive";
console.log(newData);// { name:'John Doe', email:'john#john.com', status:'alive'};
}).catch(err=>console.log(err))`
Hope this helps someone else.
HappyCoding

Resources