I have a article model defined as:
var ArticleSchema = new Schema({
type: String
,title: String
,content: String
,comments: [{
type: Schema.ObjectId
,ref: 'Comment'
}]
,replies: [{
type: Schema.ObjectId
,ref: 'Reply'
}]
,feedbacks: [{
type: Schema.ObjectId
,ref: 'Feedback'
}]
,meta: {
tags: [String] //anything
,apps: [{
store: String //app store, google play, amazon app store
,storeId: String
}]
,category: String
}
//normal, deleted, banned
, status: String
,statusMeta: {
createdBy: {
type: Schema.ObjectId
,ref: 'User'
}
,createdDate: Date
, updatedBy: {
type: Schema.ObjectId
,ref: 'User'
}
,updatedDate: Date
,deletedBy: {
type: Schema.ObjectId,
ref: 'User'
}
,deletedDate: Date
,undeletedBy: {
type: Schema.ObjectId,
ref: 'User'
}
,undeletedDate: Date
,bannedBy: {
type: Schema.ObjectId,
ref: 'User'
}
,bannedDate: Date
,unbannedBy: {
type: Schema.ObjectId,
ref: 'User'
}
,unbannedDate: Date
}
})
I have the following code to create a new article and save it.
var newArticle = new Article()
newArticle.status = helper.constant.articleTypes.other
newArticle.type = req.body.type
newArticle.category = req.body.category
newArticle.title = req.body.title
newArticle.content = req.body.content
newArticle.meta = req.body.meta
newArticle.statusMeta.createdBy = req.user
newArticle.statusMeta.createdDate = new Date
newArticle.save(function(err) {
if (err)
return next(err)
}
My pre save hook (helper function)
exports.batchValidationWrapper = function(schema, module, fieldPaths) {
for (var i = 0; i < fieldPaths.length; i++) {
var fieldPath = fieldPaths[i]
;(function(fieldPath) {
schema.pre('save', true, function(next, done) {
var self = this
var validationFunction = exports.validation[module][fieldPath]
var msg = validationFunction(self[fieldPath])
if (msg) {
self.invalidate(fieldPath, msg)
done(msg)
}
else {
done()
}
})
})(fieldPath)
}
}
and in my model i call helper
helper.batchValidationWrapper(ArticleSchema, 'article', [
'type'
,'title'
,'content'
,'comments'
,'replies'
,'feedbacks'
,'meta.tags'
,'meta.apps'
,'meta.category'
,'status'
,'statusMeta.createdBy'
,'statusMeta.createdDate'
,'statusMeta.deletedBy'
,'statusMeta.deletedDate'
,'statusMeta.undeletedBy'
,'statusMeta.undeletedDate'
,'statusMeta.bannedBy'
,'statusMeta.bannedDate'
,'statusMeta.unbannedBy'
,'statusMeta.unbannedDate'
])
helper.validation is defined as following. It's basically bunches of functions that receive input and return error message if any. If no error just return ''
exports.article = {
type: function(input) {
if (!input)
return 'type is requried'
return passIfAmongTypes('Article', input, constant.articleTypes)
}
,'statusMeta.createdDate': function(input) {
if (!input)
return 'created date is required'
return ''
}
}
I got error saying that created date is required when I try to create a new article.
I have tried newArticle.markModified('statusMeta') and newArticle.markModified(statusMeta.createdDate), both not working. I dont think it's necessary to mark it modified, since it's nested object type, not mixed type (from mongoose doc)
I also tried setting newArticle.statusMeta = {}, not working either.
When I set the break point, newArticle.statusMeta.createdDate is undefined
The reason I dont want to use default value for createdDate is that, setting default seems to happen before running pre('save') hook, which makes my validation code always fail
It's my own bug. Inside helper.js I used helper.funcName() instead of exports.funcName(). it's so hard to debug in javascript even with webstorm IDE.
Related
I am attempting to add a form result to an existing client in a collection and all form data variables being passed are added successfully, however, a default date variable is not being created and saved despite being in the schema.
Here is the schema:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// Create Schema
const FormSchema = new Schema({
formID: {
type: String
},
formName: {
type: String
},
date_completed: {
type: Date,
default: Date.now
},
formData: {
type: JSON
}
});
const ClientSchema = new Schema({
clientID: {
type: String,
required: true,
unique: true
},
dob: {
type: Date,
required: true
},
formResults: {
tags: [
{
type: FormSchema
}
]
}
});
module.exports = Client = mongoose.model('client', ClientSchema);
And here is the method posting the form results:
router.post('/:id', auth, (req, res) => {
Client.update(
{ clientID: req.params.id },
{
$push: {
formResults: {
$each: [
{
formID: req.body.formID,
formName: req.body.formName,
formData: req.body.formData
}
]
}
}
}
)
.then(() => res.json({ success: true }))
.catch(
err => res.status(404).json({ success: false }) && console.log(err)
);
});
I have tried forcing the date by passing date_completed: Date.now with the other form variables but this makes no difference. The results are still saved with no date variable listed. I have also tried dropping the collection and recreating it, this gave no changes. And I have checked the indexes for the collection, for which there is only _id and clientID.
Here is the data in saved in the database when executed and showing there is no date_completed: value.
Stored Data
At first glance your code is correct and should have no problem as it complies with the documentation and tutorials of mongoose, you can test this code:
// Create Schema
const FormSchema = new Schema({
formID: {
type: String
},
formName: {
type: String
},
date_completed: {
type: Date,
default: function() {
if (!this.date_completed) {
return Date.now();
}
return null;
}
},
formData: {
type: JSON
}
});
or:
var minuteFromNow = function(){
var timeObject = new Date();
return timeObject;
};
// Create Schema
const FormSchema = new Schema({
formID: {
type: String
},
formName: {
type: String
},
date_completed: {
type: Date,
default: minuteFromNow
},
formData: {
type: JSON
}
});
Let us also say this null is a valid value for a Date property, unless you specify required. Defaults only get set if the value is undefined, not if its falsy.
Honestly, I wasn't able to find any reason why it was not working. I waited a few days and the issue fixed itself. I don't believe there was anything wrong with the code, I think the issue was to do with MongoDB Atlas not updating results and displaying the dates that were being created but again I have no idea why this would be the case.
I am trying to make a method to fetch a "page" from the document base where the query matches _id or permalink.
The below code example returns a mongoose error:
'Cast to ObjectId failed for value "hello-world" at path "_id" for model "pages"'
Now, obviously the query isn't an ObjectId if the case is 'hello-world' or any other string permalink. So how do I go about using $or in this case, or is there a smarter way to go about it?
/**
* Describes methods to create, retrieve, update, and delete pages
* #returns void
*/
function Pages() {
this.pages = require('../models/pages')
this.users = require('../models/users')
require('mongoose').connect(require('../../config/database/mongodb').url)
}
/**
* Retrieve a page by permalink or id
* #param {string} pageQuery - id or permalink
* #callback {function} cFunction
*/
Pages.prototype.getOne = function(pageQuery, cFunction) {
this.pages.findOne({$or: [{ 'permalink': pageQuery }, { '_id': pageQuery }] })
.populate('author', 'email')
.select('title permalink body author')
.exec(function(error, result) {
if (error) {
cFunction(error)
return
}
cFunction(result)
})
}
Pages model
const mongoose = require('mongoose'),
Schema = mongoose.Schema,
ObjectId = Schema.ObjectId,
pages = new Schema({
title: { type: String },
permalink: { type: String, unique: true },
body: { type: String },
author: { type: ObjectId, ref: 'users' },
createdAt: { type: Date },
revisedAt: { type: Date }
})
.index({
title: 'text',
permalink: 'text',
body: 'text'
})
module.exports = mongoose.model('pages', pages)
Users model
const mongoose = require('mongoose'),
Schema = mongoose.Schema,
ObjectId = Schema.ObjectId,
users = new Schema({
email: { type: String, unique: true },
username: { type: String, unique: true },
password: { type: String },
createdAt: { type: Date }
})
.index({
email: 'text',
username: 'text'
})
module.exports = mongoose.model('users', users)
It looks like if you run new ObjectId(pageQuery) and it's not a valid ObjectId, it will throw an error telling you that (i.e. Error: Argument passed in must be a single String of 12 bytes or a string of 24 hex characters.)
In saying that, I would just use a try/catch block at the beginning of Pages.prototype.getOne to try and cast a pageQueryOid variable, and if you get to the catch block you know it's because pageQuery is not a valid ObjectId.
Using this method you no longer need an $or filter, but can just build your exact filter based on if pageQuery is a valid ObjectId. The following is just an example of what this might look like, but you can update it to meet your needs:
Pages.prototype.getOne = function(pageQuery, cFunction) {
var ObjectId = require('mongoose').Types.ObjectId
var pageQueryOid
try {
pageQueryOid = new ObjectId(pageQuery)
} catch(err) {
console.log("pageQuery is not a valid ObjectId...")
}
var filter
if (pageQueryOid) {
filter = { '_id': pageQueryOid }
} else {
filter = { 'permalink': pageQuery }
}
this.pages.findOne(filter)
.populate('author', 'email')
.select('title permalink body author')
.exec(function(error, result) {
if (error) {
cFunction(error)
return
}
cFunction(result)
})
}
I am using mongoose for my express.js project.
Here is my article model:
var ArticleSchema = new Schema({
type: String
,title: String
,content: String
,comments: [{
type: Schema.ObjectId
,ref: 'Comment'
}]
,replies: [{
type: Schema.ObjectId
,ref: 'Reply'
}]
,feedbacks: [{
type: Schema.ObjectId
,ref: 'Feedback'
}]
,meta: {
tags: [String] //anything
,apps: [{
store: String //app store, google play, amazon app store
,storeId: String
}]
,category: String
}
, status: String
,statusMeta: {
createdBy: {
type: Schema.ObjectId
,ref: 'User'
}
,createdDate: Date
, updatedBy: {
type: Schema.ObjectId
,ref: 'User'
}
,updatedDate: Date
,deletedBy: {
type: Schema.ObjectId,
ref: 'User'
}
,deletedDate: Date
,undeletedBy: {
type: Schema.ObjectId,
ref: 'User'
}
,undeletedDate: Date
,bannedBy: {
type: Schema.ObjectId,
ref: 'User'
}
,bannedDate: Date
,unbannedBy: {
type: Schema.ObjectId,
ref: 'User'
}
,unbannedDate: Date
}
}, {minimize: false})
This is my controller function
exports.createArticle = function(req, res, next) {
//1. save article
var newArticle = new Article()
newArticle.status = helper.constant.entityStatus.normal
newArticle.type = req.body.type
newArticle.category = req.body.category
newArticle.title = req.body.title
newArticle.content = req.body.content
//comments omit
//replies omit
//feedbacks omit
newArticle.meta = req.body.meta
newArticle.statusMeta.createdBy = req.user
newArticle.statusMeta.createdDate = new Date
newArticle.save(function(err) {
if (err)
return next(err)
//2. add article to user
req.user.articles.push(newArticle)
req.user.save(function(err) {
if (err)
return next(err)
//3. refetch article, done
var query = Article.findById(newArticle._id)
helper.populateCommonFieldsForQuery(query)
query.exec(function(err, article) {
if (err)
return next(err)
if (! article)
return next(helper.getGeneralError('unable to fetch saved article'))
return res.json(helper.dataAppendedWithMessage(article.toJSON(), 'success', 'successfully created article'))
})
})
})
}
When I create article with meta.tags to be an empty array, everything works. If the tags are not empty, then the save callback (function(err) {})is not fired.
Before save write this:
newArticle.markModified('meta');
it is a bug with my own code. inside helper.js I used helper.functionName() instead of exports.functionName(), which is undefined. It's so hard to catch such bugs in javascript
I have a model called Shop whos schema looks like this:
'use strict';
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var ShopSchema = new Schema({
name: { type: String, required: true },
address: { type: String, required: true },
description: String,
stock: { type: Number, default: 100 },
latitude: { type: Number, required: true },
longitude: { type: Number, required: true },
image: String,
link: String,
tags: [{ type: Schema.ObjectId, ref: 'Tag' }],
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now }
});
module.exports = mongoose.model('Shop', ShopSchema);
I want to use the array tags to reference to another model via ObjectId obviously. This set up works fine when I add ids into the property via db.shops.update({...}, {$set: {tags: ...}}) and the ids get set properly. But when I try to do it via the Express.js controller assigned to the model, nothing gets updated and there even is no error message. Here is update function in the controller:
// Updates an existing shop in the DB.
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Shop.findById(req.params.id, function (err, shop) {
if (err) { return handleError(res, err); }
if(!shop) { return res.send(404); }
var updated = _.merge(shop, req.body);
shop.updatedAt = new Date();
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(200, shop);
});
});
};
This works perfect for any other properties of the Shop model but just not for the tags. I also tried to set the type of the tags to string, but that didn't help.
I guess I am missing something about saving arrays in Mongoose?
It looks like the issue is _.merge() cannot handle merging arrays properly, which is the tags array in your case. A workaround would be adding explicit assignment of tags array after the merge, if it is ok to overwrite the existing tags.
var updated = _.merge(shop, req.body);
if (req.body.tags) {
updated.tags = req.body.tags;
}
Hope this helps.. If the workaround is not sufficient you may visit lodash forums.
I have an object:
{ SKU: 'TR1234',
Description: 'Item 1',
UoM: 'each',
client_id: '531382e3005fe0c926bd3957',
Meta: { Test: 'test1', Image: 'http://www.aol.com' } }
I'm trying to save it given my schema:
var ItemSchema = new Schema({
sku: {
type: String,
trim: true,
},
description: {
type: String,
trim: true,
},
company_id: {
type: Schema.ObjectId,
ref: 'Client',
},
createdOn: {
type: Date,
default: Date.now
},
updatedOn: {
type: Date,
default: Date.now
}
}, {versionKey: false});
But it doesn't save and I assume it's because of the capitalized key names. However, those are dynamically generated from a CSV which is parsed with https://github.com/Keyang/node-csvtojson
Ideas?
You can also just use a setter in your mongoose schema, like that:
function toLower (v) {
return v.toLowerCase();
}
var UserSchema = new Schema({
email: { type: String, set: toLower }
});
Just apply it to your fields.
There is also one more approach, just:
email : { type: String, lowercase: true }
Update for keys:
If you would like to change keys, you should the approach likes 'ecdeveloper' mentioned below. My answer was for values, so it makes sense to give this reputation to 'ecdeveloper'. Sorry for confusing.
Here is one more approach without creating a new object:
Object.prototype.keysToUpper = function () {
var k;
for (k in this) {
if (this.hasOwnProperty(k))
this[k.toLowerCase()] = this[k];
delete this[k];
}
return this;
};
What about calling toLowerCase() on each key from your object, and build a new object with lower case keys?
// Assumy your object name is obj
var newObj = {};
Object.keys(obj).forEach(function(key) {
newObj[key.toLowerCase()] = obj[key];
});
// Here you can save your newObj