I am building an array with data and want to push that array to sub-document.
var pubArray = [];
var count = 5
for (i = 0; i < count; i++) {
pubArray.push({publicationName: req.body.publicationName[i], dateSent:req.body.dateSent[i]});
};
Students.findOne({studentNumber: filter}, function (err, student) {
student.publications.push({pubArray});
student.save();
});
If I use the {publicationName: req.body.publicationName[i], dateSent:req.body.dateSent[i]} inside the student.publications.push it works fine. If I try to push the array, nothing happens.
Note that the .push() method in mogoose works just like it's JavaScript equivalent in that it is "pushing" a single element onto the array, rather than a whole array. So you can either assign the whole array or just construct in the loop:
student.publications = pubArray;
or:
// Construct with .push in loop:
Students.findOne({ "studentNumber": filter },function(err,student) {
for ( var i = 0; i < count: i++ ) {
student.publications.push({
"publicationName": req.body.publicationName[i],
"dateSent": req.body.dateSent[i]
});
}
student.save(function(err) {
// Complete
});
});
But really you would be better off using an "atomic" operator of $push with $each in a direct update. This is then just one trip to the server, rather than two:
Students.update(
{ "studentNumber": filter },
{ "$push": { "publications": { "$each": pubArray } } },
function(err,numAffected) {
}
);
That is generally worlds better than the "find/modify/save" pattern, and not only in being more efficient, but it also avoids potential conflicts or overwriting data since the object and array is modified "in-place" in the database, with the state current to the time of modification.
Atomic operators should always be favoured for the performance benefits as well as lack of conflicts in modification.
The publications property of the student object is an Array. You can simply assign this property to the pubArray created earlier:
Students.findOne({studentNumber: filter}, function (err, student) {
student.publications = pubArray;
student.save();
});
Is it possible to $addToSet and determine which items were added to the set?
i.e. $addToSet tags to a post and return which ones were actually added
Not really, and not with a single statement. The closest you can get is with the findAndModify() method, and compare the orginal document form to the fields that you submitted in your $addToSet statement:
So considering an initial document:
{
"fields": [ "B", "C" ]
}
And then processing this code:
var setInfo = [ "A", "B" ];
var matched = [];
var doc = db.collection.findAndModify(
{ "_id": "myid" },
{
"$addToSet": { "fields": { "$each": setInfo } }
}
);
doc.fields.forEach(function(field) {
if ( setInfo.indexOf(field) != -1 ) {
matched.push(field);
}
});
return matched;
So that is a basic JavaScript abstraction of the methods and not actually nodejs general syntax for either the native node driver or the Mongoose syntax, but it does describe the basic premise.
So as long as you are using a "default" implementation method that returns the "original" state of the document before it was modified the you can play "spot the difference" as it were, and as is shown in the code example.
But doing this over general "update" operations is just not possible, as they are designed to possibly affect one or more objects and never return this detail.
I have a mongodb model called User, which has a mixed schema type variable called "inventory" (contains all the items a user contains). I would like to loop through all the users, and change the name of each item in their inventory. In particular, I would like to convert strings in the format of "10_alex_magician" or "3_maia_princess" to "alex_magician" and "maia_princess" respectively. The string conversion is relatively straightforward, and I'm using x.split('').slice(1).join('') to accomplish the conversion.
Where I'm having trouble is even when console.log shows that the conversion has been applied, it doesn't seem to be updating correctly to mongodb, yet no error message is being thrown. Does anyone know how to solve this?
Node.js function
//function to change old naming of items "10_alex_magician" to "alex_magician"
function modifyUser() {
User.find({}, function(err, results) {
_.map(results, function(result) {
var regex = /^\d+_[A-Za-z]+_[A-Za-z]+$/
for (var i = 0, len = result.inventory.length; i < len; i++) {
if(regex.test(result.inventory[i].itemName)) {
result.inventory[i].itemName = result.inventory[i].itemName.split('_').slice(1).join('_');
result.save(function(err, r) {
if(err) console.log(err);
//logging r shows that the text has been correctly updated
console.log(r)
});
}
}
})
})
}
Format of inventory variable
"inventory": [
{
"type": "sticker",
"numberOwned": 2,
"itemName": "1_alex_magician"
},
{
"type": "sticker",
"numberOwned": 1,
"itemName": "10_alex_scuba"
}
],
Mongoose only has automatic change detection for top-level properties and you are modifying a nested property, so mongoose doesn't know anything changed. Use markModified to tell mongoose you are mucking with inventory.
result.inventory[i].itemName = result.inventory[i].itemName.split('_').slice(1).join('_');
result.markModified('inventory');
result.save(....)
For efficiency, you may want to consider both .lean() and .stream() for this type of query and just do your updates with findByIdAndUpdate, passing just the updated inventory property.
Consider following sample documents stored in CouchDB
{
"_id":....,
"rev":....,
"type":"orders",
"Period":"2013-01",
"Region":"East",
"Category":"Stationary",
"Product":"Pen",
"Rate":1,
"Qty":10,
"Amount":10
}
{
"_id":....,
"rev":....,
"type":"orders",
"Period":"2013-02",
"Region":"South",
"Category":"Food",
"Product":"Biscuit",
"Rate":7,
"Qty":5,
"Amount":35
}
Consider following SQL query
SELECT Period, Region,Category, Product, Min(Rate),Max(Rate),Count(Rate), Sum(Qty),Sum(Amount)
FROM Sales
GROUP BY Period,Region,Category, Product;
Is it possible to create map/reduce views in couchdb equivalent to the above SQL query and to produce output like
[
{
"Period":"2013-01",
"Region":"East",
"Category":"Stationary",
"Product":"Pen",
"MinRate":1,
"MaxRate":2,
"OrdersCount":20,
"TotQty":1000,
"Amount":1750
},
{
...
}
]
Up front, I believe #benedolph's answer is best-practice and best-case-scenario. Each reduce should ideally return 1 scalar value to keep the code as simple as possible.
However, it is true you'd have to issue multiple queries to retrieve the full resultset described by your question. If you don't have the option to run queries in parallel, or it is really important to keep the number of queries down it is possible to do it all at once.
Your map function will remain pretty simple:
function (doc) {
emit([ doc.Period, doc.Region, doc.Category, doc.Product ], doc);
}
The reduce function is where it gets lengthy:
function (key, values, rereduce) {
// helper function to sum all the values of a specified field in an array of objects
function sumField(arr, field) {
return arr.reduce(function (prev, cur) {
return prev + cur[field];
}, 0);
}
// helper function to create an array of just a single property from an array of objects
// (this function came from underscore.js, at least it's name and concept)
function pluck(arr, field) {
return arr.map(function (item) {
return item[field];
});
}
// rereduce made this more challenging, and I could not thoroughly test this right now
// see the CouchDB wiki for more information
if (rereduce) {
// a rereduce handles transitionary values
// (so the "values" below are the results of previous reduce functions, not the map function)
return {
OrdersCount: sumField(values, "OrdersCount"),
MinRate: Math.min.apply(Math, pluck(values, "MinRate")),
MaxRate: Math.max.apply(Math, pluck(values, "MaxRate")),
TotQty: sumField(values, "TotQty"),
Amount: sumField(values, "Amount")
};
} else {
var rates = pluck(values, "Rate");
// This takes a group of documents and gives you the stats you were asking for
return {
OrdersCount: values.length,
MinRate: Math.min.apply(Math, rates),
MaxRate: Math.max.apply(Math, rates),
TotQty: sumField(values, "Qty"),
Amount: sumField(values, "Amount")
};
}
}
I was not able to test the "rereduce" branch of this code at all, you'll have to do that on your end. (but this should work) See the wiki for information about reduce vs rereduce.
The helper functions I added at the top actually made the code overall much shorter and easier to read, they're largely influenced by my experience with Underscore.js. However, you can't include CommonJS modules in reduce functions, so it has to be written manually.
Again, best-case scenario is to have each aggregated field get it's own map/reduce index, but if that isn't on option to you, the above code should get you what you've described here in the question.
I will propose a very simple solution that requires one view per variable you want to aggregate in your "select" clause. While it is certainly possible to aggregate all variables in a single view, the reduce function would be far more complex.
The design document looks like this:
{
"_id": "_design/ddoc",
"_rev": "...",
"language": "javascript",
"views": {
"rates": {
"map": "function(doc) {\n emit([doc.Period, doc.Region, doc.Category, doc.Product], doc.Rate);\n}",
"reduce": "_stats"
},
"qty": {
"map": "function(doc) {\n emit([doc.Period, doc.Region, doc.Category, doc.Product], doc.Qty);\n}",
"reduce": "_stats"
}
}
}
Now, you can query <couchdb>/<database>/_design/ddoc/_view/rates?group_level=4 to get the statistics about the "Rate" variable. The result should look like this:
{"rows":[
{"key":["2013-01","East","Stationary","Pen"],"value":{"sum":4,"count":3,"min":1,"max":2,"sumsqr":6}},
{"key":["2013-01","North","Stationary","Pen"],"value":{"sum":1,"count":1,"min":1,"max":1,"sumsqr":1}},
{"key":["2013-01","South","Stationary","Pen"],"value":{"sum":0.5,"count":1,"min":0.5,"max":0.5,"sumsqr":0.25}},
{"key":["2013-02","South","Food","Biscuit"],"value":{"sum":7,"count":1,"min":7,"max":7,"sumsqr":49}}
]}
For the "Qty" variable, the query would be <couchdb>/<database>/_design/ddoc/_view/qty?group_level=4.
With the group_level property you can control over which levels the aggregation is to be performed. For example, querying with group_level=2 will aggregate up to "Period" and "Region".
Let's say I have two types of docs with one referencing the other, e.g. "orders" and "order_replies" the later one having a field "ref" which contains an ID of an order document.
For my validate_doc_update function I want a user only to be able to change an order_reply document if he is the author of the original order.
How can I get the "author" field from the order with the ID newDoc.ref within the function?
Here's a part of my validation function so far:
if(userCtx.roles.indexOf('employee') >= 0) {
// [...] other doc types
if (newDoc.type == 'order_reply') {
//newDoc.ref.author is not the proper approach
require((userCtx.name == newDoc.ref.author), "this is not your order reply!");
}
}
Put the author's ID in the order's ID. For example:
The order: { "_id": "order/jack/1", ... }
An order's reply: { "ref": "order/jack/1", ... }
So you can check with:
if (userCtx.roles.indexOf('employee') !== -1) {
if (newDoc.type === 'order_reply') {
require(userCtx.name === newDoc.ref.split('/', 3)[1], "this is not your order reply!");
}
}
If you have multiple authors, use "order/author1/author2/.../authorN/ordernum" as _id of the order and check with:
var parts = newDoc.ref.split('/'),
authors = parts.slice(1, -1);
require(authors.indexOf(userCtx.name) !== -1, "this is not your order reply!");
UPDATE: Due to bug COUCHDB-1229, use of "/" in doc._id may cause problems. Depending on your use-case, it may be better to use another separator, e.g. ":".