I'm looking for the conditions, fields, etc that i would need in a query, or an async flow to query a single document which has an array of subdocuments at the top-level.
Model:
let postSchema = new mongoose.Schema({
date : {type: Boolean, require: true},
message : {type: String, require: true}
});
let Post = new mongoose.Schema({
locid: {type: String, unique: {indexed: true}, require: true},
posts : {type: [postSchema], require: false}
});
Essentially, I would provide a Locid value as shown above into a function that i'm hoping would look like this:
Post.methods.getLastTwentyPostsForOne = function (locid) {
return new Promise(function (values, error) {
let p = new Post();
p.find({"locid" : locid}, {/*...the conditions and elements here...*/}).exec()
.then(function (found) {
//..would return the last 20 posts, or less if there are less...
}, function (err) {
//..handle errors..
})
})
};
The most basic way i can think of doing this, would be to just fetch the whole array, and since mongo stores entries one after the other chronologically, to just parse the array and appending the last 20 or less posts contained in it into a final array (with a second promise-based function).
this makes the previous method, look like this:
function returnLastTwenty(posts) {
return new Promise(function (results) {
if (posts.length === 0) {
return results([]);
}
if (posts.length <= 20) {
return results(posts);
}
let length = posts.length;
var next = [];
for (i = length - 21; i < (length -1); i++) {
next.append(posts[i]);
}
if (next.length === 20) {
return results(next);
} else {
console.log('Some Error: found more than 20 posts, but parsed less');
return results(next)
}
});
}
Post.methods.getLastTwentyPostsForOne = function (locid) {
return new Promise(function (values, error) {
let p = new Post();
p.find({"locid" : locid})
.then(function (found) {
if (found.length === 1) {
returnLastTwenty(found[0].posts)
.then(function (results) {
return values(results)
})
} else {
return error("Didn't find a unique document for locid: " + locid);
}
}, function (err) {
return error(err)
})
})
};
While this does work.. the for loop and second async method seems too much.. Any clues as to how to achieve this with mongoose with a single query?
Update - Answer
Thanks to Ratan's comment, the following seemed to do the trick.
function getPosts(amount, asOfIndex, locid) {
return new Promise(function (posts, none) {
Post.find({'locid' : locid},
{'posts' :
{"$slice" : [asOfIndex, amount]}
}
)
.exec()
.catch(function (err) {
return none(err);
})
.then(function (found) {
if (found.length === 0 || found.length > 1) return none(); //This can be changed for more specific error handling
return posts(found[0].posts);
});
});
}
With index being an arbitrary Number passed through. Which could be an index from an API call, etc. Where asOfIndex should be negative in order to slice from the bottom of the array, and positive from the top (https://docs.mongodb.com/manual/reference/operator/projection/slice/). Slicing a given interval then just becomes a question of playing with the values of asOfIndex and amount.
Also while doing some digging, found a way to query the date element of the subdocs inside the array based on a date interval thanks to the following posts: https://stackoverflow.com/a/29026860/7183483 and https://stackoverflow.com/a/22287745/7183483 . For those who might want something like it!
function getPostsBetween(recentDate, farthestDate, forLocid) {
return new Promise(function (posts, none) {
Post.aggregate(
{'$match': {'locid': forLocid}},
{'$unwind' : '$posts'},
{'$match' :
{'$and': [
{'posts.date': {'$gt': new Date(farthestDate.toISOString())}},
{'posts.date': {'$lt': new Date(recentDate.toISOString())}}
]
}
},
{"$group" : {
'_id' : '$_id',
"posts" : {'$push' : '$posts'}
}
})
.exec()
.catch(function (err) {
return none(err);
})
.then(function (found) {
if (found.length === 0 || found.length > 1) return none();
return found(found[0].posts);
})
})
}