mongoose virtual attribute not populating in some cases where populate() is used - node.js

I'm trying to populate the job.creator and use the virtual attribute from the User object (user.profile) to give only public information for the job creator.
/**
* User Schema
*/
var UserSchema = new Schema({
favorites: {
jobs: [{ type: Schema.ObjectId, ref: 'Job', index: true }]
}
});
// Public profile information
UserSchema
.virtual('profile')
.get(function () {
return {
favorites: this.favorites,
profileImageUrls: this.profileImageUrls //also not being populated
};
});
UserSchema
.virtual('profileImageUrls')
.get(function(){
var defaultUrl = cfg.home + '/images/cache/default-profile-img.png'
, smallUrl = cfg.home + '/images/cache/default-profile-img-small.png';
return {
small: smallUrl
, default: defaultUrl
};
});
/**
* Job Schema
*/
var JobSchema = new Schema({
creator: { type: Schema.ObjectId, ref: 'User', index: true, required: true },
});
When I try to get the virtual attribute .profile from the job.creator object, I am missing some values:
//controller
Job.populate(job, { path: 'creator', select: '-salt -hashedPassword' }, function(err, job){
if ( job.creator ) {
job.creator = job.creator.profile; //this is missing some attributes
}
return res.json(job);
})
{
//...
creator: {
favorites: {
jobs: [ ] //this is not being populated
}
}
}
Its also missing job.creator.profileImageUrls which is a virtual attribute off the User object.

I'd recommend not doing the job.creator = job.creator.profile line, that doesn't really jibe well with how Mongoose works. Also, where is profileImageUrls coming from? Doesn't seem to be in the schema.

I don't know if this is desirable, but I got it working with the following:
_.each(jobs, function(job, i){
if ( job.creator ) {
job._doc.creator = job.creator.profile;
}
});
res.json(jobs);

Related

Mongoose Querying on Array of Object or Virtual for array of Object

I have the following Mongoose schema as defined on this page
const AuthorSchema = new Schema({
name: String
});
const BlogPostSchema = new Schema({
title: String,
author: { type: mongoose.Schema.Types.ObjectId, ref: 'Author' },
comments: [{
author: { type: mongoose.Schema.Types.ObjectId, ref: 'Author' },
content: String
}]
});
Now I want to create a virtual on AuthorSchema to get the BlogPosts which have comments of that author.
I tried creating a virtual function but with no success
Both virtual and methods can solve your problems:
Virtual:
// Model
AuthorSchema.virtual('blogPosts').get(function () {
return this.model('BlogPost').find({
comments: { $elemMatch: { author: this._id } },
})
});
// Usage
const author = await Author.findById(id);
const blogPosts = await author.blogPosts;
Methods:
// Model
AuthorSchema.method.blogPosts= function (cb) {
return this.model('BlogPost').find({
comments: { $elemMatch: { author: this._id } },
}, cb)
};
// Usage
const author = await Author.findById(id);
const blogPosts = await author.blogPosts();

how to get comments with their author with mongoose, nodejs

i am creating items inside collections and in each items authors are able to leave a comment. i want retrieve comment with their author . so that i referenced author id inside comment schema and i am only getting author id when i populate author in my get request. So can anyone help to get comments with its author information?
ITEM SCHEMA
import mongoose from "mongoose";
const { Schema, model } = mongoose;
const itemSchema = new Schema(
{
name: { type: String },
comments: [
{
owner: { type: Schema.Types.ObjectId, ref: "User" },
text: { type: String },
},
],
owner: { type: Schema.Types.ObjectId, ref: "User" },
collections: { type: Schema.Types.ObjectId, ref: "Collection" },
},
);
itemSchema.index({ "$**": "text" });
export default model("Item", itemSchema);
GET COMMENT ROUTE
itemRouter.get(
"/:itemId/comments",
JWTAuthMiddleware,
adminAndUserOnly,
async (req, res, next) => {
try {
if (req.params.itemId.length !== 24)
return next(createHttpError(400, "Invalid ID"));
const items = await ItemModal.findById(req.params.itemId).populate("owner");
if (!items)
return next(
createHttpError(
400,
`The id ${req.params.itemId} does not match any items`
)
);
res.status(200).send(items.comments);
} catch (error) {
next(error);
}
}
);
What i am getting is only user id and comment

Feathersjs-Mongoose populate data

When using find method, how can populate data from other collection. The join operation that we do with sql databases. Right now i am using something like :
code:
async find(data, params) {
let records = await super.find(data, params);
let newrecords = records.data.map(async (user) => {
let professordetails = await this.app
.service("users")
.get(user.professorId);
professordetails.password = undefined;
user.professorId = professordetails;
return user;
});
return await Promise.all(newrecords).then((completed) => {
return completed;
});
}
This is course service and its model :
module.exports = function (app) {
const modelName = "courses";
const mongooseClient = app.get("mongooseClient");
const { Schema } = mongooseClient;
const { ObjectId } = Schema;
const schema = new Schema(
{
name: { type: String, required: true },
details: { type: String, required: true },
professorId: { type: ObjectId, ref: "users", required: true },
enrolledStudents: [{ type: ObjectId, ref: "users" }],
},
{
timestamps: true,
}
);
// This is necessary to avoid model compilation errors in watch mode
// see https://mongoosejs.com/docs/api/connection.html#connection_Connection-deleteModel
if (mongooseClient.modelNames().includes(modelName)) {
mongooseClient.deleteModel(modelName);
}
return mongooseClient.model(modelName, schema);
};
This is something like a unwanted operation as we are having populate. But i couldn't do it with populate.

mongoose $or with mixed string and _id (ObjectId) not working

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)
})
}

Mongoose unable to create new document

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.

Resources