What could be wrong with this update function?
fixrecs2 = function() {
var arr = myColl.find({ d: 1 }).fetch();
for (var i = 0; i < arr.length; i++) {
var code = arr[i].c;
var rec = myOtherColl.findOne( { cc: code });
if (rec) {
c(rec._id)
myOtherColl.update( {_id: rec._ID }, { $set: {dt: "ant"} } );
}
}
console.log(i + " records processed.");
}
I have never had trouble updating my documents before in this way. Checking the output in the console, I can tell that all the records that I expect to find are there. I can see their _id values printed by console.log(). But the dt field does not get updated. In some cases, the dt field already exists, in some cases it doesn't, but update is supposed to add a field if it's not there, right?
I have tried adding a callback, but it did not seem to run. (I have not been able to find a good callback example for the update function.) In any case, according to the docs, I should get an error message in the console if update fails. I'm still running the insecure package, so there's no allow or deny rules to worry about. I'm really stumped by this!
The issue in this case, turned out to be a simple misspelling, rec._ID is not the same as rec._id. This was likely overlooked due to the common capitalization of MongoDB's ObjectID.
Related
Why on earth is this update failing? This is basic, I've done this many other times.
Meteor.users.update(id, {$set: {lastname: "Archer"}});
That query above has even been simplified from what it originally was, and it's still having the same problem. Originally I was compiling the update programatically:
console.log(obj);
console.log(id);
if (obj) collection.update(id, obj);
Here is my exact output from those lines:
Object {$set: Object}
$set: Object
lastname: "Archer"
__proto__: Object
__proto__: Object
main.js?b9104f881abb80f93da518738bf1bfb4cab0b2b6:68
YXeudfnHyKmsGXaEL
main.js?b9104f881abb80f93da518738bf1bfb4cab0b2b6:69
update failed: MongoError: invalid query
debug.js:41
The id is correct, and if the obj has something wrong with it, it'll be news to me!
Now, I'm quite positive this has nothing to do with my allow function. In the first tests of this part of my program, I actually did get the update not allowed (or whatever it is) error, but I modified my allow function to take this into account, and that error went away.
Here's my allow function. I have a user system where a user can have a student object that "mirrors" it's name information, hence the added complexity. That portion of the function is only engaged in certain circumstances, and it doesn't alter the behavior of the allowance.
Meteor.users.allow({
update: function (userId, doc, fields, modifier) {
var allow = (userId && (doc._id === userId) && _.without(fields,
'firstname', 'lastname', 'student_ids', 'payment_ids', 'phones').length == 0) || Meteor.user().admin;
if (allow && modifier.$set && (_.contains(fields, 'firstname') || _.contains(fields, 'lastname'))) {
var user = Meteor.users.findOne(userId);
var obj = {};
if (modifier.$set.firstname) obj.firstname = modifier.$set.firstname;
if (modifier.$set.lastname) obj.lastname = modifier.$set.lastname;
if (obj) Students.update({_id: {$in: user.student_ids}, reflectsUser: true}, {$set: obj});
}
return allow;
}
});
It turns out my allow function was the problem, but in a bit of a sneaky way.
The Meteor.users.update call wasn't actually the one that was failing, it was this one:
Students.update({_id: {$in: user.student_ids}, reflectsUser: true}, {$set: obj});
I wasn't properly checking that user.student_ids field, so if it was undefined (that user didn't have any students) then the query was invalid. Throwing in a line to vet that array:
var student_ids = user.student_ids || [];
solved the problem.
Since the Meteor error didn't tell me which query was invalid, it led me on a bit of a wild goose chase. Just goes to show that good errors can go a long way!
Also, performing database queries that have other database queries as side-effects is something to be done very carefully.
I know this is a nood question, but I'm trying to work out how to update a value in a document from a route in iron router. I've found the spot I need to put the function, but I'm struggling with the mongo code needed to make it work.
I'm trying to increment a views element each time a link is clicked, so have added the following code to the route.
data: function () {
var project = projectDocs.findOne(this.params._id);
// need to increment views value by one
console.log(project.views);
projectDocs.update({id: project.id},
{$inc: {views: 1}}
);
console.log(project.views);
return project;
}
});
The project.views value is returning the correct value, but the code to update the value throws an exception at the moment.
I tried the simple thing of project.views++ which increments the variable within the function but it never gets pushed to the database (no surprises there I guess).
Can someone point me in the direction I need to get this value to inc (and is this even the right place to do this?).
Thanks.
Peter.
OK, I found this link that has lead me part of the way http://books.google.com.au/books?id=uGUKiNkKRJ0C&pg=PA37&lpg=PA37&dq=Cannot+apply+$inc+modifier+to+non-number&source=bl&ots=h7qyOddRsf&sig=EWFw9kNLGHoFEUS-nTNsBStDRcQ&hl=en&sa=X&ei=cRGXUse0DNGciAfk6YHgCA&ved=0CFcQ6AEwBQ#v=onepage&q=Cannot%20apply%20%24inc%20modifier%20to%20non-number&f=false which explains that you can only inc numeric values (I had this as a string it seems.
Now the problem is that I seem to be in an endless loop.
The function now looks like
this.route('projectPage', {
path: '/projects/:_id',
waitOn: function() {
return Meteor.subscribe('singleProject', this.params._id);
},
data: function () {
var project = projectDocs.findOne(this.params._id);
// need to increment views value by one
console.log("Views", project.views);
console.log("Project", project);
projectDocs.update(project._id,
{$inc: {views: 1}}
);
console.log(project.views);
return project;
}
});
Why would this be looping?
Use _id instead of id. So
projectDocs.update({_id: project._id},
{$inc: {views: 1}}
);
If that's not it, perhaps you could update your answer with whatever exception you are getting.
Just read the fantastic new documentation on iron-router a bit further and moved the $inc function to the unload hook and all seems to be good.
this.route('projectPage', {
path: '/projects/:_id',
waitOn: function() {
return Meteor.subscribe('singleProject', this.params._id);
},
data: function () {
return projectDocs.findOne(this.params._id);
},
unload: function() {
var project = projectDocs.findOne(this.params._id);
// need to increment views value by one
projectDocs.update(project._id,
{$inc: {views: 1}}
);
}
// could possibly use layout: popup_layout? here
});
Would love some confirmation that this is actually where I should be doing this (and it does seem a bit inefficient to be doing so many "findOne"'s) but its working for the moment.
Ok, still in my toy app, I want to find out the average mileage on a group of car owners' odometers. This is pretty easy on the client but doesn't scale. Right? But on the server, I don't exactly see how to accomplish it.
Questions:
How do you implement something on the server then use it on the client?
How do you use the $avg aggregation function of mongo to leverage its optimized aggregation function?
Or alternatively to (2) how do you do a map/reduce on the server and make it available to the client?
The suggestion by #HubertOG was to use Meteor.call, which makes sense and I did this:
# Client side
Template.mileage.average_miles = ->
answer = null
Meteor.call "average_mileage", (error, result) ->
console.log "got average mileage result #{result}"
answer = result
console.log "but wait, answer = #{answer}"
answer
# Server side
Meteor.methods average_mileage: ->
console.log "server mileage called"
total = count = 0
r = Mileage.find({}).forEach (mileage) ->
total += mileage.mileage
count += 1
console.log "server about to return #{total / count}"
total / count
That would seem to work fine, but it doesn't because as near as I can tell Meteor.call is an asynchronous call and answer will always be a null return. Handling stuff on the server seems like a common enough use case that I must have just overlooked something. What would that be?
Thanks!
As of Meteor 0.6.5, the collection API doesn't support aggregation queries yet because there's no (straightforward) way to do live updates on them. However, you can still write them yourself, and make them available in a Meteor.publish, although the result will be static. In my opinion, doing it this way is still preferable because you can merge multiple aggregations and use the client-side collection API.
Meteor.publish("someAggregation", function (args) {
var sub = this;
// This works for Meteor 0.6.5
var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
// Your arguments to Mongo's aggregation. Make these however you want.
var pipeline = [
{ $match: doSomethingWith(args) },
{ $group: {
_id: whatWeAreGroupingWith(args),
count: { $sum: 1 }
}}
];
db.collection("server_collection_name").aggregate(
pipeline,
// Need to wrap the callback so it gets called in a Fiber.
Meteor.bindEnvironment(
function(err, result) {
// Add each of the results to the subscription.
_.each(result, function(e) {
// Generate a random disposable id for aggregated documents
sub.added("client_collection_name", Random.id(), {
key: e._id.somethingOfInterest,
count: e.count
});
});
sub.ready();
},
function(error) {
Meteor._debug( "Error doing aggregation: " + error);
}
)
);
});
The above is an example grouping/count aggregation. Some things of note:
When you do this, you'll naturally be doing an aggregation on server_collection_name and pushing the results to a different collection called client_collection_name.
This subscription isn't going to be live, and will probably be updated whenever the arguments change, so we use a really simple loop that just pushes all the results out.
The results of the aggregation don't have Mongo ObjectIDs, so we generate some arbitrary ones of our own.
The callback to the aggregation needs to be wrapped in a Fiber. I use Meteor.bindEnvironment here but one can also use a Future for more low-level control.
If you start combining the results of publications like these, you'll need to carefully consider how the randomly generated ids impact the merge box. However, a straightforward implementation of this is just a standard database query, except it is more convenient to use with Meteor APIs client-side.
TL;DR version: Almost anytime you are pushing data out from the server, a publish is preferable to a method.
For more information about different ways to do aggregation, check out this post.
I did this with the 'aggregate' method. (ver 0.7.x)
if(Meteor.isServer){
Future = Npm.require('fibers/future');
Meteor.methods({
'aggregate' : function(param){
var fut = new Future();
MongoInternals.defaultRemoteCollectionDriver().mongo._getCollection(param.collection).aggregate(param.pipe,function(err, result){
fut.return(result);
});
return fut.wait();
}
,'test':function(param){
var _param = {
pipe : [
{ $unwind:'$data' },
{ $match:{
'data.y':"2031",
'data.m':'01',
'data.d':'01'
}},
{ $project : {
'_id':0
,'project_id' : "$project_id"
,'idx' : "$data.idx"
,'y' : '$data.y'
,'m' : '$data.m'
,'d' : '$data.d'
}}
],
collection:"yourCollection"
}
Meteor.call('aggregate',_param);
}
});
}
If you want reactivity, use Meteor.publish instead of Meteor.call. There's an example in the docs where they publish the number of messages in a given room (just above the documentation for this.userId), you should be able to do something similar.
You can use Meteor.methods for that.
// server
Meteor.methods({
average: function() {
...
return something;
},
});
// client
var _avg = { /* Create an object to store value and dependency */
dep: new Deps.Dependency();
};
Template.mileage.rendered = function() {
_avg.init = true;
};
Template.mileage.averageMiles = function() {
_avg.dep.depend(); /* Make the function rerun when _avg.dep is touched */
if(_avg.init) { /* Fetch the value from the server if not yet done */
_avg.init = false;
Meteor.call('average', function(error, result) {
_avg.val = result;
_avg.dep.changed(); /* Rerun the helper */
});
}
return _avg.val;
});
I'm using meteor 0.3.7 in Win7(32) and trying to create a simple logging system using 2 MongoDB collections to store data that are linked by DBRef.
The current pseudo schema is :
Users {
username : String,
password : String,
created : Timestamp,
}
Logs {
user_id : DBRef {$id, $ref}
message : String
}
I use server methods to insert the logs so I can do some upserts on the clients collection.
Now I want to do an old "left join" and display a list of the last n logs with the embedded User name.
I don't want to embed the Logs in Users because the most used operation is getting the last n logs. Embedding in my opinion was going to have a big impact in performance.
What is the best approach to achieve this?
Next it was great if possible to edit the User name and all items change theis name
Regards
Playing around with Cursor.observe answered my question. It may not be the most effective way of doing this, but solves my future problems of derefering DBRefs "links"
So for the server we need to publish a special collection. One that can enumerate the cursor and for each document search for the corresponding DBRef.
Bare in mind this implementation is hardcoded and should be done as a package like UnRefCollection.
Server Side
CC.Logs = new Meteor.Collection("logs");
CC.Users = new Meteor.Collection("users");
Meteor.publish('logsAndUsers', function (page, size) {
var self = this;
var startup = true;
var startupList = [], uniqArr = [];
page = page || 1;
size = size || 100;
var skip = (page - 1) * size;
var cursor = CC.Logs.find({}, {limit : size, skip : skip});
var handle = cursor.observe({
added : function(doc, idx){
var clone = _.clone(doc);
var refId = clone.user_id.oid; // showld search DBRefs
if (startup){
startupList.push(clone);
if (!_.contains(uniqArr, refId))
uniqArr.push(refId);
} else {
// Clients added logs
var deref = CC.Users.findOne({_id : refid});
clone.user = deref;
self.set('logsAndUsers', clone._id, clone);
self.flush();
}
},
removed : function(doc, idx){
self.unset('logsAndUsers', doc._id, _.keys(doc));
self.flush();
},
changed : function(new_document, idx, old_document){
var set = {};
_.each(new_document, function (v, k) {
if (!_.isEqual(v, old_document[k]))
set[k] = v;
});
self.set('logsAndUsers', new_document._id, set);
var dead_keys = _.difference(_.keys(old_document), _.keys(new_document));
self.unset('logsAndUsers', new_document._id, dead_keys);
self.flush();
},
moved : function(document, old_index, new_index){
// Not used
}
});
self.onStop(function(){
handle.stop();
});
// Deref on first Run
var derefs = CC.Users.find({_id : {$in : uniqArr} }).fetch();
_.forEach(startupList, function (item){
_.forEach(derefs, function(ditems){
if (item["user_id"].oid === ditems._id){
item.user = ditems;
return false;
}
});
self.set('logsAndUsers', item._id, item);
});
delete derefs; // Not needed anymore
startup = false;
self.complete();
self.flush();
});
For each added logs document it'll search the users collection and try to add to the logs collection the missing information.
The added function is called for each document in the logs collection in the first run I created a startupList and an array of unique users ids so for the first run it'll query the db only once. Its a good idea to put a paging mechanism to speed up things.
Client Side
On the client, subscribe to the logsAndUsers collection, if you want to make changes do it directly to the Logs collection.
LogsAndUsers = new Meteor.collection('logsAndUser');
Logs = new Meteor.colection('logs'); // Changes here are observed in the LogsAndUsers collection
Meteor.autosubscribe(function () {
var page = Session.get('page') || 1;
Meteor.subscribe('logsAndUsers', page);
});
Why not just also store the username in the logs collection as well?
Then you can query on them directly without needing any kind of "join"
If for some reason you need to be able to handle that username change, you just fetch the user object by name, then query on Logs with { user_id : user._id }
Using MongoDB and the latest 10gen C# driver (CSharpDriver-1.3.1.4349), I am trying to do an "in place" update and get back the # of documents effected in the result.
public static long SaveListings(string state, bool isActive, DateTime updateDate)
{
var result = Collection().Update(
Query.And(
Query.EQ("State", state),
Query.And(
Query.EQ("IsActive", isActive),
Query.LT("UpdateDate", updateDate))),
Update.Set("IsActive", false), UpdateFlags.Multi);
return result != null ? result.DocumentsAffected : -1;
}
The result is null for some reason. If I were doing this from the console, I could get the number of rows effected by doing this:
db.Listing.update( { State: state.Abbreviation, IsActive: true, UpdateDate: { $lt: expiredDate } }, { $set: { IsActive: false } }, false, true);
var numRows = db.getLastErrorObj().n;
Any idea what I'm doing wrong or is this a bug in the C# driver?
Update contains overloaded method that take SafeMode. Just add it to your code as fourth parameter to your update and should be not null:
...
UpdateFlags.Multi,
SafeMode.True);
That's not driver bug, it work as expected. Mongodb does not wait for document if get inserted without safe mode (therefore driver return null), but if you saying SafeMode = true -- you force mongodb wait until document get inserted.
Doing inserts and updates without specifying safe mode yields null because they're asynchronous operations - there's simply no way to know how the insert or update went.
Therefore, add e.g. SafeMode.True as the last argument in inserts and updates in cases where you care about the result. This will make the driver issue a getLastError command, which blocks until the insert/updated has completed:
var result = collection.Update(query, update, SafeMode.True);
You can read some more about getLastError and different safe modes here (general) and here (SafeMode with the C# driver).
Try getLastError here: http://www.webtropy.com/articles/art9-1.asp?f=GetLastError