I want to use observe for one of my collection on the server but I need to get userId,
I'm trying to use this.userId and also Meteor.userId() but not working! see the below code for more detail and error message
how to resolve it ?
Messages.find({state:"outbox"}).observe({
added: (doc) => {
console.log(" observe ");
console.log("userId : " + this.userId); // undefined
console.log("Meteor.userId(): " + Meteor.userId()); // "Exception in queued task: Error: Meteor.userId can only be invoked in method calls. Use this.userId in publish functions."
//.......
}
});
thanks for you attention.
Within the observe callbacks, the this keyword does not point to the publication object (it points to the cursor of the related query), so it does not have a userId property.
You can create a closure to make the userId available to the function using
const userId = this.userId;
in the body of the publication itself, and then simply use it in the callback (as userId).
Related
I am trying to pass a value in the callback of an async meteor method. "mongoCollections" is global variable
// Async method
let waiter = function(cb) {
setTimeout(() => {
cb(undefined, {data: 'test', other: mongoCollections})
}, 1000);
}
// Meteor method
Meteor.methods({
'getCollections': () => {
let func = Meteor.wrapAsync(waiter);
let res = func();
return res;
}
});
On the client
Meteor.call('getCollections', (err, res) => {
console.log(err, res)
});
The issue is that in its current state the client callback is not fired, no error or anything.
But if I remove the "other: mongoCollections" part of the object then the callback is fired. Why would sending mongoCollections prevent the callback from being fired at all? If there is an error how can I catch it?
My guess is that you are loosing your context between waiter() and the execution of cb(), which means that mongoCollections is undefined in cb(), thus the call fails.
Try to log mongoCollections in the anonymous function that you setTimeout for. It will probably show as undefined.
Or try it like this:
let waiter = function(cb) {
var _mongoCollections = mongoCollections;
setTimeout(() => {
cb(undefined, {data: 'test', other: _mongoCollections})
}, 1000);
}
(which puts mongoCollections in the closure instsead)
Another possibility (based on comment below): Your mongoCollections object is not serializable. You can try it by logging the result of JSON.stringify(mongoCollections). If this fails you have to extract the parts of the object you need, that can be serialized.
There are a number of things that could be happening here, but my guess is that an error is occurring somewhere and the error message is getting swallowed by a handler somewhere deeper.
You probably want to be using Meteor.setTimeout instead of vanilla setTimeout at a minimum. Have a look here: http://docs.meteor.com/api/timers.html#Meteor-setTimeout
Beyond that, I would follow the previous answerer's advice and try to make sure that mongoCollections is as global as you think it is. If the only change between the callback working and not working is the addition of a single symbol, then the culprit is likely that your added symbol is undefined.
This Meteor code is working fine, but I would like to ask if it is the way Meteor does things or it is a un predictable side effect that may change under some condition later.
The things is that when I do
DisplayCol.insert({action: 'task1', element: 'p', value: value_variable});
Meteor also inserts the correct userId (using 2 different browsers logged in as 2 different users) which I did not explicitly included in the document.
The above line of code is inside a server side function which is called from Meteor method.
here is the relevant information;
//lib/collection.js
DisplayCol = new Mongo.Collection('displayCol');
//server.js
Meteor.publish('displayCol', function () {
return DisplayCol.find({userId: this.userId});
});
DisplayCol.before.insert(function (userId, doc) {
doc.userId = userId;
});
In the docs of Collection hooks > Additional notes > second bulleted paragraph says:
userId is available to find and findOne queries that were invoked within a publish function.
But this is a collection.insert. So should I explicitly include the userId in the document or let the collection hook do its hidden magic? Thanks
No, there is no hidden magic in that code, your before hook is inserting the userId field in the document.
When you do an insert like this,
DisplayCol.insert({action: 'task1', element: 'p', value: value_variable});
the doc that your are inserting is { action: 'task1', element: 'p', value: value_variable }
Because, you have this hook,
DisplayCol.before.insert(function (userId, doc) {
doc.userId = userId;
});
it changes the doc before inserting into collection. So the above hook will change your doc to {action: 'task1', element: 'p', value: value_variable, userId: 'actual-user-id' }
This is the expected behaviour.
Regarding your other point in the question,
userId is available to find and findOne queries that were invoked
within a publish function.
Previously userId parameter in the find and findOne returns null, so user needs to pass userId as a parameter as mentioned in this comment. Additional notes mentions that the hack is not required any more. It has nothing to do with inserting userId field into the collection document.
To have a quick test, remove the DisplayCol.before.insert hook above, you will not see userId field in the newly inserted documents.
UPDATE
Just to clarify your doubt further, from the 4th point in the docs that you provided
It is quite normal for userId to sometimes be unavailable to hook
callbacks in some circumstances. For example, if an update is fired
from the server with no user context, the server certainly won't be
able to provide any particular userId.
which means that if the document is inserted or updated on the server, there will be no user associated with the server, in that case, userId will return null.
Also you can check the source code yourself here. Check the CollectionHooks.getUserId method, it uses Meteor.userId() to get the userId.
CollectionHooks.getUserId = function getUserId() {
var userId;
if (Meteor.isClient) {
Tracker.nonreactive(function () {
userId = Meteor.userId && Meteor.userId(); // <------- It uses Meteor.userId() to get the current user's id
});
}
if (Meteor.isServer) {
try {
// Will throw an error unless within method call.
// Attempt to recover gracefully by catching:
userId = Meteor.userId && Meteor.userId(); // <------- It uses Meteor.userId() to get the current user's id
} catch (e) {}
if (!userId) {
// Get the userId if we are in a publish function.
userId = publishUserId.get();
}
}
return userId;
};
I have a helper where I want to acces the properties of a different collection.
Template.notification.helpers({
username: function () {
game = Games.findOne({_id: this.gameId}, {fields: {players:1}});
console.log(game) // output is correct
}
})
If I log this, it wil produce the result I expected:
Object {players: Array[2], _id: "qF3skjX2755BYcr8p"}
However, if I in my helper function I try to use/reach this properties I get an undefined error.
Template.notification.helpers({
username: function () {
game = Games.findOne({_id: this.gameId}, {fields: {players:1}});
console.log(game._id) // error;
console.log(game.players) // error
}
})
Output:
Exception from Deps recompute function: TypeError: Cannot read property 'players' of undefined
Why is this happening?
This happens because when Meteor initiall loads on your web browser, all the html and js is ready, but the data is not yet ready.
If you tried to check console.log(game) it may be null. It does this only when the page has loaded. If you load the template after all the data has downloaded you wouldn't see this issue.
When the data arrives the username helper would re-run with the new data.
In the meanwhile you just need to take care of this exception:
var game = Games.findOne({_id: this.gameId}, {fields: {players:1}});
if(!game) return null;
I have no issues when using implicit updates (angelFire). However I need for some of my data use explicit updating. So I implemented angelFireCollection on the exact same ref I was using previously but despite the console.log explicitly saying that the read was granted and trying it with both with the onloadcallback and without, I don't get data directly into my assigned variable AND once the callback fires I get a strange looking object that DOES contain the data but not in the form I expect. My scope variable ends up with an empty collection. Never gets populated. Here is the code:
var streamController = function ($rootScope, $scope, $log, $location, angularFireCollection, profileService) {
//Wait for firebaseLogin...
$rootScope.$watch('firebaseAuth', init);
function init() {
if ($rootScope.firebaseAuth == false) {
return
};
var refUsers = new Firebase($rootScope.FBURL+'/users/'+$rootScope.uid);
$scope.profile = angularFireCollection(refUsers, function onload(snapshot) {
console.log(snapshot)
});
};
};
myApp.gwWebApp.controller('StreamController', ['$rootScope', '$scope', '$log', '$location', 'angularFireCollection', 'profileService',
streamController]);
}());
Here is what the console.log looks like ( ie; what snapshot looks like ):
>snapshot
T {z: R, bc: J, V: function, val: function, xd: function…}
Here is the earlier message before the snapshot was returned:
Firebase Login Succeeded! fbLoginController.js:16
FIREBASE: Attempt to read /users/529ccc5d1946a93656320b0a with auth={"username":"xxxxxxx#me.com","id":"529ccc5d1946a93656320b0a"} firebase.js:76
FIREBASE: /: "auth.username == 'admin'" firebase.js:76
FIREBASE: => false firebase.js:76
FIREBASE: /users firebase.js:76
FIREBASE: /users/529ccc5d1946a93656320b0a: "auth.id == $user" firebase.js:76
FIREBASE: => true firebase.js:76
FIREBASE:
FIREBASE: Read was allowed.
and finally the desired binding that ends up with an empty array: again from the console:
$scope.profile
[]
Anyone know what I could possibly be doing wrong?? This is like 5 lines of code. Frustrating.
I have put stops in angelFireCollection factory function and can see that the data is getting added to the collection in the callbacks inside that function but my binded variable never gets updated.
UPDATE
Ok experimenting with a plnkr. It seems that angularFireCollection EXPECTS your returning a LIST of items. The snapshot returns properly if you inspect snapshot.val() it will be whatever object structure was stored in firebase. IF you use angularFireCollection it does indeed bind to the variable HOWEVER it turns a non-list object into a garbled mess and you can not access the object user the normal dot operator. This is either a bug or it is a severe limitation of angularFireCollection which will cause me to revaluate how easily I can use firebase as the backend. I can't share my plnkr because it is accessing non-public data but tomorrow if i have time I will create a public firebase with an object store and demonstrate.
Ok. So it appears that indeed angularFireCollection is MEANT to be array based. Which is fine. It would be VERY helpful if the angularFire documentation was updated to make that clear. As such it is not an implicit vs explicit update technique.
For an explicit non-array based approach I have come up with the following code. Had I not been mislead by the documentation I would have gone down this path originally.
var MainCtrl = function($scope, angularFire) {
$scope.test = {};
var _url = 'https://golfwire.firebaseio.com/tmp';
var _ref = new Firebase(_url);
var promise = angularFire(_ref, $scope, 'implicit');
promise.then ( function(data){
$scope.explicit=angular.copy($scope.implicit );
});
}
You then work locally with the 'explicit' copy and when ready just update the 'implicit' by assigning: $scope.implicit = $scope.explicit.
Here is a plnkr: http://plnkr.co/edit/bLJrL1
According to the Meteor documentation....
collection.insert(doc, [callback])
callback Function
Optional. If present, called with an error object as the first argument and the _id as the second.
...then later down...
On the server, if you don't provide a callback, then insert blocks until the database acknowledges the write, or throws an exception if something went wrong. If you do provide a callback, insert returns immediately. Once the insert completes (or fails), the callback is called with error and result arguments, same as for methods.
Which is it, error and _id or error and result? I do have Meteor.methods that are firing their callbacks correctly with error, result available to the scope.
I just can't get the callback to work correctly on a collection.insert(doc, [callback])
Either way I can't get my callback to register anything?
function insertPost(args) {
this.unblock;
if(args) {
post_text = args.text.slice(0,140);
var ts = Date.now();
Posts.insert({
post: post_text,
created: ts
}, function(error, _id){
// or try function(error, result) and still get nothing
// console.log('result: ' + result);
console.log('error: ' + error);
console.log('_id: ' + _id); //this._id doesn't work either
});
}
return;
}
What am I doing wrong? I have been up since 2 am coding...6 pm my time zone...I am blurry, so I might (probably) be missing something quite obvious.
Cheers
Steeve
This was a bug, fixed in the next release. Now, if you provide a callback to insert, it will be called with error and result arguments, where result is the ID of the new document, or null if there's an error.
Since this is serverside code you can just do:
var id = Posts.insert({data}); // will block until insert is complete
and the id will be available.