I'm trying to figure out the best way to track changes to fields when using mongoose.js. For example, every time the name field on an object is set, I want to add a new entry to that object's history (as an embedded document) that looks something like { field: 'name', previous: 'foo', current: 'bar', date: '3/06/2012 9:06 am' }.
I started by trying to use a plug-in that hooks .pre('save') but I can't figure out which fields have been modified without grabbing the old value from the database and comparing them myself. Then I thought I could use custom setters, but I ran into the same problem - I don't know which field was modified. Currently I'm left with doing something like this which hard codes the field name into to the setter:
var comment = new Schema({
name : { type: String, set: trackName },
history : [Change]
});
var trackName = function(val) {
var change = new Change;
change.field = 'name';
change.previous = this.name;
change.current = val;
change.date = Date.now();
this.history.push(change);
return val;
}
But this means I need a custom setter for each field name that I want to track. I'm guessing there must be a better way to accomplish this.
Looks like i missed 'Document.modifiedPaths'. This does exactly what I need to determine which fields have been modified.
Related
I have a search in SuiteScript 2.0 that's working fine. But for each record the search brings back, I want to update a particular field (I use it elsewhere to determine that the record has been examined). It's potentially a very large result set, so I have to page it. Here's my code:
var sResult = mySearch.runPaged({pageSize: 10});
for (var pageIndex = 0; pageIndex < sResult.pageRanges.length; pageIndex++)
{
var searchPage = sResult.fetch({ index: pageRange.index });
searchPage.data.forEach(function (result)
{
var name = result.getValue({ name: "altname"})
result.setValue({
name: 'tracker',
value: new Date()
})
});
}
You can see where I have a call to result.setValue(), which is a non-existent function. But it shows where I want to update the 'tracker' field and what data I want to use.
Am I barking up the wrong tree entirely here? How do I update the field for each result returned?
As Simon says you can't directly update a search result, but you can use submitFields method.
This example is from NetSuite documentation:
var otherId = record.submitFields({
type: 'customrecord_book', //record Type
id: '4', // record Id
values: {
'custrecord_rating': '2'
}
});
This approach will save more governance than load and save the record.
AFAIK You can't directly update a search result and write the value back to the record, the record needs to be loaded first. The snippet doesn't say what the type of record it is you're searching for or want to load, but the general idea is (in place of your result.setValue line):
var loadedRecord = Record.load({type:'myrecordtype', id:result.id});
loadedRecord.setValue( {fieldId:'tracker', value: new Date() });
loadedRecord.save();
Keep in mind SuiteScript governance and the number of records your modifying. Load & Save too many and your script will terminate earlier than you expect.
Bad design: instead of using result.setValue inside the iteration, push those to an "update" array then after the data.forEach have another function that loops thru the update array and processes them there with record.submitFields()
Be careful of governance....
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.
To perform SubmitFields onto standard Netsuite Records (i.e. Purchase Orders) it is something like this:
var poId = context.key;
var id = record.submitFields({
type: record.Type.PURCHASE_ORDER,
id: poId,
values: {
custbody_someField: someValue
},
options: {
enableSourcing: false,
ignoreMandatoryFields : true
}
});
What is the type field for Custom Records? I tried the ID of the Custom Record, but it doesn't work:
e.g.
type: record.Type.customrecord_my_record_id
I don't know what the 'official' answer is. The fake enum types don't have any custom record references that I was able to find. Setting the type to the string that is the id of the custom record works for me. (No record.Type. prefix though)
... type: "customrecord_my_record_id", ...
That is true that the references are only for standard record types. You can alternatively get all enums into a variable and log it using
var recordTypesEnums = Object.keys(record.Type);
//you may log recordTypesEnums array
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
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