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

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.

Related

How to solve EagerLoadingError [SequelizeEagerLoadingError]: Table_B is not associated to Table_A?

I am trying to fetch data from Table_A and Table_B using node and sequelize
Table Structure
Table_A:
id PK
name Text
Table_B:
id PK
a_id FK_tableA_id
name Text
Model
TableA.js
'use strict';
const DataTypes = require('sequelize').DataTypes;
module.exports = (sequelize) => {
const Table_A = sequelize.define('Table_A', {
id: {
type: DataTypes.UUID,
allowNull: false,
primaryKey: true
},
name: {
type: DataTypes.TEXT,
allowNull: true
}
});
Table_A.associate = models => {
Table_A.belongsTo(models.Table_B, { as: 'tb' });
}
return Table_A;
};
TableB.js
'use strict';
const DataTypes = require('sequelize').DataTypes;
module.exports = (sequelize) => {
const Table_B = sequelize.define('Table_B', {
id: {
type: DataTypes.UUID,
allowNull: false,
primaryKey: true
},
a_id: {
type: DataTypes.UUID,
allowNull: false,
defaultValue: null
},
name: {
type: DataTypes.TEXT,
allowNull: true
}
});
return Table_B;
};
I am getting below error while I am trying to run the query using sequelize, Can you please guide me where I am making the mistake?
Error
EagerLoadingError [SequelizeEagerLoadingError]: Table_B is not associated to Table_A!
at Function._getIncludedAssociation (C:\Project\test\FilterTest\node_modules\sequelize\dist\lib\model.js:545:13)
at Function._validateIncludedElement (C:\Project\test\FilterTest\node_modules\sequelize\dist\lib\model.js:482:53)
at C:\Project\test\FilterTest\node_modules\sequelize\dist\lib\model.js:401:37
at Array.map (<anonymous>)
at Function._validateIncludedElements (C:\Project\test\FilterTest\node_modules\sequelize\dist\lib\model.js:397:39)
at Function.aggregate (C:\Project\test\FilterTest\node_modules\sequelize\dist\lib\model.js:1204:12)
at Function.count (C:\Project\test\FilterTest\node_modules\sequelize\dist\lib\model.js:1252:31)
at async Promise.all (index 0)
at async Function.findAndCountAll (C:\Project\test\FilterTest\node_modules\sequelize\dist\lib\model.js:1268:27)
index.js
'use strict';
const { Op } = require('sequelize');
const {sequelize, connect } = require('./db');
const uninitModels = require('./models');
let initModels = uninitModels(sequelize);
initModels = { connection: sequelize, ...initModels }
const {
Table_A, Table_B
} = initModels;
function dbCall(final) {
Table_A.findAndCountAll(final).then((result)=>{
console.log(result)
}).catch((err)=>{
console.log(err)
})
}
function data() {
let final = {
include: [
{
model: Table_B,
attributes: ['id', 'name', 'a_id'],
as: 'tb'
}
]
}
dbCall(final);
}
data();
I suppose you didn't register associations that should be registered by calling associate methods of models.
Also you confused how models are linked. If a model1 has a foreign key field pointing to a model2 then an association should be model1.belongsTo(model2).
In your case it should be:
Table_A.associate = models => {
Table_A.hasMany(models.Table_B, { as: 'tb', foreginKey: 'a_id' });
}
and in the model Table_B:
Table_B.associate = models => {
Table_B.belongsTo(models.Table_A, { as: 'ta', foreginKey: 'a_id' });
}
Pay attention to foreignKey option, you need to indicate it explicitly because your foreign key field is named other than Table_A+id.
I got stuck on this same error (for what seemed like an eternity) and finally realized that there was a big flaw in the way I was declaring my associations.
Incorrect:
Account.associate = function (models) {
Account.hasMany(models.History, {
onDelete: "cascade"
});
};
Account.associate = function (models) {
Account.hasMany(models.User, {
onDelete: "cascade"
});
};
In hindsight, this was a really silly oversight doing two declarations here. tl;dr the 2nd declaration was canceling out the 1st one.
Correct:
Account.associate = function (models) {
Account.hasMany(models.History, {
onDelete: "cascade"
});
Account.hasMany(models.User, {
onDelete: "cascade"
});
};
One declaration with multiple function calls for the win.

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.

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 do I reference mongoose model to another model?

I have a mongoose schema for stories that looks like this:
{
id: {
type: Number,
default: 0
},
title: {
type: String,
maxLength: 60
},
author: {
userid: {
type: Number
},
username: {
type: String
}
}
chapters: [chapter],
numchapters: {
type: Number,
default: 1
},
favs: {
type: Number,
default: 0
},
completed: {
type: Boolean,
default: false
}
}
What I'm trying to do is reference a document in a separate collection (users), and use the values of its userid and username fields in the author field.
how do I do this?
current code:
storyobj.populate('author', {path: 'author', model: 'users', select: 'userid username'}, (err) => {
if (err) {
console.log(err)
}
})
just in case it's relevant, the structure of the users collection looks like this:
{
username: {
type: String,
},
email: {
type: String,
},
password: {
type: String,
},
userid: {
type: Number
},
isAdmin: {
type: Boolean,
default: false
},
banned: {
type: Boolean,
default: false
}
}
EDIT:
I've changed the author field in the Stories model to look like this:
author: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
}
This is so I tell Mongoose, "Hey, I want this field to reference a user in the User collection".
Here are some more details that I hope will be of help.
Full code:
var storydb = require('../models/stories/story');
var chapterdb = require('../models/stories/chapter');
var userdb = require('../models/user');
const file = JSON.parse(fs.readFileSync('test.json')); // this is a file with the data for the stories I am trying to insert into my database
for (const d in file) {
var storyobj = new storydb({
id: d,
chapters: []
});
for (let e = 0; e < file[d].length; e++) {
var abc = file[d][e];
var updatey = {
chaptertitle: abc.chapter,
chapterid: parseInt(abc.recid),
words: abc.wordcount,
notes: abc.notes,
genre: abc.gid.split(', '),
summary: abc.summary,
hidden: undefined,
loggedinOnly: undefined,
posted: new Date(Date.parse(abc.date)),
bands: abc.bandnames.split(', ')
};
var kbv = getKeyByValue(userlookup, abc.uid);
storyobj.title = abc.title;
storyobj.numchapters = file[d].length;
storyobj.favs = file[d][0].numfavs;
updatey.characters = abc.charid.split(/, |,/g);
storyobj.chapters.push(updatey)
}
storyobj.save();
}
In file, there's a unique ID representing the author of each story. kbv returns the userid associated with that unique ID (note that they're NOT the same).
Now, here's where I'm having trouble:
What I want to do is find a user matching the userid in kbv, and make that the author property in the story model.
The code I'm currently using to try and achieve that:
storydb.findOne({storyobj}, 'author').populate("author", (f) => console.log(f));
const Stories = require("./path/to/model");
Stories
.find({ /* query */ }, { /* projection */ })
.populate("author.username", ["userid", "username"])
.then(/* handle resolve */)
.catch(/* handle rejection */)
For this to work, you have to add a ref key to the userid key in your model, where the ref value is the name of the model it's referencing.
Story.model.js
const StorySchema = new Schema({
author: {
userid: { type: Schema.Types.ObjectId, ref: "users", required: true },
/* other props */
}
/* other props */
});

How to extend Sequelize model

Is there a way to extend (maybe inherit) model to add hooks and fields after model was defined?
So something like this:
User = sequelize.define("user", {
name: sequelize.String
});
makeStateful(User); // adds state,updated,added fields and some hooks
this is not possible at the moment. But you could easily make it work the other way around: Define your mixin before and use that when you define the model:
var Sequelize = require('sequelize')
, sequelize = new Sequelize('sequelize_test', 'root')
var mixin = {
attributes: {
state: Sequelize.STRING,
added_at: Sequelize.DATE
},
options: {
hooks: {
beforeValidate: function(instance, cb) {
console.log('Validating!!!')
cb()
}
}
}
}
var User = sequelize.define(
'Model'
, Sequelize.Utils._.extend({
username: Sequelize.STRING
}, mixin.attributes)
, Sequelize.Utils._.extend({
instanceMethods: {
foo: function() {
return this.username
}
}
}, mixin.options)
)
User.sync({ force: true }).success(function() {
User.create({ username: 'foo' }).success(function(u) {
console.log(u.foo()) // 'foo'
})
})

Categories