I have CommenentSchema:
const CommentSchema: Schema = new Schema(
{
article: {
type: Schema.Types.ObjectId,
ref: "Article",
},
comment: {
type: String,
required: [true, "Cannot post an empty comment"],
},
commentBy: {
type: Schema.Types.ObjectId,
ref: "User",
},
},
{
toJSON: {
virtuals: true,
transform: (_doc, ret) => {
delete ret.id;
delete ret.__v;
},
},
toObject: { virtuals: true },
timestamps: true,
}
I'm trying to get the totalComments of an article (in a separate ArticleSchema) which I was able to achieve with aggregate and pre save middleware like so:
// Comment Count
// 1 - Calc. number of comments of an article when a comment is submitted
CommentSchema.statics.calcTotalComments = async function (articleId) {
const stats = await this.aggregate([
{
$match: { article: articleId },
},
{
$group: {
_id: "article",
nComment: { $sum: 1 },
},
},
]);
if (stats.length > 0) {
// update article
await Article.findByIdAndUpdate(articleId, {
totalComments: stats[0].nComment,
});
} else {
// set to default
await Article.findByIdAndUpdate(articleId, {
totalComments: 0,
});
}
};
CommentSchema.post("save", function () {
// points to current comment
this.constructor.calcTotalComments(this.article);
});
I also want the number of totalComments to change when a comment is deleted but it doesn't fire at pre /^findOneAnd/
// 2 - Calc. number of comments of an article when a comment is updated/deleted
CommentSchema.pre<Query<ICommentDocument, ICommentDocument>>(
/^findOneAndUpdate/,
async function (next: Function) {
// points to query of coment
// #ts-ignore: igore property doc does not exist on type Query...
this.doc = await this.model.findOne(this.getFilter());
// #ts-ignore: igore property doc does not exist on type Query...
console.log(this.doc);
next();
}
);
CommentSchema.post<Query<ICommentDocument, ICommentDocument>>(
/^findOneAnd/,
async function () {
// #ts-ignore: igore property doc does not exist on type Query...
await this.doc.constructor.calcTotalComments(this.doc.article);
}
);
I have tried all previous solution but none did work. Even the pre hook doesn't fire a simple console.log('hello');
What am I doing wrong?
Related
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.
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 am trying to make a dynamic query based on multiple selection of the user.
In my application I have the Publication schema that has the Pet schema embedded as follows:
var status = ["public", "private", "deleted"];
var publication_schema = new Schema({
pet:{
type: Schema.Types.ObjectId,
ref: "Pet"
},
status: {
type: String,
enum: status,
default: status[0]
}
});
module.exports = mongoose.model('Publication', publication_schema);
var pet_schema = new Schema({
type: {
type: String,
require: true
},
createdDate: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Pet', pet_schema);
Insyde an async method I build the query, getting all the user input values from the object filter, also I have the query object where I push the different criteria and use it with an $and
let query = {};
let contentQuery = []
if (filter.public && !filter.private) {
contentQuery.push({ status: { $eq: "public" } });
} else if (filter.privada && !filter.public) {
contentQuery.push({ status: { $eq: "private" } });
}
query = { $and: contentQuery }
try {
const publication = await Publication.find(query).populate('pet');
} catch (e) {
console.log(e)
}
the problem is when I want to add more criteria such as follows:
if (filter.specie) { // for example filter.specie equals 'cat'
contentQuery.push({ pet: { type: { $eq: filter.specie } } });
}
I get the error:
'Cast to ObjectId failed for value "{ type: { \'$eq\': \'cat\' } }" at path "pet" for model "Publication"',
name: 'CastError',
stringValue: '"{ type: { \'$eq\': \'cat\' } }"',
kind: 'ObjectId',
value: { type: { '$eq': 'cat' } },
path: 'pet',
reason: undefined,
model: Model { Publication } }
So. How can I do to query the fields of publication and also the pet fields inside publication?
You can have a look on Populate Query Conditions
Instead of .populate('pet') you could do something like
Publication.find({})
.populate({
path: 'pet',
match: { specie: 'cat'},
// You can select the fields you want from pet, or remove the select attribute to select all
select: 'name -_id',
// Here you could add options (e.g. limit)
options: { limit: 5 }
}).exec();
The above query will get you all Publications with pet.specie equals to 'cat'
I'm using sequelize, and I have a function that uses findOrCreate, however it seems to throw and error "RangeError: Maximum call stack size exceeded" despite sometimes adding the entries.
The code I use is below, and I am passing it 6 tids. Does anyone know whats going on and how to resolve/prevent this?
exports.sqlAddTags = async function(did, tids) {
try {
await sequelize.authenticate();
const promises = [];
tids.forEach(t =>{
console.log(did, t)
promises.push(FB_Tag.findOrCreate({
where: {
did: did,
tid: t
}
}));
});
return await Promise.all(promises);
} catch (e) {
console.log(e.message)
}
};
My model:
FB_Tag.init({
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
did: {
type: Sequelize.STRING,
allowNull: false,
},
tid: {
type: Sequelize.STRING,
allowNull: false
},
}, {
sequelize,
modelName: 'fb_tag',
timestamps: false
});
EDIT: Seems with just 1 tid, it also throws this error
If the item already exists, I seem to get this message. Not sure if this is normal?
CREATE OR REPLACE FUNCTION pg_temp.testfunc(OUT response "public"."fb_tags", OUT sequelize_caught_exception text) RETURNS RECORD AS $func_66aef14340b2417dabf18d3cdfafa589$ BEGIN INSERT INTO "public"."fb_tags" ("i
d","did","tid") VALUES (DEFAULT,'jcBUnwocFS9I7RekMZHJ','7AQS1zPkLIUsSfx8lC0a') RETURNING * INTO response; EXCEPTION WHEN unique_violation THEN GET STACKED DIAGNOSTICS sequelize_caught_exception = PG_EXCEPTION_DETAIL; END $func_66aef14340b2417dabf18d3cdfafa589$ LANG
UAGE plpgsql; SELECT (testfunc.response).*, testfunc.sequelize_caught_exception FROM pg_temp.testfunc(); DROP FUNCTION IF EXISTS pg_temp.testfunc();
I've created a test script with the same logic as above that works, so I can only put this down to be how the function is called..
const newTags = [];
const oldTags = [];
const postgresTags = []
data.tags.forEach(t => {
if(t.id === t.title) {
newTags.push(t.title)
} else {
oldTags.push(t.id);
postgresTags.push(t.id)
}
});
if(oldTags.length > 0) {
await firestoreAddTagsToDesign(context.auth.uid, oldTags, data.did, "old");
}
if(newTags.length > 0) {
const setTags = await firestoreAddNewTags(context.auth.uid, newTags, data.did);
await firestoreAddTagsToDesign(context.auth.uid, setTags, data.did, "new");
setTags.forEach(i => postgresTags.push(i.id));
}
await sqlAddTags(data.did, postgresTags);
I wish to update a property per object in array of objects, but if some of the objects doesn't exists, insert the object instead.
Currently I used "upsert", which creates a new document when no document matches the query, unfortunately it is replacing a single item with my entire list.
Worth to mention that I am using mongoist to perform an async requests.
My code:
this.tokenArray = [
{ token: "654364543" },
{ token: "765478656" },
{ token: "876584432" },
{ token: "125452346" },
{ token: "874698557" },
{ token: "654364543" }
]
database.upsertDatebaseItem(this.tokenArray.map(x => { return x.token }), { valid : true }, 'Tokens');
async upsertDatebaseItem(itemKey, itemValue, collectionName) {
try {
await this.database[collectionName].update({ token : { $in: itemKey}}, { $set: itemValue }, {upsert : true} , {multi : true});
} catch (error) {
console.log(`An error occurred while attempting to update ${itemType} to the database: ${error}`);
return false;
}
}
Found the way to do it:
const bulkUpdate = this.tokenArray.map((x) => {
return {
"updateOne": {
"filter": { "token": x.token },
"update": { "$set": { "valid": true } },
"upsert": true
}
};
});
and:
this.database[collectionName].bulkWrite(bulkUpdate);
To upsert with mongoist, use the following:
var bulk = db.collection.initializeOrderedBulkOp()
for(var doc of docs) bulk.find( { _id: doc._id } ).upsert().updateOne(doc); // or use replaceOne()
await bulk.execute();
Converted to your case that would be
var bulk = db.collectionName.initializeOrderedBulkOp()
for(var tokenItem of tokenArray) bulk.find( { token : tokenItem.token } ).upsert().updateOne(tokenItem); // or use replaceOne()
await bulk.execute();