Querying nested objects in mongodb - mongodb

I am trying to query a User collection with multiple nested objects, and I'm not sure how to properly use the projection operators (e.g. $) as they seem to work for arrays not objects.
Each user has a "booksRecords" object, with multiple book objects (e.g. hc_1_3, hc_1_4, etc). Each book object has a field called currLevel and I am trying to find kids that have at least one book object where currLevel: 'nursery'.
I tried doing
User.find({'booksRecords.$.currLevel': 'nursery'}), but that doesn't seem to be working and I wonder what is the correct way to query nested objects?
I checked Querying nested in mongoDB, but it is different from my case as I'm querying nested objects.
[
//first object
{
_id: "xxx",
booksRecords: {
hc_1_3: {
markedRead: false,
currLevel: "elementary"
},
hc_1_2: {
markedRead: false,
currLevel: "elementary"
}
}
},
//second object
{
_id: "xyz",
booksRecords: {
hc_1_3: {
markedRead: false,
currLevel: "elementary"
},
hc_1_2: {
markedRead: false,
currLevel: "nursery"
}
}
}
]

$ projection applies to array only.
You need to use $where to evaluate each document:
db.User.find( { $where: function() {
for (var i=0 in this.booksRecords) {
if (this.booksRecords[i].currLevel === 'nursery') {
return true;
}
}
return false;
} });

can you please this:
var userList = db.User.find();
var booksRecordsList={};
while(userList.hasNext()){
var user = userList.next();
for(var key in user.booksRecords){
if ( !( key in booksRecordsList ) ) {
booksRecordsList[key] = key;
}
}
};
db.User.find().forEach(function(doc){
for (var booksRecord in booksRecordsList){
var booksRecordItem = doc.booksRecords[booksRecord];
if(booksRecordItem.currLevel == "nursery"){
print(tojson(doc));
}
}
});

Related

What happens behind the scene when you update a nested array element in a MongoDB Document

When I do a nested object update inside an array in a Document. Does Mongo DB Engine needs to fetch and parse the whole document update the field and reinsert the document ?
db.ControllerPointCollection.updateOne({
"_id": "Ashutosh Das_MigrationTest_0_1_0"
}, {
$set: {
"Tables.$[t].Blocks.$[b].Points.$[p].Description": "Hey You"
}
}, {
arrayFilters: [{
"t.ID": 32
}, {
"b.ID": 268
}, {
"p.PointDefinitionID": 280
}]
})
Behind the scene, mongodb has a class called Model and inside Model class compose other behaviours with initializing other classes and one of them, I call it Sync which is implemented like this. this is not exact code, but you get the idea:
interface HasId {
id?: number; //optional
}
export class ApiSync<T extends HasId> {
constructor(public rootUrl: string) {}
// if user has Id, that means it is already stored in db, so we make a put request, if it does not then we make post
// so in mongoose, saving means Http request to db
save(data: T): AxiosPromise {
const { id } = data;
if (id) {
return axios.put(this.rootUrl + id, data);
} else {
return axios.post(this.rootUrl, data);
}
}
fetch(id: number): AxiosPromise {
return axios.get(this.rootUrl + id);
}
}

How to avoid unexpected side effect in computed properties - VueJS

I am trying to prefill a form with data from a vuex store.In the code provided is the expected result, I need but I know that this is not the way to do it. I am fairly new to Vue/Vuex. The inputs use a v-model thats why i cant use :value="formInformation.parentGroup" to prefill.
data() {
return {
groupName: { text: '', state: null },
parentName: { text: '', state: null },
};
},
computed: {
formInformation() {
const groups = this.$store.getters.groups;
const activeForm = this.$store.getters.activeForm;
if (activeForm.groupIndex) {
const formInfo = groups[0][activeForm.groupIndex][activeForm.formIndex]
this.groupName.text = formInfo.name // Is there a way to not use this unexpected side effect ?
return formInfo;
} else {
return 'No Form Selected';
}
},
},
I searched for an answere for so long now that i just needed to ask it. Maybe i am just googling for something wrong, but maybe someone here can help me.
You are doing all right, just a little refactoring and separation is needed - separate all the logic to computed properties (you can also use mapGetters):
mounted() {
if (this.formInformation) {
this.$set(this.groupName.text, this.formInformation.name);
}
},
computed: {
groups() {
return this.$store.getters.groups;
},
activeForm() {
return this.$store.getters.activeForm;
},
formInformation() {
if (this.activeForm.groupIndex) {
return this.groups[0][this.activeForm.groupIndex][
this.activeForm.formIndex
];
}
}
}
You could either make groupName a computed property:
computed: {
groupName() {
let groupName = { text: '', state: null };
if (formInformation.name) {
return groupName.text = formInfo.name;
}
return groupName;
}
Or you could set a watcher on formInformation:
watch: {
formInformation: function (newFormInformation, oldFormInformation) {
this.groupName.text = formInfo.name;
}
},
Avoid mutating data property in computed.
Computed are meant to do some operation (eg. reduce, filter etc) on data properties & simply return the result.
Instead, you can try this:
computed: {
formInformation() {
const groups = this.$store.getters.groups;
const activeForm = this.$store.getters.activeForm;
if (activeForm.groupIndex) {
const formInfo = groups[0][activeForm.groupIndex][activeForm.formIndex]
// this.groupName.text = formInfo.name // <-- [1] simply, remove this
return formInfo;
} else {
return 'No Form Selected';
}
}
},
// [2] add this, so on change `formInformation` the handler will get called
watch: {
formInformation: {
handler (old_value, new_value) {
if (new_value !== 'No Form Selected') { // [3] to check if some form is selected
this.groupName.text = new_value.name // [4] update the data property with the form info
},
deep: true, // [5] in case your object is deeply nested
}
}
}

How to replace a manual id with an ObjectID _id in mongoDB?

Let's say I have a database with two collections, kids and classes. Each kid belongs to one class.
Each class has a previously created integer id.
I want to replace the kid.class_id with the (ObjectID) _id of the class, not the (integer) id of the class.
However, when I run the script below, it doesn't reset the class_id with the class._id -- it remains the old integer id.
mongoose.connect(someMongodbUri, { useMongoClient: true }, (err, db) => {
let kidsCount = 0;
db.collection('kids').find({}).each((err, kid) => {
kidsCount++;
db.collection('classes')
.findOne({ id: kid.class_id })
.then((class, err) => {
let newClassId = class._id;
db.collection('kids').updateOne(
{ _id: kid._id },
{ $set: { class_id: newClassId } }
).then(() => {
console.info('Updated', kid.class_id);
kidsCount--;
if (kidsCount === 0) { db.close(); }
});
});
});
});
Am I missing something? Thanks for any help you can offer!
We can convert integerId to Object id.
var ObjectId = require('mongodb').ObjectID;
let newClassId = ObjectId(class._id);
There may be better or elegent ways that i don't know, but this works for me.

Error with count of docs where criteria applies to embedded array

I have a mongoDB collection called Assignments, which have multiple bids (embedded arrays). When one of those bids are set as accepted:true, they are considered an accepted_bid.
I want a function which returns all the docs (or a count of docs) that have one bid out of many (embedded arrays), which are owned by the logged in user.
The following does not work. I'm looking to have {{stats.count}} in the HTML file.
Template.dashboard.stats = function() {
return Assignments.find({completed:true}, {
transform: function(doc) {
if(doc.bids) {
var accepted_bid = _(doc.bids).findWhere({owner:Meteor.userId(),accepted:true});
doc.bid = accepted_bid;
}
return doc;
}
});
};
Im not sure if this would work but it returns a count:
Template.dashboard.helpers({
stats: function() {
var assignments = Assignments.find({
completed: true
}, {
transform: function(doc) {
if (doc.bids) {
var accepted_bid = _(doc.bids).findWhere({
owner: Meteor.userId(),
accepted: true
});
if(accepted_bid) doc.bid = accepted_bid;
}
return doc;
}
}).fetch();
return _(assignments).chain().pluck("bid").compact().value().length;
}
});
It can be used with {{stats}}

Using $inc to increment a document property with Mongoose

I would like to increment the views count by 1 each time my document is accessed. So far, my code is:
Document
.find({})
.sort('date', -1)
.limit(limit)
.exec();
Where does $inc fit in here?
Never used mongoose but quickly looking over the docs here it seems like this will work for you:
# create query conditions and update variables
var conditions = { },
update = { $inc: { views: 1 }};
# update documents matching condition
Model.update(conditions, update).limit(limit).sort('date', -1).exec();
Cheers and good luck!
I ran into another problem, which is kind of related to $inc.. So I'll post it here as it might help somebody else. I have the following code:
var Schema = require('models/schema.js');
var exports = module.exports = {};
exports.increase = function(id, key, amount, callback){
Schema.findByIdAndUpdate(id, { $inc: { key: amount }}, function(err, data){
//error handling
}
}
from a different module I would call something like
var saver = require('./saver.js');
saver.increase('555f49f1f9e81ecaf14f4748', 'counter', 1, function(err,data){
//error handling
}
However, this would not increase the desired counter. Apparently it is not allowed to directly pass the key into the update object. This has something to do with the syntax for string literals in object field names. The solution was to define the update object like this:
exports.increase = function(id, key, amount, callback){
var update = {};
update['$inc'] = {};
update['$inc'][key] = amount;
Schema.findByIdAndUpdate(id, update, function(err, data){
//error handling
}
}
Works for me (mongoose 5.7)
blogRouter.put("/:id", async (request, response) => {
try {
const updatedBlog = await Blog.findByIdAndUpdate(
request.params.id,
{
$inc: { likes: 1 }
},
{ new: true } //to return the new document
);
response.json(updatedBlog);
} catch (error) {
response.status(400).end();
}
});