Can only define custom mongoose methods in options - javascript

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.

Related

Mongoose Pre /^findOneAnd/ Hook Middleware Doesn't Fire

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?

How to pull items from reference array based on condition in array?

I am working in creating a todo list and one of the action I want users to do is delete all completed todos in one click. I have my models here, and the code I have, I have been reading trying to figure it out, but can't find it anywhere. Thanks in advance.
User Model
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
todos: [{
type: mongoose.Types.ObjectId,
ref: 'Todo'
}]
});
Todo model:
const TodoSchema = new mongoose.Schema({
creator: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User',
},
content: {
type: String,
required: true,
},
completed: {
type: Boolean,
default: false
},
});
This is what I have now. I get a "Cast to ObjectId failed for value true at path todos.
router.delete('/delete/completed', auth, async (req, res) => {
try {
const userTodos = await User.update(
{'_id':req.user.id},
{ $pull: { 'todos': { 'completed': true } } },
{ multi: true }
);
if (!userTodos) {
return res.status(400).send('Server error');
}
return res.json({ userTodos });
//return res.json({msg: 'All completed removed'})
} catch (err) {
console.error(err.message);
return res.status(404).json({ msg: 'Something went wrong, try again' });
}
});
If you are (as it seems from your code) using mongoose, you could use mongoose's populate feature:
const userTodos = await User.find(
{'_id':req.user.id}).populate('todos', {
match: {completed: true}
});
please note, however, that you'll need to delete both the documents in the todos collection, AND the todo reference in the user's todos array. You may consider to remove one side of the reference, see the pros and cons of two-way referencing here

How to call mongoose method in the select method

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)

How do I set a foreign key for a Sequelize model?

This is very basic, and should work.. but doesn't. So first my models:
const Conversation = sequelize.define('Conversation', {
name: {
type: DataTypes.STRING,
allowNull: false
},
...
})
Conversation.associate = (models, options) => {
Conversation.hasOne(models.Audio, options)
}
and:
module.exports = (sequelize /*: sequelize */ , DataTypes /*: DataTypes */ ) => {
const Audio = sequelize.define("Audio", {
name: {
type: DataTypes.STRING,
unique: true,
allowNull: true
},
})
Audio.associate = (models, options) => {
Audio.belongsTo(models.Conversation, options)
}
I have a model loader that does:
fs.readdirSync(`${__dirname}`)
.filter((modelFile) => {
return path.extname(modelFile) === '.js' && modelFile !== 'index.js'
})
.map((modelFile) => {
let model = sequelize.import(`./${modelFile}`)
models[model.name] = model
return model
})
.filter((model) => models[model.name].associate)
.forEach((model) => {
models[model.name].associate(models, {
hooks: true,
onDelete: 'CASCADE'
});
})
So it calls the associate method for all models that have it defined. This works, in that, when I .sync it, it creates a ConversationId field in my Conversations table.
When I try to execute:
let updAudio = {
ConversationId,
name: 'myname'
}
await global.db.models.Audio.create(updAudio, { logging: console.log })
ConversationId is not null, but when it saves in the DB, it's null. I've inspected it a hundred times. The raw query looks like:
INSERT INTO "Audios" ("id","name","createdAt","updatedAt") VALUES (DEFAULT,'myname','2019-10-20 19:59:18.139 +00:00','2019-10-20 19:59:18.139 +00:00') RETURNING *;
So what happened to ConversationId?
You might need to add the foreign key in the model definition:
(One of the two may work)
const Audio = sequelize.define("Audio", {
name: {
type: DataTypes.STRING,
unique: true,
allowNull: true
},
conversationId : {
type: Sequelize.INTEGER,
references: 'Conversations' // or "conversations"? This is a table name
referencesKey: 'id' // the PK column name
}
})
or
const Audio = sequelize.define("Audio", {
name: {
type: DataTypes.STRING,
unique: true,
allowNull: true
},
conversationId : {
type: Sequelize.INTEGER,
references: {
model: Conversation
key: 'id'
}
}
})
You might also need to define your belongsTo association this way:
Audio.belongsTo(Conversation, foreignKey: 'conversationId');
Try to look for the generated raw query on creation of the tables and start from there.

Error: Unrecognized data type for field classMethods

When using the following model definition:
var address = connection.define('address', {
id : {
type :sequelize.INTEGER ,
primaryKey : true ,
autoIncrement : true
},
content : sequelize.TEXT ,
classMethods : {
associate : function(model){
var user = model.user;
user.hasMany(address);
}
}
});
I am unable to debug the: Error: Unrecognized data type for field classMethods
Clearly you have mixed the model definition part and other options part. Look at the examples like Sequelize classMethods vs instanceMethods, they should be in two objects, as two seperate arguments.
Therefore, your code should at least be modified to something like:
var address = connection.define('address', {
id: {
type: sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
content: sequelize.TEXT
},
// <-- here, another object / argument started
{
classMethods: {
associate: function (model) {
var user = model.user;
user.hasMany(address);
}
}
});
i missed the bracket and face this issue
'use stri
ct';
module.exports = function(sequelize, DataTypes) {
var category = sequelize.define('category', {
name: DataTypes.STRING,
image_url: DataTypes.STRING,
thumb_url: DataTypes.STRING,
type1: {
type: DataTypes.ENUM,
values: ['public_services', 'local_services']
}
}//this one,
{//this bracket
classMethods: {
associate: function(models) {
// associations can be defined here
// category.hasMany(models.directory, [{foreignKey:'dir_fid'}])
}
}
});
return category;
};
var address = connection.define('address',{
id:{
type :sequelize.INTEGER ,
primaryKey : true ,
autoIncrement : true
},
content : sequelize.TEXT
},
{
classMethods : {
associate : function(model){
var user = model.user;
user.hasMany(address);
}
}
}
);
^^Syntax Issue

Categories