Server side meteor collections aren't visible on client side - mongodb

I have collections that are published on the server side, and use iron router's waitOn to subscribe to these collections. However, on the client side I can never see any reference to the collections defined on the server side. The only way I can access them is by defining the collection on the client side (devices = new Meteor.Collection('devices')), but I don't see anyone doing this in their examples online. Here is the code:
Client Code:
Router.route('/devices', {
waitOn: function() {
return [
Meteor.subscribe('devices', Meteor.user()._id),
];
},
action: function() {
this.render();
}
});
Server Side:
Devices = new Mongo.Collection("devices");
Devices.allow({
'insert': function (userId, doc) {
if (userId === doc.accountId) {
return true;
}
return false;
},
'update': function (userId, doc) {
if (userId === doc.accountId) {
return true;
}
return false;
},
});
Meteor.publish('devices', function(id) {
return Devices.find({accountId: id});
});
I have removed autopublish, and from the examples online, I should just be able to reference Devices.find({}). Instead, I have to use devices = new Meteor.Collection('devices') which then causes issues since if I call this again, it will say I already have a collection called devices. Does anyone know why I can't reference Devices?

The reason you can't reference it on the client side is that you haven't made the collection available to the client.
Leave the .allow and .publish methods on the server side, but move your collection creation to the lib folder to make it available on both the client and server.
/lib/collections.js:
Devices = new Mongo.Collection("devices");

Related

MongoDB: can't return random document from collection

I'm working on a word game and am trying to return a random wordpair (my collection) on a page load. I'm using Express and have adapted my code from this tutorial if that's of any use.
A GET request renders my page just fine, and I'm trying to send a random WordPair object alongside the title:
router.get('/', function(req, res, next) {
res.render('play', { title: 'play', random_wordpair: wordpair_controller.wordpair_random});
});
The wordpair_random function is here inside a controller file I've made (which also successfully manages listing the wordpairs and creating new ones etc).
// Get random WordPair
exports.wordpair_random = function() {
WordPair.aggregate(
[{
$sample: {
size: 1
}
}]
)
.exec(function(err, random_wordpair) {
if (err) {
return next(err);
}
console.log(random_wordpair);
return random_wordpair;
});
};
Then inside a play.pug template, I'm simply trying to display this result:
h3 random wordpair selection is: #{random_wordpair}
But all I can see is the function rendered as HTML text. Can anyone tell me what I'm doing wrong?
I also understand looking at the documentation for MongoDB $sample aggregation that I need to be calling my function on the database object, but I've seen various examples and some don't do this. When I try calling db.wordpair.aggregate(...) (or WordPair or wordpairs as it appears in mLab) directly after initializing db in my app.js file, I get undefined errors. My db object doesn't seem to contain the correct data for this request.
Thanks!
I guess you're writing this in Node.JS. A core feature in Node.JS is non-blocking IO model. That means, the code won't wait for a database call to complete to move on.
Another concept you need to get it right is that Node.JS, being a variation of JavaScript, in nature is a functional programming. Assigning a function to a property of a JSON object like below won't cause the function to execute. It simply creates a pointer to the function body, that's why your application prints the function itself.
{ title: 'play', random_wordpair: wordpair_controller.wordpair_random}
To fix this, use a callback
exports.wordpair_random = function(callback) {
WordPair.aggregate([$sample: {size: 1}}]).exec(callback);
};
Then in you web function:
router.get('/', function(req, res, next) {
wordpair_controller.wordpair_random(function(err, result) {
//Handle errors if needed.
res.render('play', { title: 'play', random_wordpair:result });
})
});

Meteor fetch server-side job without collection

I want to submit a request from my Meteor client to the server, that has the server make an HTTP request to a website, and then return the response to the client.
On a REST web server, I would make an HTTP GET from the client to the server, which would then make its own request and respond to the client.
I haven't added a REST interface to my Meteor app and don't want to add this overhead for just this one need. However, using collections to get this done is unweildy and not the right tool.
Is there any way for the Meteor client to securely ask the server to do something and get a response without using collections? I'm messing with meteor Methods such as:
Meteor.methods({
'/http/get'(name, cbk) {
cbk = cbk || function() {};
HTTP.get('http://www.google.com', {}, (err, data) => {
cbk(err, data);
});
},
});
However this isn't seeming to work. The call is being made on the Client side.
This is exactly what Meteor methods are for.
Meteor methods docs
Server
First define your method on your server:
Meteor.methods({
// Namespace for clarity
'make.rest_call'(callback) {
HTTP.get('http://www.google.com', {}, (err, data) => {
callback(err, data);
});
}
});
OR
If you need the client to do something with the data then return a promise here (promise docs)
Meteor.methods({
// Namespace for clarity
'make.rest_call'(callback) {
return new Promise((resolve, reject) => {
HTTP.get('http://www.google.com', {}, (err, data) => {
if (err) { reject(err); }
resolve(data);
});
}
}
});
Client
Then call it from your client:
// Simple call (just makes the call, does nothing on the client)
Meteor.call('make.rest_call');
OR
// Promise based call
Meteor.call('make.rest_call', (error, result) => {
if (error) { /* do something with error */ }
// result contains your promise.
result.then((data) => {
// do something with returned data
});
});

Meteor Collections.count() too slow on UI

I have done a Simple Publish/subscribe in my meteor project and whenever User lands on home page I have to show count of total users. Users are around 15000. Now in template helper I have code written a code as,
CLIENT-SIDE
Template.voters.helpers({
voters : function() {
return voters.find({});
},
count : voterscount
});
then on SERVER-SIDE
voters = new Mongo.Collection("voters");
voterscount = function() {
return voters.find({}).count();
}
Meteor.publish('voters', function() {
return voters.find({});
});
Meteor.publish('voterscount', function() {
return voterscount;
});
The output that I receive is that the count starts from 0 to 15000 on UI Which is irritating.
I don't want rolling up of the digits on UI and should show the static count on UI as 15000 whenever page refreshed.
Why is this so slow it. In production i will have around 10 million documents in collection. This is big drawback. Any help, please?
This is a good use case for a universal publication, i.e. one that is automatically sent to all clients.
Server:
stats = new Mongo.collection('stats'); // define a new collection
function upsertVoterCount(){
stats.upsert('numberOfVoters',{ numberOfVoters: voters.find().count() });
}
upsertVoterCount();
Meteor.publish(null,function(){ // null name means send to all clients
return stats.find();
});
var voterCursor = voters.find();
voterCursor.observe({
added: upsertVoterCount,
removed: upsertVoterCount
});
Then on the client you can get the voter count anytime with:
var nVoters = stats.findOne('numberOfVoters').numberOfVoters;
SERVER-publish.js
function upsertVoterCount(){
voterscount.upsert('numberOfVoters',
{
numberOfVoters : voters.find().count()
});
}
// null name means send to all clients
Meteor.publish(null ,function() {
upsertVoterCount();
return voterscount.find();
});
var voterCursor = voters.find();
voterCursor.observe({
added: upsertVoterCount,
removed: upsertVoterCount
});
LIB-collection.js
// define a new collection
voterscount = new Mongo.Collection('voterscount');
CLIENT-home.js
Template.voter.helpers({
count : function() {
return voterscount.findOne().numberOfVoters;
}
});

Meteor onRendered function and access to Collections

When user refresh a certain page, I want to set some initial values from the mongoDB database.
I tried using the onRendered method, which in the documentation states will run when the template that it is run on is inserted into the DOM. However, the database is not available at that instance?
When I try to access the database from the function:
Template.scienceMC.onRendered(function() {
var currentRad = radiationCollection.find().fetch()[0].rad;
}
I get the following error messages:
Exception from Tracker afterFlush function:
TypeError: Cannot read property 'rad' of undefined
However, when I run the line radiationCollection.find().fetch()[0].rad; in the console I can access the value?
How can I make sure that the copy of the mongoDB is available?
The best way for me was to use the waitOn function in the router. Thanks to #David Weldon for the tip.
Router.route('/templateName', {
waitOn: function () {
return Meteor.subscribe('collectionName');
},
action: function () {
// render all templates and regions for this route
this.render();
}
});
You need to setup a proper publication (it seems you did) and subscribe in the route parameters. If you want to make sure that you effectively have your data in the onRendered function, you need to add an extra step.
Here is an example of how to make it in your route definition:
this.templateController = RouteController.extend({
template: "YourTemplate",
action: function() {
if(this.isReady()) { this.render(); } else { this.render("yourTemplate"); this.render("loading");}
/*ACTION_FUNCTION*/
},
isReady: function() {
var subs = [
Meteor.subscribe("yoursubscription1"),
Meteor.subscribe("yoursubscription2")
];
var ready = true;
_.each(subs, function(sub) {
if(!sub.ready())
ready = false;
});
return ready;
},
data: function() {
return {
params: this.params || {}, //if you have params
yourData: radiationCollection.find()
};
}
});
In this example you get,in the onRendered function, your data both using this.data.yourData or radiationCollection.find()
EDIT: as #David Weldon stated in comment, you could also use an easier alternative: waitOn
I can't see your collection, so I can't guarantee that rad is a key in your collection, that said I believe your problem is that you collection isn't available yet. As #David Weldon says, you need to guard or wait on your subscription to be available (remember it has to load).
What I do in ironrouter is this:
data:function(){
var currentRad = radiationCollection.find().fetch()[0].rad;
if (typeof currentRad != 'undefined') {
// if typeof currentRad is not undefined
return currentRad;
}
}

Meteor code must always run within a fiber when deploy in meteor server

I kept having this error when i deploy my app onto meteor cloud server.
Meteor code must always run within a Fiber
at _.extend.get (app/packages/meteor/dynamics_nodejs.js:14:13)
at _.extend.apply (app/packages/livedata/livedata_server.js:1268:57)
at _.extend.call (app/packages/livedata/livedata_server.js:1229:17)
at Meteor.startup.Meteor.methods.streamTwit (app/server/server.js:50:24)
however, I have already wrapped within Fibers
streamTwit: function (twit){
var userid = '1527228696';
twit.stream(
'statuses/filter',
{ follow: userid},
function(stream) {
stream.on('data', function(tweet) {
Fiber(function(){
if(tweet.user.id_str === userid)
{
Meteor.call('addQn', tweet);
}
}).run();
console.log(tweet);
console.log('---------------------------------------------------------');
console.log(tweet.user.screen_name);
console.log(tweet.user.name);
console.log(tweet.text);
});
}
);
}
I don't know what's the reason but someone suggested that i should wrap it with Meteor.bindEnvironment instead. Hence, I did this:
streamTwit: function (twit){
this.unblock(); // this doesn't seem to work
console.log('... ... trackTweets');
var _this = this;
var userid = '1527228696';
twit.stream(
'statuses/filter',
{ follow: userid},
function(stream) {
stream.on('data', function(tweet) {
Meteor.bindEnvironment(function () {
if(tweet.user.id_str === userid)
{
Meteor.call('addQn', tweet);
}
}, function(e) {
Meteor._debug("Exception from connection close callback:", e);
});
console.log(tweet);
console.log('---------------------------------------------------------');
console.log(tweet.user.screen_name);
console.log(tweet.user.name);
console.log(tweet.text);
});
}
);
}
//add question method
addQn:function(tweet){
questionDB.insert({'tweet': tweet, 'date': new Date()});
}
but now it doesn't even work. I realise that this only happened when I tried to insert some data into mongodb.
May I know what is the problem with my code? Thanks!
All these codes were written in app/server/server.js
You shouldn't need to use Meteor.call on the server side. That is for client-side code only. Just call addQn directly or better yet, inline it since it's just one line of code.