I have a fairly long and complicated nested objected with optional objects inside (see below). I have a request coming in that could have some fields/objects missing - how can I handle that? Can I just use the "?" operand - I have tried but can't seem to get it working. Is there another method I could try?
const product = new Product({
prodID: req.body.prodID,
prodComponents: {
textComponent :{
item: req.body.prodComponents.textComponent.item ? req.body.prodComponents.textComponent.item : null
}
imageComponent:{
item: req.body.prodComponents.imageComponent.item ? req.body.prodComponents.imageComponent.item : null
}
}
)};
There are plenty of ways to resolve your problem. The variable: req.body is just another object as almost everything in javascript.
I'd recommend taking that object and parse it into variables or other object that your program or data model will handle with no exceptions of access into empty or undefined fields.
function createProductDtoToProduct(data) {
const model = {
prodID: data.prodID,
prodComponents: {
textComponent: {
item: null
},
imageComponent: {
item: null
}
}
};
if (data.prodComponents && data.prodComponents.textComponentItem)
model.prodComponents.textComponent.item = {
id: data.textComponentItem.id,
title: prodComponents.textComponentItem.title || "defaultTitle",
}
if (data.prodComponents && data.prodComponents.imageComponentItem)
model.prodComponents.imageComponent.item = {
id: data.imageComponentItem.id,
title: prodComponents.imageComponentItem.title || "defaultTitle",
}
return model;
}
router.post("/createProduct", async (req, res) => {
const product = new Product(createProductDtoToProduct(req.body));
await db.save(product);
return "OK";
});
Also you can validate the body of the request with many tools such as:
https://express-validator.github.io/docs/
I have been searching for a while and I didn't find any good answer. I have n-deep tree that I am storing in DB and I would like to populate all parents so in the end I get the full tree
node
-parent
-parent
.
.
-parent
So far I populate to level 2, and as I mentioned I need to get to level n.
Node.find().populate('parent').exec(function (err, items) {
if (!err) {
Node.populate(items, {path: 'parent.parent'}, function (err, data) {
return res.send(data);
});
} else {
res.statusCode = code;
return res.send(err.message);
}
});
you can do this now (with https://www.mongodb.com/blog/post/introducing-version-40-mongoose-nodejs-odm)
var mongoose = require('mongoose');
// mongoose.Promise = require('bluebird'); // it should work with native Promise
mongoose.connect('mongodb://......');
var NodeSchema = new mongoose.Schema({
children: [{type: mongoose.Schema.Types.ObjectId, ref: 'Node'}],
name: String
});
var autoPopulateChildren = function(next) {
this.populate('children');
next();
};
NodeSchema
.pre('findOne', autoPopulateChildren)
.pre('find', autoPopulateChildren)
var Node = mongoose.model('Node', NodeSchema)
var root=new Node({name:'1'})
var header=new Node({name:'2'})
var main=new Node({name:'3'})
var foo=new Node({name:'foo'})
var bar=new Node({name:'bar'})
root.children=[header, main]
main.children=[foo, bar]
Node.remove({})
.then(Promise.all([foo, bar, header, main, root].map(p=>p.save())))
.then(_=>Node.findOne({name:'1'}))
.then(r=>console.log(r.children[1].children[0].name)) // foo
simple alternative, without Mongoose:
function upsert(coll, o){ // takes object returns ids inserted
if (o.children){
return Promise.all(o.children.map(i=>upsert(coll,i)))
.then(children=>Object.assign(o, {children})) // replace the objects children by their mongo ids
.then(o=>coll.insertOne(o))
.then(r=>r.insertedId);
} else {
return coll.insertOne(o)
.then(r=>r.insertedId);
}
}
var root = {
name: '1',
children: [
{
name: '2'
},
{
name: '3',
children: [
{
name: 'foo'
},
{
name: 'bar'
}
]
}
]
}
upsert(mycoll, root)
const populateChildren = (coll, _id) => // takes a collection and a document id and returns this document fully nested with its children
coll.findOne({_id})
.then(function(o){
if (!o.children) return o;
return Promise.all(o.children.map(i=>populateChildren(coll,i)))
.then(children=>Object.assign(o, {children}))
});
const populateParents = (coll, _id) => // takes a collection and a document id and returns this document fully nested with its parents, that's more what OP wanted
coll.findOne({_id})
.then(function(o){
if (!o.parent) return o;
return populateParents(coll, o.parent))) // o.parent should be an id
.then(parent => Object.assign(o, {parent})) // replace that id with the document
});
Another approach is to take advantage of the fact that Model.populate() returns a promise, and that you can fulfill a promise with another promise.
You can recursively populate the node in question via:
Node.findOne({ "_id": req.params.id }, function(err, node) {
populateParents(node).then(function(){
// Do something with node
});
});
populateParents could look like the following:
var Promise = require('bluebird');
function populateParents(node) {
return Node.populate(node, { path: "parent" }).then(function(node) {
return node.parent ? populateParents(node.parent) : Promise.fulfill(node);
});
}
It's not the most performant approach, but if your N is small this would work.
Now with Mongoose 4 this can be done. Now you can recurse deeper than a single level.
Example
User.findOne({ userId: userId })
.populate({
path: 'enrollments.course',
populate: {
path: 'playlists',
model: 'Playlist',
populate: {
path: 'videos',
model: 'Video'
}
}
})
.populate('degrees')
.exec()
You can find the official documentation for Mongoose Deep Populate from here.
Just don't :)
There is no good way to do that. Even if you do some map-reduce, it will have terrible performance and problems with sharding if you have it or will ever need it.
Mongo as NoSQL database is really great for storing tree documents. You can store whole tree and then use map-reduce to get some particular leafs from it if you don't have a lot of "find particular leaf" queries. If this doesn't work for you, go with two collections:
Simplified tree structure: {_id: "tree1", tree: {1: [2, {3: [4, {5: 6}, 7]}]}}. Numbers are just IDs of nodes. This way you'll get whole document in one query. Then you just extract all ids and run second query.
Nodes: {_id: 1, data: "something"}, {_id: 2, data: "something else"}.
Then you can write simple recurring function which will replace node ids from first collection with data from second. 2 queries and simple client-side processing.
Small update:
You can extend second collection to be a little more flexible:
{_id: 2, data: "something", children:[3, 7], parents: [1, 12, 13]}
This way you'll be able to start your search from any leaf. And then, use map-reduce to get to the top or to the bottom of this part of tree.
This is a more straight forward approach to caub's answer and great solution. I found it a bit hard to make sense of at first so I put this version together.
Important, you need both 'findOne' and 'find' middleware hooks in place for this solution to work. *
* Also, the model definition must come after the middleware definition *
const mongoose = require('mongoose');
const NodeSchema = new mongoose.Schema({
children: [mongoose.Schema.Types.ObjectId],
name: String
});
const autoPopulateChildren = function (next) {
this.populate('children');
next();
};
NodeSchema
.pre('findOne', autoPopulateChildren)
.pre('find', autoPopulateChildren)
const Node = mongoose.model('Node', NodeSchema)
const root = new Node({ name: '1' })
const main = new Node({ name: '3' })
const foo = new Node({ name: 'foo' })
root.children = [main]
main.children = [foo]
mongoose.connect('mongodb://localhost:27017/try', { useNewUrlParser: true }, async () => {
await Node.remove({});
await foo.save();
await main.save();
await root.save();
const result = await Node.findOne({ name: '1' });
console.log(result.children[0].children[0].name);
});
I tried #fzembow's solution but it seemed to return the object from the deepest populated path. In my case I needed to recursively populate an object, but then return the very same object. I did it like that:
// Schema definition
const NodeSchema = new Schema({
name: { type: String, unique: true, required: true },
parent: { type: Schema.Types.ObjectId, ref: 'Node' },
});
const Node = mongoose.model('Node', NodeSchema);
// method
const Promise = require('bluebird');
const recursivelyPopulatePath = (entry, path) => {
if (entry[path]) {
return Node.findById(entry[path])
.then((foundPath) => {
return recursivelyPopulatePath(foundPath, path)
.then((populatedFoundPath) => {
entry[path] = populatedFoundPath;
return Promise.resolve(entry);
});
});
}
return Promise.resolve(entry);
};
//sample usage
Node.findOne({ name: 'someName' })
.then((category) => {
if (category) {
recursivelyPopulatePath(category, 'parent')
.then((populatedNode) => {
// ^^^^^^^^^^^^^^^^^ here is your object but populated recursively
});
} else {
...
}
})
Beware it's not very efficient. If you need to run such query often or at deep levels, then you should rethink your design
Maybe a lot late for that but mongoose has some documentation on this :
Ancestors Tree Array
Materialized Path Tree Array
I think the first one is more appropriate to you as you are looking to populate parents.
With that solution, you can with one regex query, search all the documents matching your designered output tree.
You would setup documents with this Schema :
Tree: {
name: String,
path: String
}
Paths field would be the absolute path in your tree :
/mens
/mens/shoes
/mens/shoes/boots
/womens
/womens/shoes
/womens/shoes/boots
For example you could search all the childrens of your node '/mens/shoes' with one query :
await Tree.find({ path: /^\/mens/shoes })
It would return all the documents where the path starts with /mens/shoes :
/mens/shoes
/mens/shoes/boots
Then you'd only need some client-side logic to arrange it in a tree structure (a map-reduce)
I am a noob with Node.JS.
I am using CouchDB and Cradle.
In couchDB I have a database named 'test' and inside it I have a document named 'exercise'.
The document has 2 fields: "FullName" and "Age".
The code in order to save the data is as follows:
var cradle = require('cradle');
var connection = new(cradle.Connection)('http://127.0.0.1', 5984, {
auth: { username: 'toto_finish', password: 'password' }
});
var db = connection.database('test');
db.save('exercise', {
FullName: param_name, Age: param_age
}, function (err, res) {
if (err) {
// Handle error
response += ' SAVE ERROR: Could not save record!!\n';
} else {
// Handle success
response += ' SUCESSFUL SAVE: The record was saved in CouchDB!\n';
}
http_res.end(response);
});
this code works well and it saves the data to the CouchDB.
My problem is when I want to read the data.
The code that I wrote is:
var cradle = require('cradle');
var connection = new(cradle.Connection)('http://127.0.0.1', 5984, {
auth: { username: 'toto_finish', password: 'password' }
});
var db = connection.database('test');
db.view('exercise/all', {descending: true}, function(err, res)
{
console.log(res);
res.forEach(function (row) {
response = 'FullName: ' + row.FullName + '\n Age: ' + row.Age + '\n';
});
});
http_res.end(response);
when I am trying to print response, response is empty and I don't know what I am doing wrong. I know that it does not go inside the forEach loop but I don't understand why.
the console output is:
[ { id: 'exercise',
key: null,
value:
{ _id: 'exercise',
_rev: '1-7042e6f49a3156d2099e8ccb3cc7d937',
FullName: 'Toto Finish',
Age: '30' } } ]
Thanks in advance for any response or answer.
Try moving the http_res.send() call inside the callback provided to db.view - the anonymous function( err, res ) { }.
I'm not sure however about the .forEach statement, you'll only get the last value from your query in the response variable, you should look into that as well.
spotirca is right
The db.view function is async so http_res.end(response) gets called before the view returns any data.
You can prove this by returning the date in both the console.log and http_res.end
console.log(res, new Date())
and
http_res.end(response, new Date());
The http response will have the earlier date/Time.
I'm attempting to use the mapReduce function of Mongodb via Mongoose, but the map function I'm passing in is never called. Here is the data currently contained in the "Post" model collection:
[ { data: 'Tag test data',
name: 'Tag Test',
_id: 5130dff2560105c235000002,
__v: 0,
comments: [],
tags: [ 'tag1', 'tag2', 'tag3' ] },
{ data: 'Testing tags. Again.',
name: 'Another test post',
_id: 5131213b611fe1f443000002,
__v: 0,
comments: [],
tags: [ 'tags', 'test', 'again' ] } ]
Here is the code:
var Schema = mongoose.Schema;
var PostSchema = new Schema ({
name : String
, data : String
, tags : [String]
});
mongoose.model('Post', PostSchema);
var o = {};
o.map = function() {
if (!this.tags) {
//console.log('No tags found for Post ' + this.name);
return;
}
for (index in this.tags) {
emit(this.tags[index], 1);
}
}
o.reduce = function(previous, current) {
var count = 0;
for (index in current) {
count += current[index];
}
return count;
}
o.out = { replace : 'tags'}
o.verbose = true;
var Post = mongoose.model('Post');
Post.mapReduce(o, function(error, model, stats) {
console.log('model: ' + model);
console.log('stats: ' + stats);
});
The "model" and "stats" objects are always undefined, and the log statements in the map function are never called. If I do something like this with the Post model outside of the mapReduce function, I get the data at the top of the post as expected:
Post.find().exec(function(err, posts) {
console.log(posts);
});
Any suggestions? I'm sure something is just slightly off...
You can't call console.log from within the map and reduce functions as it's not supported by Mongo's JavaScript engine.
To debug your map/reduce/finalize functions you can use the MongoDB print statement. The output will be added to your Mongo log file.
I'm playing with nodeJS and mongoJS. I've successfully queried the DB and returned a set of data:
var databaseUrl = "redirect"; // "username:password#example.com/mydb"
var collections = ["things", "reports"]
var db = require("mongojs").connect(databaseUrl, collections);
db.things.find({url: "google.com"}, function(err, things) {
if( err || !things) {
console.log("URL not Found");
}
else things.forEach( function(RedirectURL) {
console.log(RedirectURL);
} );
});
This returns this:
{ _id: 4fb2c304f2dc05fe12e8c428,
url: 'google.com',
redirect:
[ { url: 'www.goolge.com', weight: 10 },
{ url: 'www.google.co.uk', weight: 20 } ] }
What I need to do now is select certain elements of the array such as redirect.weight or redirect.url.
Does mongoJS allow me to do this easily or is there a better way?
Any pointers greatly appreciated as ever!
Ric
MongoJS implements the mongo API. Calls to document contents use the dot notation.
So in the example above,
var weight = RedirectURL.redirect.0.weight
// equal to 10, since it is the first item of the redirect array.
The same principle can be used with the find commands. So if you wish to find documents with weight = 10, use the following command
db.things.find({redirect.weight: 10})