Sub-document saving is not updating parent - node.js

I'm trying to learn some relationship mechanics in mongoose. I have two models, a parent and a child:
var childrenSchema = new Schema({
name : String,
date : {type : Date, default: Date.now},
attribute1 : String,
attribute2 : String,
})
var parentSchema = new Schema({
name: String,
children: [childrenSchema]
})
exports.parent = mongoose.model('Parent', parentSchema);
exports.children = mongoose.model('Person', childrenSchema);
I will create a parent object in an initial call, and send an asynchronous call to an api which fetches children information based on the child's name. While that async call is out, I return the parent as is, because the user doesn't need to see the children's information immediately.
var Parent = require('schema.js').parent;
var Child= require('schema.js').children;
function addParent(p){
var parent = new Parent();
parent.name = p.result.name;
var child = new Child();
child.name = p.result.childname;
parent.children.push(child);
getChildDetails(child); // Async function to get children info..
parent.save(); //Save the parent so the information we return is persisted.
return parent; //Children probably not fully populated here. Not a problem.
}
function getChildDetails(child){
var promiseapi = require('mypromiseapi');
promiseapi.fetch('childinfo',child.name).then(function(result){
child.attribute1 = result.attribute1;
child.attribute2 = result.attribute2;
}).then( function(){
child.save(); // I expect the parent's information to be updated.
});
}
However, I am now in a little bit of a desync issue. Parent object has the single child on it, but only name and some mongoose specific information is populated (objectId). A child table is created also, with the child information fully populated, and it has the same objectID as the child that's affixed to the parent.
Why does the object that is affixed to the parent not get updated when I save it independently elsewhere in my code?

I solved it by using Populations (http://mongoosejs.com/docs/populate.html) instead of the pure schemas. The new schema models look like this:
var childrenSchema = new Schema({
name : String,
date : {type : Date, default: Date.now},
attribute1 : String,
attribute2 : String,
})
var parentSchema = new Schema({
name: String,
children: [{type: Schema.Types.ObjectId, ref:'Child'}]
})
And the new addParent method looks like this:
function addParent(p){
var parent = new Parent();
parent.name = p.result.name;
var child = new Child();
child.name = p.result.childname;
parent.children.push(child._id);
child.save();
getChildDetails(child); // Async function to get children info..
parent.save(function(err,result){
Parent.populate(result,{path:'children'},function(err,resultparent){
return(result);
});
}); //Save the parent so the information we return is persisted.
}

Related

how to query mongoDb with array of boolean fields?

My mongoose model schema looks like -
{
email: { type: String},
Date: {type: Date},
isOnboarded: {type: Boolean},
isVip: {type: Boolean},
isAdult: {type: Boolean}
}
In my frontend I have 3 checkboxes for "isVip", "isOnboarded" and "isAdult" options. If they are checked I'm adding them to an array, which I'll pass to the server. Let's say if "isVip" and "isAdult" are checked, I will pass [isVip, isAdult] in post api to server. Now how can I write a query to get all the documents with the fields in array as true i.e in above example how can I retrieve all docs with {isVip: true, isAdult:true}
I'm having trouble because the array values keep changing, it can be only one field or 3 fields. I couldn't find a way to give condition inside mongoose query.
User.find(
{ [req.query.array]: true},
{ projection: { _id: 0 } }
)
User is my mongoose model.
I want something like this (documents with the value 'true' for the fields given in the array) and 'req.query.array' is the array with field names I passed from frontend.
You have to create your object in JS and pass then to mongo in this way:
var query = {}
if(isVip) query["isVip"] = true;
if(isOnboarded) query["isOnboarded"] = true;
if(isAdult) query["isAdult"] = true;
And then you can use the mongoose method you want, for example:
var found = await model.find(query)
And this will return the document that matches the elements.
Also, note that this is to create and object to be read by the query, so you can use this into an aggregation pipeline or whatever you vant
Check the output:
var query = {}
query["isVip"] = true;
query["isOnboarded"] = true;
query["isAdult"] = true;
console.log(query)
Is the same object that is used to do the query here
{
"isVip": true,
"isOnboarded": true,
"isAdult": true
}
Also, to know if your post contains "isVip", "isOnboarded" or "isAdult" is a javascript question, not a mongo one, but you can use something like this (I assume you pass a string array):
var apiArray = ["isVip","isAdult"]
var isVip = apiArray.includes("isVip")
var isAdult = apiArray.includes("isAdult")
var isOnboarded = apiArray.includes("isOnboarded")
console.log("isVip: "+isVip)
console.log("isAdult: "+isAdult)
console.log("isOnboarded: "+isOnboarded)

Mongoose Populate and Search by Array of References

I'm trying to wrap my head around the Mongoose Populate syntax and structure. I have two schemas. The Parent has an array of Child references.
const Parent = new Schema({
name: String,
children: [{type: Schema.ObjectId, ref: 'Child'}]
});
const Child = new Schema({
name: String
});
To populate the Parent I've been doing this:
Parent
.findById(parent.id)
.populate('children')
.exec( (err, parent) => {
parent.children = arrayOfInsertedChildDocs;
parent.save();
});
The Parent references save, but is there a way to query for Parents that have a reference to a certain Child? For example all Parents that have a reference to a child with the Id of ObjectId('xxxxxxxxx') in their children array?
This is what I've been trying but it's not working.
let query = { "children._id": mongoose.Types.ObjectId(childId) };
Parent.find(query, (err, parents) => {
//process parents
})
Is this possible?
Figured it out. The query to get the Parent from a child id in the nested array is:
Parent
.find({"children":ObjectId(child.id)})
.populate("children")
.exec((err,parents) => {
//process parents
});
You want to search for element in MongoDB array set. Suppose you have a document.
{
name :"harry",
childen:["10ahhdj20","9ajskjakj9","8aiisu38","2jjaksjah0"]
}
So for searching inside an array list, you can use this.
db.parents.find( { tags: { $all: [ "10ahhdj20", "812922dd" ] } } )
You will get all the parents who have these children.

Mongoose Plugin - schema.add to Subschema

I am writing a mongoose plugin that involves adding a elements to the subschema. I was thinking it would be quite straightforward, but I've been trying unsuccessfully for a few days now.
Below is a simplification of the schema:
var inheritables = require('./inheritables.server.module.js');
var OuFieldTypeSchema = new Schema({
name: String,
desc: String
});
var OuFieldType = mongoose.model('OuFieldType', OuFieldTypeSchema);
var OuSchema = new Schema({
name: String,
fieldTypes: [OuFieldTypeSchema],
});
OuSchema.plugin(inheritables, {inherit: ['fieldTypes']});
mongoose.model('Ou', OuSchema);
Within the body of my Mongoose Plugin, I'd like to iterate over the "inherit" array and add the following to the schemas that represent each of these elements in the Ou schema;
A simplification of my plugin is as follows:
var _ = require('lodash');
module.exports = exports = function(schema, options) {
_.each(options.inherit, function(currItemSchema){
var addToSchema = {inheritedFrom: {name: String}};
//Really want to add this to the OuFieldTypeSchema represented by the fieldTypes element array.
schema.add(addToSchema, currItemSchema + ".");
});
});
Note that the second parameter of add() adds a prefix, but this doesn't work.
I've also tried:
_.each(options.inherit, function(currItemSchema){
var addToSchema = {};
addToSchema[currItemSchema] = {inheritedFrom: {name: String}};
schema.add(addToSchema);
});
The expected behaviour here is that elsewhere in my plugin, I have a method that sets the inheritedFrom schema. No error is received when the item is set, but the resulting output never sees the inheritedFrom elements.
I am currently adding inheritedFrom to each subschema that is used by OuFieldType, but the above is a simplification and is not very DRY.
I'm new to Node.JS - any pointers in the right direction would be greatly appreciated.

Why are mongoose references turning into IDs?

According to http://mongoosejs.com/docs/populate.html, if I set a ref property to an object, and not an ID, when getting it, I should get back an object and not an ID. I'm referring to this part of the page:
var guille = new Person({ name: 'Guillermo' });
guille.save(function (err) {
if (err) return handleError(err);
story._creator = guille;
console.log(story._creator.name);
// prints "Guillermo" in mongoose >= 3.6
// see https://github.com/LearnBoost/mongoose/wiki/3.6-release-notes
Here's my sample code:
var T1schema = new mongoose.Schema({
otherModel:{type:mongoose.Schema.Types.ObjectId, ref:"T2"}
});
var T1 = mongoose.model('T1', T1schema);
var T2schema = new mongoose.Schema({
email: {type: String},
});
var T2 = mongoose.model('T2', T2schema);
var t1 = new T1();
var t2 = new T2({email:"foo#bar.com"});
t1.otherModel = t2;
Now when I refer to t1.otherModel, it's just an ObjectId, and not a T2. For example:
console.log(t1.otherModel.email);
prints undefined instead of "foo#bar.com". Any ideas why? Note I'm using Mongoose 3.6.18 according to it's package.json.
Thanks!
I think your expectations here just don't match what mongoose does. The schema is a way of saying "model M's property P will always be of type T". So when you set a value, mongoose uses the schema definitions to cast the set value to the type the schema requires. Here's a little REPL session. Note setting a number property with a string value casts it to a number, but trying to store a boolean in a number field just ignores the errant value.
> var mongoose = require('mongoose')
> var schema = new mongoose.Schema({prop1: Number})
> var Model = mongoose.model('Model', schema)
> var m = new Model
> m.prop1 = 42
42
> m.prop1
42
> m.prop1 = "42"
'42'
> m.prop1
42
> m.prop1 = false
false
> m.prop1
42
So when your schema says something is going to be an ObjectId, if you give it a model instance, mongoose immediately converts it to an ObjectId in preparation for a write to the database, which is the common case. Normally if you just set that model instance, you don't need to get it back out of the parent model before saving the parent model to the database.
So the model instance getter defined properties are always going to return something compatible with the schema, and populate has to do with loading refs from the DB, but for whatever reason mongoose just doesn't work the same comparing a .populated instance with a non-populated instance. I can see why this is confusing and perhaps unexpected/disappointing though.
Mongoose is normalizing the instance to match the Schema, which specifies that otherModel is an ObjectId.
otherModel:{type:mongoose.Schema.Types.ObjectId, ref:"T2"}
So, Mongoose treats t1.otherModel = t2; the same as:
t1.otherModel = t2._id;
The ref isn't used until .populate() is called (either directly on the document or in a query), which needs both objects to be saved:
t2.save(function (err) {
t1.save(function (err) {
console.log(t1.otherModel);
// 7890ABCD...
t1.populate('otherModel', function () {
console.log(t1.otherModel);
// { email: 'foo#bar.com', _id: 7890ABCD..., __v: 0 }
});
});
});

Embedded document not saved to its collection

It seems as if my embedded documents are not being saved to their respective collections. Here's my model:
var County = new Schema({
_id : Schema.ObjectId,
name : String,
biggestCity : String
});
var Country = new Schema({
_id : Schema.ObjectId,
name : String,
counties : {type: [County], ref: "County"}
});
var Continent = new Schema({
_id : Schema.ObjectId,
countries : {type: [Country], ref: "Country"},
});
...and here's the code that I use to save them to MongoDB:
var continentModel = mongoose.model("Continent");
var continent = new continentModel();
country.name = name;
var countryModel = mongoose.model("Country");
var countyModel = mongoose.model("County");
for (var i = 0; i < req.body.countries.length; i++) {
var country = new countryModel();
country.name = req.body.countries[i].name;
for (var j = 0; j < req.body.countries[i].counties.length; j++) {
var county = new countyModel();
county.name = req.body.countries[i].counties[j].name;
county.biggestCity = req.body.countries[i].counties[j].biggestCity;
countries.counties.push(county);
}
continent.countries.push(country;
}
continent.save();
If I do a db.continents.find(), a document comes back with all the properties (including country and county) populated.
But if I do a db.counties.find() or a db.countries.find(), nothing comes back. So it seems as if the County and Country documents are not being saved to the DB to their respective collections, but rather saved to the Continent collection as regular properties instead (not embedded documents).
What am I doing wrong?
This may be too simple, but you are only calling continent.save() and never calling county.save() or country.save() at the end of the for loops. Is that just an omission or does that fix the problem. If it is an omission, please see my note about posting the output.

Resources