How to call mongoose method in the select method - javascript

I have a mongoose model that represents a player and want to be able to fetch the player and when selecting the player, want to call isReady like a getter.
The model looks like so:
const PlayerSchema = new Schema({
user: { type: Schema.Types.ObjectId, ref: "User" },
famousPerson: { type: String }
})
PlayerSchema.methods.isReady = function (cb) {
return Boolean(this.famousPerson)
}
And I want to be able to call it like so:
const player = await PlayerModel
.findOne({_id: playerId})
.select(["_id", "username", "isReady"])
Am I able to set the method on the class as a getter?

You can use mongoose virtuals for this, but to work as expected you need to configure your schema so that it can return virtuals, because by default virtuals will not be included.
const PlayerSchema = new Schema(
{
famousPerson: { type: String },
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
PlayerSchema.virtual("isReady").get(function () {
return Boolean(this.famousPerson);
});

You Can Follow This Code
const player = await PlayerModel
.findOne({_id: playerId})
.select(" +_id +username +isReady)

Related

Can only define custom mongoose methods in options

According to the mongoose docs, there are 3 ways to add custom methods to your documents:
Through schema options
Directly assigning a "methods" object to the schema
Using the Schema.method() helper
However, after many tries, I have only managed to get methods working using option 1.
I am curious as to why options 2 & 3 are not working for me. here is my code:
app.js
socket.on("message", async function (clusterData, callback) {
console.log("socket event fired");
const parentCluster = await Message.findById(clusterData.clusterId);
coonsole.log(parentCluster); // exists as expected
parentCluster.optionsMethod(); // log : "options method called" ✔
parentCluster.objectMethod(); // error : parentCluster.objectMethod is not a function ❌
parentCluster.helperMethod(); // error : parentCluster.helperMethod is not a function ❌
});
Message.js
import mongoose from "mongoose";
const messageSchema = new mongoose.Schema({
mentions: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }],
text: { type: String, trim: true },
file: { type: String },
dateString: { type: String, required: true },
timestamp: { type: Number, required: true },
});
const messageClusterSchema = new mongoose.Schema(
{
sender: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},
channel: {
type: mongoose.Schema.Types.ObjectId,
ref: "Channel",
required: true,
},
group: {
type: mongoose.Schema.Types.ObjectId,
ref: "Group",
required: true,
},
content: [messageSchema],
clusterTimestamp: {
type: Number,
required: true,
},
},
{
toObject: { virtuals: true },
toJSON: { virtuals: true },
methods: {
optionsMethod() {
console.log("options method called");
},
},
}
);
messageClusterSchema.virtual("lastMessage").get(function () {
return this.content[this.content.length - 1];
});
messageClusterSchema.pre("validate", function () {
console.log("pre validate ran");
this.clusterTimestamp = this.content[this.content.length - 1].timestamp;
});
// assign directly to object
messageSchema.methods.objectMethod = function () {
console.log("object method called");
};
// assign with helper
messageSchema.method("helperMethod", function () {
console.log("helper method called");
});
console.log(messageSchema.methods); // {objectMethod: [Function (anonymous)], helperMethod: [Function (anonymous)]}
console.log(messageSchema.methodOptions); // { helperMethod: undefined }
const Message = mongoose.model("Message", messageClusterSchema);
export default Message;
The issue is that,
objectMethod and helperMethod is in messageSchema and In Message.js file, you are creating model of messageClusterSchema which you are importing and using in socket function. Both methods can only be called with a model-instance of messageSchema. And that's why optionsMethod is calling, but the other two are not. Basically you need to create model of messageSchema and export it to use it in other files.
In short, the error is:
const Message = mongoose.model("Message", messageClusterSchema);
The model is generated using messageClusterSchema, but the methods are assigned to messageSchema:
messageSchema.methods.objectMethod = function () {
console.log("object method called");
};
// assign with helper
messageSchema.method("helperMethod", function () {
console.log("helper method called");
});
They should be assigned to messageClusterSchema.

Mongoose: pre('validate') middleware is not working

This is my mongoose Setup. This happens only when i use class syntax. When i do the same thing with the use of functional programming it works fine. This is the first time i am using class syntax to do this. I think that's where the problem lies. I am doing something wrong with my class definition.
This is my mongoose Setup. This happens only when i use class syntax. When i do the same thing with the use of functional programming it works fine. This is the first time i am using class syntax to do this. I think that's where the problem lies. I am doing something wrong with my class definition.
const mongooseService = require('./services/mongoose.service')
const slugify = require('slugify')
const { marked } = require('marked')
const createDomPurifier = require('dompurify')
const { JSDOM } = require('jsdom')
const dompurify = createDomPurifier(new JSDOM().window)
class ArticleDao {
Schema = mongooseService.getMongoose().Schema
articleSchema = new this.Schema({
title: {
type: String,
required: true,
},
description: {
type: String,
},
markdown: {
type: String,
required: true,
},
createdAt: {
type: Date,
default: new Date(),
},
slug: {
type: String,
required: true,
unique: String,
},
sanitizedHtml: {
type: String,
required: true,
},
})
Article = mongooseService.getMongoose().model('Article', this.articleSchema)
constructor() {
console.log(`created new instance of DAO`)
this.setPreValidation()
}
setPreValidation() {
console.log('h')
this.articleSchema.pre('save', (next) => {
if (this.title) {
this.slug = slugify(this.title, { lower: true, strict: true })
}
if (this.markdown) {
this.sanitizedHtml = dompurify.sanitize(marked(this.markdown))
}
next()
})
}
async addArticle(articleFields) {
const article = new this.Article(articleFields)
await article.save()
return article
}
async getArticleById(articleId) {
return this.Article.findOne({ _id: articleId }).exec()
}
async getArticleBySlug(articleSlug) {
return this.Article.findOne({ slug: articleSlug })
}
async getArticles() {
return this.Article.find().exec
}
async updateArticleById(articleId, articleFields) {
const existingArticle = await this.Article.findOneAndUpdate({
_id: articleId,
$set: articleFields,
new: true,
}).exec()
return existingArticle
}
async removeArticleById(articleId) {
await this.Article.findOneAndDelete({ _id: articleId }).exec()
}
}
module.exports = new ArticleDao()
This is the error i get:
Article validation failed: sanitizedHtml: Path `sanitizedHtml` is required., slug: Path `slug` is required.

i can't populate mongoose deep subdocument

Below is the code that simplified the model and schema I'm having a hard time with
const guildSchema = new Schema<Guild>({
sheets: [sheetSchema],
crews: [crewSchema],
});
const GuildModel= getModel('Guild', guildSchema)
const sheetSchema = new Schema<Sheet>({
deales: [dealSchema]
})
const SheetModel = getModel('Guild.sheets', sheetSchema)
const dealSchema = new Schema<Deal>({
crew: [{ type: Schema.Types.ObjectId, refPath: 'Guild.crews' }],
damage: { type: Number, required: true },
})
const DealModel = getModel('Guild.sheets.deales', dealSchema)
const crewSchema = new Schema<Crew>({
name: { type: String, required: true },
})
const CrewModel= getModel('Guild.crews', crewSchema)
and this is Mocha-chai testcode what always throw exception
it("populated guild.sheets.deales.boss must have name",async () => {
const guild = await GuildModel.findOne({})
await guild.populate({
path: 'sheets.deales.crew'
}).execPopulate()
expect(guild.sheets[0].deales[0].crew).to.has.property("name") // expected [] to have property 'name'
})
None of the answers on stackoverflow solved my problem. I wasted 5 hours on just a few lines of this code. please help me
You checked this? https://github.com/Automattic/mongoose/issues/1377#issuecomment-15911192
This person changed nested code
var opts = {
path: 'author.phone',
select: 'name'
};
BlogPost.populate(docs, opts, function (err, docs) {
assert.ifError(err);
docs.forEach(function (doc) {
console.log(doc);
});
callback(null);
from this
var authors = docs.map(function(doc) {
return doc.author;
});
User.populate(authors, {
path: 'phone',
select: 'name'
}, callback);
to this.
author(User)is in BlogPost. BlogPost Schema has just User ObjectId, so can't understand author.phone
I might have already checked it, but I'm uploading it just in case.

Do I have to define different mongodb models in separate files?

Recently, I have been working with mongodb with one single model. When I tried to add a second model, I noticed that I might face some issues.
First, here's the code with one single model:
riskRating.js
const mongoose = require('mongoose')
const Schema = mongoose.Schema;
let riskRatingRow = new Schema({
securitycause: {
type: String
},
operationalrisk: {
type: String
},
riskid: {
type: String
},
riskstatements: {
type: String
},
businessline: {
type: String
},
supportingasset: {
type: String
},
category: {
type: String
},
frequency: {
type: String
},
impact: {
type: String
},
inherentriskrating: {
type: String
},
controleffectiveness: {
type: String
},
residualrisk: {
type: String
}
});
module.exports = mongoose.model('riskRating', riskRatingRow);
Here's how I use it in the server code:
server.js
const RiskRatingRow= require('./models/riskRating');
router.route('/table').get((req, res) => {
RiskRatingRow.find((err, tableData) => {
if (err)
console.log(err);
else
res.json(tableData);
});
});
router.route('/table/add').post((req, res) => {
console.log('REQ.body is ', req.body);
const riskRatingRow = new RiskRatingRow(req.body);
riskRatingRow.save()
.then(issue => {
res.status(200).json({
'tableRow': 'Added successfully'
});
})
.catch(err => {
res.status(400).send('Failed to create new record');
});
});
First question: Is there anything wrong so far?
Now, when I add the second model:
twoModels.js
const mongoose = require('mongoose')
const Schema = mongoose.Schema;
let riskRatingRow = new Schema({
//1st model defintion
});
const businessLineDashboardRow = new Schema({
//2nd model defintion
});
module.exports = mongoose.model('businessLineDashboard', businessLineDashboardRow);
module.exports = mongoose.model('riskRating', riskRatingRow);
I have noticed that in server.js, when I am using the first model, I am not referencing it directly. I'm rather referecing the singleModel.js file. Particularly in these two lines:
const RiskRatingRow = require('./models/riskRating');
// Here I am using directly the file reference RiskRatingRow
const riskRatingRow = new RiskRatingRow(req.body);
// Same thing here
RiskRatingRow.find((err, tableData) => {
if (err)
console.log(err);
else
res.json(tableData);
});
So, when I was about to make use of the second model, I found myself blocked since as I explained when I used the first model, I didn't reference it directly. I just referenced the file.
Thing is, that actually works fine.
But, I don't know if the model file contains two models, how am I supposed to make use of them both in the server file.
So here's my two other questions:
1/ How come that code works even though I am just referecing the model defintion file?
2/ Should I define the second model in a separate file, and reference it in order to be able to use it?
Thank you, I hope I was clear enough.
module.exports can be an object containing multiple things as properties:
module.exports = {
RiskRatingRow: mongoose.model('businessLineDashboard', businessLineDashboardRow),
BusinessLineDashboardRow: mongoose.model('riskRating', riskRatingRow),
}
Since it's an empty object ({}) by default you can also assign the exports individually:
module.exports.RiskRatingRow = mongoose.model('businessLineDashboard', businessLineDashboardRow)
module.exports.BusinessLineDashboardRow = mongoose.model('riskRating', riskRatingRow)
You can now destructure the models out of the object inside server.js:
const { RiskRatingRow, BusinessLineDashboardRow } = require('./models/twoModels')
Or, if you want to do it the old-school way:
const models = require('./models/twoModels')
const RiskRatingRow = models.RiskRatingRow
const BusinessLineDashboardRow = models.BusinessLineDashboardRow
Niklas's answer is on point. However, I have found a more complete solution:
riskRating.js
const mongoose = require('mongoose')
const Schema = mongoose.Schema;
module.exports = function(mongoose) {
let riskRatingRow = new Schema({
securitycause: {
type: String
},
operationalrisk: {
type: String
},
riskid: {
type: String
},
riskstatements: {
type: String
},
businessline: {
type: String
},
supportingasset: {
type: String
},
category: {
type: String
},
frequency: {
type: String
},
impact: {
type: String
},
inherentriskrating: {
type: String
},
controleffectiveness: {
type: String
},
residualrisk: {
type: String
}
});
let businessLineDashboardRow = new Schema({
ref: {
type: String
},
riskstatements: {
type: String
},
maximpact: {
type: String
},
controleffectiveness: {
type: String
},
recommendedriskrating: {
type: String
},
frequency: {
type: String
},
impact: {
type: String
},
validatedreviewriskrating: {
type: String
},
rationalforriskadjustment: {
type: String
}
});
var models = {
BusinessLineDashboard : mongoose.model('BusinessLineDashboard', businessLineDashboardRow),
RiskRating : mongoose.model('RiskRating', riskRatingRow)
};
return models;
}
server.js
router.route('/riskRating2').get((req, res) => {
models.RiskRating.find((err, tableData) => {
if (err)
console.log(err);
else
res.json(tableData);
analyseDeRisqueService.determineMaxImpactForEachRisk(tableData)
});
});

Sequelize Association throws an error in Create

I'm trying to do a simple association with sequelize in my NodeJS API, the idea is simple, I want to create a person and his type in the same moment. I try to follow the docs, but, when the create function is called Sequelize throws the error
"TypeError: Cannot read property 'getTableName' of undefined"
Bellow is the code used:
Models
Person.js
import Sequelize from 'sequelize';
export default (sequelize) => {
const Person = sequelize.define('person', {
email: { type: Sequelize.STRING },
nickname: { type: Sequelize.STRING(60) },
fullname: { type: Sequelize.STRING(60) },
observation: { type: Sequelize.TEXT },
}, { underscored: true, freezeTableName: true });
Person.associate = (models) => {
Person.hasOne(models.PersonType, {
foreignKey: 'person_id',
});
};
return Person;
};
PersonType.js
import Sequelize from 'sequelize';
export default (sequelize, DataTypes) => {
const PersonType = sequelize.define('person_type', {
type: { type: Sequelize.STRING(14), unique: true },
}, { underscored: true, freezeTableName: true });
PersonType.associate = (models) => {
PersonType.belongsTo(models.Person, {
foreignKey: 'person_id',
});
};
return PersonType;
};
index.js
import Sequelize from 'sequelize';
import console from 'console';
const database = process.env.DB_URL;
const sequelize = new Sequelize(database, {
dialect: 'postgres',
underscored: true,
});
const models = {
Person: sequelize.import('./person'),
PersonType: sequelize.import('./personType'),
};
Object.keys(models).forEach((modelName) => {
if ('associate' in models[modelName]) {
models[modelName].associate(models);
}
});
models.sequelize = sequelize;
models.Sequelize = Sequelize;
export default models;
The file where I try to execute the operation:
import models from '../models';
export default class PersonOperations {
constructor(db) {
this.db = db; // db here is the sequelize model of Person
}
create(person) {
return this.db.create({
email: person.email,
nickname: person.nickname,
observation: person.observation,
personType: {
type: person.personType.type,
},
}, {
include: [{
model: models.PersonType,
include: [person.personType],
}],
});
}
The error:
TypeError: Cannot read property 'getTableName' of undefined
at Function._validateIncludedElement (/home/~/node_modules/sequelize/lib/model.js:465:30)
at options.include.options.include.map.include (/home/~/node_modules/sequelize/lib/model.js:395:37)
at Array.map (<anonymous>)
at Function._validateIncludedElements (/home/~/node_modules/sequelize/lib/model.js:390:39)
I have already tried a lot of different things that I have found, but none of them helped me.
I am using freezeTableName, I have already tried to remove that, drop the database and create again, but the problem still persists.
Just Comment out the below code block from Person.js and run the code
Person.associate = (models) => {
Person.hasOne(models.PersonType, {
foreignKey: 'person_id',
});
};
Reason of error :
Here you are creating the circular dependecies , using
models.PersonType inside models.Person and then after using
models.Person inside models.PersonType
hasOne and belongsTo both are the same thing only diff is (READ) :
Player.belongsTo(Team) // `teamId` will be added on Player / Source model
Coach.hasOne(Team) // `coachId` will be added on Team / Target model

Categories