Update reactive var with server Method - javascript

I have a problem with updating some values in Meteor app on client-side. I'm trying to understand how ReactiveVar works.
When I use find() method on collection on client-side the site updates immediately each time I change something. I want to achieve the same effect using ReactiveVar and server-side Method. So the code below works correctly for me:
// Client
Template.body.onCreated(function appBodyOnCreated() {
this.subscribe('activities');
}
Template.body.helpers({
getCounter() {
return Activities.find({
editorId: Meteor.userId(),
'referredObject.type': 'LIST'
}).count();
}
});
But when I try to achieve the same effect with server-side Method it doesn't work correctly. Code below updates variable only once. If I want to get current value I need to refresh the page.
// Server
Meteor.methods({'activitiesCreateCount'(userId, objectType) {
check(userId, String);
check(objectType, String);
return Activities.find({
editorId: userId,
'referredObject.type': objectType
}).count();
}
});
// Client
Template.body.onCreated(function appBodyOnCreated() {
this.subscribe('activities');
this.activitiesAmount = new ReactiveVar(false);
}
Template.body.helpers({
getCounter() {
var tempInstance = Template.instance();
Meteor.call('activitiesCreateCount', Meteor.userId(), 'TODO', function(err, response) {
tempInstance.activitiesAmount.set(response);
});
return Template.instance().activitiesAmount.get();
}
});
How I can improve my code if I want always have a current value of the variable (like in the first client-side only example)?

Try to move Meteor.callto Template.body.onCreated
Something like this
// Server
Meteor.methods({'activitiesCreateCount'(userId, objectType) {
check(userId, String);
check(objectType, String);
return Activities.find({
editorId: userId,
'referredObject.type': objectType
}).count();
}
});
// Client
Template.body.onCreated(function appBodyOnCreated() {
self = this;
this.activitiesAmount = new ReactiveVar(false);
Meteor.call('activitiesCreateCount', Meteor.userId(), 'TODO', function(err, response) {
self.activitiesAmount.set(response);
});
}
Template.body.helpers({
getCounter() {
return Template.instance().activitiesAmount.get();
}
});

Related

Meteor/mongoDb find and update

I'm just learning Meteor and I made a little app but I have a problem with find() and update() collection on server side
For example:
if (Meteor.isServer) {
function getCollection1(){
return collection_1.find({}).fetch({});
}
....
Meteor.methods({
start: function(id) {
datas = getCollection1();
Collection2.update({_id:id}, {$set: {datas: datas}}); //sometimes it's ok(3/4)
}
...
}
Or when I await, I have an error
if (Meteor.isServer) {
async function getCollection1(){
return await collection_1.find({}).fetch({});
}
....
Meteor.methods({
start: function(id) {
getCollection1().then(function(datas){
Rooms.update({_id: id}, {$set: {datas: datas}},function(err){
console.log(err);//error: Meteor code must always run within a fiber
});
});
}
...
}
What did I do wrong?
EDIT
it seems to work well with Fiber()
if (Meteor.isServer) {
Fiber = Npm.require('fibers')
function getCollection1(){
return collection1.find({}).fetch({});
}
function setCollection2(id){
Fiber(function() {
datas = getCollection1();
collection2.update({_id: id}, {$set: {datas: datas}},function(err){
console.log(err);
});
}).run();
}
....
Meteor.methods({
start: function(id) {
setCollection2(id);
}
...
}
With the async/await version, you do not need to use Fiber. Instead you could this Meteor function: Meteor.bindEnvironment to make it work:
// ...
getCollection1().then(Meteor.bindEnvironment(function(datas){
Rooms.update(// ...);
}));
// ...
For a more understandable explanation, you could consult this video:
What is Meteor.bindEnvironment?
Based on the code you provided, my guess is that for the first example the "Collection2.update" is taking place before GetCollection1() is completed. If this is indeed the case, then on the client side when you subscribe to the collection be sure to have a way to "wait" for the subscription to complete.
For example something like this on the client side where "start()" is called...
const handle = Meteor.subscribe( 'collection_1' );
if ( handle.ready() ) {
Meteor.call('start');
} else {
const allusers = Meteor.users.find().fetch();
onData( null, {allusers} );
}
Again, this is my best guess as you did not post the error you received with your first code chunk and I can't speak for the second you in which you've attempted to implement this.

MeteorJS Infinite loop when using meteor call and meteor method

I have a sample code that goes like this:
Client Helper:
getUsername: function (userId) {
Meteor.call("getUsername", userId, function (err, result) {
if(!err) {
Session.set("setUsername", result);
else {
console.log(err);
}
});
return Session.get("setUsername");
}
Server
Meteor.methods({
"getUsername": function (userId) {
var x = Meteor.users.find({_id: userId}, {fields: {username:1}}).fetch()[0];
return x.username;
}
});
The result of this code is an infinite loop of username passing to the client. Is there a way to stop the loop and pass only the data that is needed on the client? I believe the reactivity is causing the data to loop infinitely and I am not sure how to stop it. I tried using "reactive":false on my query in the server but it does not work.
If you want to access username everywhere in client templates (so thats why you put it into session), I would not set it in template helper. I would set it on startup and get username from session in template helpers (without calling server method)
If you need username just in one template, so you want to return its value from your template helper, do not put it into session, just return it in your server method callback.
Based on your sample code, I assume, you have a set of posts and you are retrieving user name based on user id for each post. Then instead of doing it this way, you should use publish composite package to publish related users as well.
Meteor.publishComposite('getPosts', function (postIds) {
return [{
find: function() {
return Posts.find({ _id: { $in: postIds }});
// you can also do -> return Posts.find();
// or -> return Posts.find({ /* or what ever your selector is to get the posts you need*/ });
},
children: [{
find: function(post) {
return Meteor.users.find({
id: post.userId //or the correct field in your post document to get user id
}, {
fields: {
"profile": 1
}
});
}
}}
}]
});
This way your publication will take care of publishing related users along with posts. You don't need to use methods and call them each time.

Meteor user helper hangs off after update

I have this helper:
agreed: function (){
if (Meteor.users.findOne({_id: Meteor.userId(), profile: {agreedTermsOfUse: 'true'}}))
return true;
}
On the page where I check it I have this:
{{#unless agreed}}
agree form
{{else}}
Create item form.
{{list of item}}
{{/unless}}
So far, all goes well. The user signs up then he can create an item and it renders on the list of items..
Now, I've added another Meteor.call, which when getting the success call back on the client, for the creating item, it adds the item id to the users' profile.hasItems.
Then after getting succes for that method, "unless" returns false, and I have to submit the agree to form again.
What am I missing? Thanks.
"submit .create_restaurant": function (event) {
event.preventDefault();
var text = event.target.create_restaurant.value;
Meteor.call('CreateRest', Meteor.userId(), text, function(error, result){
if(error){
}else{
console.log(result, Meteor.userId());
Meteor.call('userRestaurants', result, Meteor.userId(), function (error, result) {
if (error) {
alert(123);
} else {
console.log(result);
}
})
}
}
);
event.target.create_restaurant.value = "";
}
methods:
'CreateRest': function(user_id, title) {
check(title, String);
check(user_id, String);
return callback = Restaurants.insert({
createdBy: user_id,
createdAt: new Date(),
title: title
});
},
'userRestaurants': function(rest_id, createdBy) {
var restId = checkHelper(rest_id, createdBy);
if(restId)
console.log(rest_id, createdBy);
{
var callback = Meteor.users.update(
createdBy,
{$addToSet: {'profile.hasRestaurants': restId}}
);
return callback;
}
}
I don't know why you're seeing the behaviour that you are, but I do know that you have other problems to sort out first :)
You have a huge security hole - you're passing the user id through to the method from the client. That means that anyone can simply open the browser console and create a restaurant with any user id they like as the owner. Instead, use this.userId in the method to get the id of the caller.
Why the round trip to the server? Just have the first method update the client.
So, something like this (untested, written by hand here):
"submit .create_restaurant": function (event) {
event.preventDefault();
var text = event.target.create_restaurant.value;
Meteor.call('CreateRest',text, function(error, result){
if(error){
alert(123);
}else{
console.log(result);
}
});
event.target.create_restaurant.value = "";
}
and:
'CreateRest': function(user_id, title) {
check(title, String);
check(this.userId, String);
userId = this.userId;
Restaurants.insert({
createdBy: userId,
createdAt: new Date(),
title: title
}, function(err, restId) {
if (err) throw new Meteor.Error(err);
Meteor.users.update(
userId,
{$addToSet: {'profile.hasRestaurants': restId}},
function (err, res) {
if (err) throw new Meteor.Error(err);
return restId;
}
);
});
Once that's implemented properly it might start working. If it doesn't then the issue isn't related to the code you're posting.
Finally note that from a schema perspective it's really odd that that you have profile.hasRestaurants. To find the restaurants that a user has you should just do a find on the Restaurants collection.

Angular-meteor user collection only returns 1 object

I am writing an app with angular as front end and meteor as back end and I want to retrieve every user in the collection "Users" (added module accounts-ui and accounts-password). But when I execute the following code, it only returns one object (the last added) while there are 3 users.
if(Meteor.isClient){
angular.module('timeAppApp')
.controller('SignInCtrl', function($scope, $meteor, $location) {
$scope.LogIn = function() {
console.log($meteor.collection(Meteor.users).subscribe('users_by_email', $scope.username));
console.log($meteor.collection(Meteor.users).subscribe('all_users'));
};
});
}
if (Meteor.isServer) {
// Returns all users find by email
Meteor.publish('users_by_email', function(emailE){
return Meteor.users.find({'emails.address': emailE});
});
Meteor.publish('all_users', function(){
return Meteor.users.find({}, {fields: {emails: 1}});
})
I'm new to meteor so I'm still experimenting but now I'm really stuck.
Try this,
On server side
Meteor.methods({
get_users_by_email: function (emailE) {
return Meteor.users.find({ emails: { $elemMatch: { address: emailE } } }).fetch();
}
});
On client side
$meteor.call('get_users_by_email', $scope.username).then(
function(data){
console.log('success get_users_by_email', data);
},
function(err){
console.log('failed', err);
}
);

Meteor - How to find out if Meteor.user() can be used without raising an error?

I'm looking for a way to determine if Meteor.user() is set in a function that can be called both from the server and client side, without raising an error when it is not.
In my specific case I use Meteor server's startup function to create some dummy data if none is set. Furthermore I use the Collection2-package's autoValue -functions to create some default attributes based on the currently logged in user's profile, if they are available.
So I have this in server-only code:
Meteor.startup(function() {
if (Tags.find().fetch().length === 0) {
Tags.insert({name: "Default tag"});
}
});
And in Tags-collection's schema:
creatorName: {
type: String,
optional: true,
autoValue: function() {
if (Meteor.user() && Meteor.user().profile.name)
return Meteor.user().profile.name;
return undefined;
}
}
Now when starting the server, if no tags exist, an error is thrown: Meteor.userId can only be invoked in method calls. Use this.userId in publish functions.
So in other words calling Meteor.user() on the server startup throws an error instead of returning undefined or null or something. Is there a way to determine whether it will do so prior to calling it?
I cannot solve this simply by wrapping the call with if (Meteor.isServer) within the autoValue function, as the autoValue functions are normally called from server side even when invoked by the user, and in these cases everything in my code works fine.
Note that this is related to How to get Meteor.user() to return on the server side?, but that does not address checking if Meteor.user() is available in cases where calling it might or might not result in an error.
On the server, Meteor.users can only be invoked within the context of a method. So it makes sense that it won't work in Meteor.startup. The warning message is, unfortunately, not very helpful. You have two options:
try/catch
You can modify your autoValue to catch the error if it's called from the wrong context:
autoValue: function() {
try {
var name = Meteor.user().profile.name;
return name;
} catch (_error) {
return undefined;
}
}
I suppose this makes sense if undefined is an acceptable name in your dummy data.
Skip generating automatic values
Because you know this autoValue will always fail (and even if it didn't, it won't add a useful value), you could skip generating automatic values for those inserts. If you need a real name for the creator, you could pick a random value from your existing database (assuming you had already populated some users).
Been stuck with this for two days, this is what finally got mine working:
Solution: Use a server-side session to get the userId to prevent
"Meteor.userId can only be invoked in method calls. Use this.userId in publish functions."
error since using this.userId returns null.
lib/schemas/schema_doc.js
//automatically appended to other schemas to prevent repetition
Schemas.Doc = new SimpleSchema({
createdBy: {
type: String,
autoValue: function () {
var userId = '';
try {
userId = Meteor.userId();
} catch (error) {
if (is.existy(ServerSession.get('documentOwner'))) {
userId = ServerSession.get('documentOwner');
} else {
userId = 'undefined';
}
}
if (this.isInsert) {
return userId;
} else if (this.isUpsert) {
return {$setOnInsert: userId};
} else {
this.unset();
}
},
denyUpdate: true
},
// Force value to be current date (on server) upon insert
// and prevent updates thereafter.
createdAt: {
type: Date,
autoValue: function () {
if (this.isInsert) {
return new Date;
} else if (this.isUpsert) {
return {$setOnInsert: new Date};
} else {
this.unset();
}
},
denyUpdate: true
},
//other fields here...
});
server/methods.js
Meteor.methods({
createPlant: function () {
ServerSession.set('documentOwner', documentOwner);
var insertFieldOptions = {
'name' : name,
'type' : type
};
Plants.insert(insertFieldOptions);
},
//other methods here...
});
Note that I'm using the ff:
https://github.com/matteodem/meteor-server-session/ (for
ServerSession)
http://arasatasaygin.github.io/is.js/ (for is.existy)

Categories