Reputation: 28328
I have a Tag
schema:
const TagSchema = new Schema({
label: {type: String, unique: true, minlength: 2},
updatedAt: {type: Date, default: null},
createdAt: {type: Date, default: new Date(), required: true},
createdBy: {type: mongoose.Schema.Types.ObjectId, ref: 'Account', default: null}
});
const Tag = mongoose.model('Tag', TagSchema);
Then I have a Page
schema:
const PageSchema = new Schema({
tags: {type: [{type: mongoose.Schema.Types.ObjectId, ref: 'Tag'}], default: [], maxlength: 5}
});
const Page = mongoose.model('Page', PageSchema);
As you can see it contains a Tag
reference in its tags
array.
Now what I need to do is when I fetch all tags via /tags
, I also need to count the number of times each tag is used in all Page
s.
So if a Tag
is used 2
times across all Page
s, it should set an .occurrences
property on the returned tag. For example this would be a response from /tags
:
[
{_id: '192398dsf7asdsdds8f7d', label: 'tag 1', updatedAt: '20170102', createdAt: '20170101', createdBy: '01238198dsad8s7d78ad7', occurrences: 2},
{_id: '19239asdasd8dsf7ds8f7d', label: 'tag 2', updatedAt: '20170102', createdAt: '20170101', createdBy: '01238198dsad8s7d78ad7', occurrences: 1},
{_id: '192398dsf7zxccxads8f7d', label: 'tag 1', updatedAt: '20170102', createdAt: '20170101', createdBy: '01238198dsad8s7d78ad7', occurrences: 5},
]
I would have imagined that I could achieve this pretty easily in a mongoose pre('find')
hook like this:
TagSchema.pre('find', function() {
Page.count({tags: this._id}, (err, total) => {
this.occurrences = total;
});
});
However, there are two problems here:
Page.count
throws an error saying it's not a function, which I don't understand why because I use it somewhere else perfectly fine. And Page
has been imported properly. Suggesting that you can't use count
in a hook or something similar.
this
is not the Tag
document.
So I guess that the way I am going about this is completely wrong.
Since I am a novice in MongoDB maybe someone can provide me with a better, working, solution to my problem?
Upvotes: 1
Views: 144
Reputation: 1426
db.Page.aggregate([
{
$unwind:"$tags"
},
{
$group:{
_id:"$tags",
occurrences:{$sum:1}
}
},
{
$lookup:{ //get data for each tag
from:"tags",
localField:"_id",
foreignField:"_id",
as:"tagsData"
}
},
{
$project:{
tagData:{$arrayElemAt: [ "$tagsData", 0 ]},
occurrences:1
}
}
{
$addFields:{
"tagData._id":"$_id",
"tagData.occurrences":"$occurrences"
}
},
{
$replaceRoot: { newRoot: "$tagData" }
}
])
Upvotes: 1
Reputation: 518
Ok first, you need getQuery() this allows you to access the properties that you are finding the tag
with. so if you find the tag by _id
, you will have access to it in the pre
hook by this.getQuery()._id
Second, you are going to need to use the "Parallel" middleware, can be found in the docs. This allows the find method to wait until the done
function is called, so it is gonna wait until the tag is updated with the new occurrences number.
As the object can no be accessed inn the pre
hook as it is yet to be found, you are going to have to use findOneAndUpdate
method in the pre
hook.
So the code should look like :
The find
method :
Tag.find({ _id: "foo"}, function (err, foundTag) {
console.log(foundTag)
})
The pre
hook :
TagSchema.pre('find', true, function (next, done) {
mongoose.models['Page'].count({ tags: this.getQuery()._id }, (err, total) => {
mongoose.models['Tag'].findOneAndUpdate({ _id: this.getQuery()._id }, { occurrences: total }, function () {
next()
done()
})
});
});
Upvotes: 0