How do I properly filter data with meteor publish/subscribe so I don't retrieve more data than the client wants to see? - mongodb

I have a meteor app where I want to let the user click buttons to switch between 2 different filtered views of the data, say "chart" records with a status of 10 or 11. Some users of the app might never want to see status 10, while others might never want to see status 11.
I'm trying to find a way through publish/ subscribe to work this out most efficiently... not pulling up records someone doesn't want to see, while also reducing network traffic.
First idea was the following... a publish on the server with a parameter of the status:
Meteor.publish("charts1", function (status) {
console.log('someone subscribed to my publish of charts.. returning all charts now of status ' + status + " max 500 though");
return Chart.find({"chartStatus": status}, {limit: 500, sort: {"demographics.lastName": 1}});
});
then, on client, I have a template helper:
chartsOnClient: function () {
return Chart.find({"chartStatus":Session.get('currentStatusFilter')}, {sort: {"demographics.lastName": 1}});
}
and 2 buttons which, when clicked will set the session filter and subscribe at the same time:
Template.addChartForm.events = {
'click #mybutton10': function () {
console.log("subscribing to to status 10...");
Session.set('currentStatusFilter', 10);
Meteor.subscribe('charts1', 10);
},
'click #mybutton11': function () {
console.log("subscribing to status 11...");
Session.set('currentStatusFilter', 11);
Meteor.subscribe('charts1', 11);
},
}
and, of course, a template that iterated over "chartsOnClient" to display the records.
This worked... when I clicked button10, I got status 10 in my template, and clicking button11 gave me status 11 in my template...And, the added benefit was that if I never clicked button11, my local client only held the records of status 10, and vice versa, and clicking both buttons would fill up my local collection with the union of both sets.
The only issue I see with this method is that every click of the buttons results in the server message "someone subscribed to my publish of charts..."... indicating that the client is talking to the server and running the find method.
I suspect this is not ideal... for, if I wanted, I could move the 2 "subscribe" calls outside of the click events, like this:
Meteor.subscribe('charts1', 10);
Meteor.subscribe('charts1', 11);
Template.addChartForm.events = {
'click #mybutton10': function () {
console.log("subscribing to to status 10...");
Session.set('currentStatusFilter', 10);
},
'click #mybutton11': function () {
console.log("subscribing to status 11...");
Session.set('currentStatusFilter', 11);
},
}
and when I do this, I get the same end user experience, however, the server console only shows the "someone subscribed to my publish"... message once upon startup, instead of every time the buttons are clicked.
The downside is that both sets of records are pulled up to the client for each user, even users that might never click on both buttons. But the upside is that the subscribe method is not called each time they click to switch between views of the data...
Is there something I'm not understanding here? Which method is best? is there a better way to do this altogether? I'm new to meteor and mongo.
Thank you.
Edit, based on #mattk , I am going to do the subscribes in the button clicks, but use an array in a session variable to prevent a second subscription if I've already subscribed with that particular filter:
'click #mybutton10': function () {
Session.set('currentStatusFilter', 10);
var filtersAppliedSoFar = Session.get('filtersAppliedSoFar');
if (filtersAppliedSoFar.indexOf(10) == -1) {
console.log("subscribing to to status 10...");
filtersAppliedSoFar.push(10);
Session.set('filtersAppliedSoFar', filtersAppliedSoFar);
Meteor.subscribe('charts1', 10);
}
},
'click #mybutton11': function () {
Session.set('currentStatusFilter', 11);
var filtersAppliedSoFar = Session.get('filtersAppliedSoFar');
if (filtersAppliedSoFar.indexOf(11) == -1) {
console.log("subscribing to status 11...");
filtersAppliedSoFar.push(11);
Session.set('filtersAppliedSoFar', filtersAppliedSoFar);
Meteor.subscribe('charts1', 11);
}
},
This way, I don't pull up the data until the user clicks on the particular filter, but I also don't resubscribe if they click back and forth between the 2 filters, which is expected.
EDIT AGAIN: after asking this question How do you securely log out and clear all subscriptions? and getting directed toward Subscription Manager https://github.com/meteorhacks/subs-manager I have discovered that Subscription manager achieves what I was looking for here: I didn't want it to hit the server a second time if my client called .subscribe again. Instead of using a session variable (filtersAppliedSoFar) to know if the client has already subscribed, the subs manager keeps track of this automatically...I just call .subscribe on the subscription manager object and it won't hit the server the second time. .. then the added benefit is I can call .clear() when logging out and all subscriptions are stopped.

There's nothing wrong with your first pattern. Every time you talk to the server, you want to ask it or tell it something new, and that's exactly what you're doing: you're asking it for ONLY the data you need. If you want to reduce bandwidth, limit the fields returned; chances are you aren't going to need every field that is stored in the doc.
I follow something like this:
//Client code (event click)
Meteor.subscribe('patients',
{"chartStatus":Session.get('currentStatusFilter')},
{
fields: {'demographics': 1, 'chartStatus': 1},
sort: {"demographics.lastName": 1}
});
//Server code
Meteor.publish('patients', function(query, options) {
if (options) {
check(options, {
sort: Match.Optional(Object),
limit: Match.Optional(Number),
fields: Match.Optional(Object)
});
}
return Patients.find(query,options);
});
Note that the client can now ask for whatever fields she wants. If in the future there are certain fields in the doc that shouldn't be sent over the wire, you need to do a permission check, too. Right now, this doesn't seem like a problem for you since you've been sending over the entire doc.

Related

How to stop the user from entering the duplicate record on default save

I have a custom module where there is an email field. Now i want to stop the user if the email is already in the database.
I want to stop the user on save button and show the error. Like when a required field goes empty.
I tried to get some help but was not able to understand it.
Note: I realized after posting this that you are using suitecrm which this answer will not be applicable toward but I will leave it in case anyone using Sugar has this question.
There are a couple of ways to accomplish this so I'll do my best to walk through them in the order I would recommend. This would apply if you are using a version of Sugar post 7.0.0.
1) The first route is to manually create an email address relationship. This approach would use the out of box features which will ensure your system only keeps track of a single email address. If that would work for your needs, you can review this cookbook article and let me know if you have any questions:
https://support.sugarcrm.com/Documentation/Sugar_Developer/Sugar_Developer_Guide_9.2/Cookbook/Adding_the_Email_Field_to_a_Bean/
2) The second approach, where you are using a custom field, is to use field validation. Documentation on field validation can be found here:
https://support.sugarcrm.com/Documentation/Sugar_Developer/Sugar_Developer_Guide_9.2/Cookbook/Adding_Field_Validation_to_the_Record_View/index.html
The code example I would focus on is:
https://support.sugarcrm.com/Documentation/Sugar_Developer/Sugar_Developer_Guide_9.2/Cookbook/Adding_Field_Validation_to_the_Record_View/#Method_1_Extending_the_RecordView_and_CreateView_Controllers
For your example, I would imagine you would do something like this:
Create a language key for your error message:
./custom/Extension/application/Ext/Language/en_us.error_email_exists_message.php
<?php
$app_strings['ERROR_EMAIL_EXISTS_MESSAGE'] = 'This email already exists.';
Create a custom controller for the record creation (you may also want to do this in your record.js):
./custom/modules//clients/base/views/create/create.js
({
extendsFrom: 'RecordView',
initialize: function (options) {
this._super('initialize', [options]);
//reference your language key here
app.error.errorName2Keys['email_exists'] = 'ERROR_EMAIL_EXISTS_MESSAGE';
//add validation tasks
this.model.addValidationTask('check_email', _.bind(this._doValidateEmail, this));
},
_doValidateEmail: function(fields, errors, callback) {
var emailAddress = this.model.get('your_email_field');
//this may take some time so lets give the user an alert message
app.alert.show('email-check', {
level: 'process',
title: 'Checking for existing email address...'
});
//make an api call to a custom (or stock) endpoint of your choosing to see if the email exists
app.api.call('read', app.api.buildURL("your_custom_endpoint/"+emailAddress), {}, {
success: _.bind(function (response) {
//dismiss the alert
app.alert.dismiss('email-check');
//analyze your response here
if (response == '<email exists>') {
errors['your_email_field'] = errors['your_email_field'] || {};
errors['your_email_field'].email_exists = true;
}
callback(null, fields, errors);
}, this),
error: _.bind(function (response) {
//dismiss the alert
app.alert.dismiss('email-check');
//throw an error alert
app.alert.show('email-check-error', {
level: 'error',
messages: "There was an error!",
autoClose: false
});
callback(null, fields, errors);
})
});
},
})
Obviously, this isn't a fully working example but it should get you most of the way there. Hope this helps!

Azure Mobile Services Node.js update column field count during read query

I would like to update a column in a specific row in Azure Mobile Services using server side code (node.js).
The idea is that the column A (that stores a number) will increase its count by 1 (i++) everytime a user runs a read query from my mobile apps.
Please, how can I accomplish that from the read script in Azure Mobile Services.
Thanks in advance,
Check out the examples in the online reference. In the table Read script for the table you're tracking you will need to do something like this. It's not clear whether you're tracking in the same table the user is reading, or in a separate counts table, but the flow is the same.
Note that if you really want to track this you should log read requests to another table and tally them after the fact, or use an external analytics system (Google Analytics, Flurry, MixPanel, Azure Mobile Engagement, etc.). This way of updating a single count field in a record will not be accurate if multiple phones read from the table at the same time -- they will both read the same value x from the tracking table, increment it, and update the record with the same value x+1.
function read(query, user, request) {
var myTable = tables.getTable('counting');
myTable.where({
tableName: 'verses'
}).read({
success: updateCount
});
function updateCount(results) {
if (results.length > 0) {
// tracking record was found. update and continue normal execution.
var trackingRecord = results[0];
trackingRecord.count = trackingRecord.count + 1;
myTable.update(trackingRecord, { success: function () {
request.execute();
});
} else {
console.log('error updating count');
request.respond(500, 'unable to update read count');
}
}
};
Hope this helps.
Edit: fixed function signature and table names above, adding another example below
If you want to track which verses were read (if your app can request one at a time) you need to do the "counting" request and update after the "verses" request, because the script doesn't tell you up front which verse records the user requested.
function read(query, user, request) {
request.execute( { success: function(verseResults) {
request.respond();
if (verseResults.length === 1) {
var countTable = tables.getTable('counting');
countTable.where({
verseId: verseResults[0].id
}).read({
success: updateCount
});
function updateCount(results) {
if (results.length > 0) {
// tracking record was found. update and continue normal execution.
var trackingRecord = results[0];
trackingRecord.count = trackingRecord.count + 1;
countTable.update(trackingRecord);
} else {
console.log('error updating count');
}
}
}
});
};
Another note: make sure your counting table has an index on the column you're selecting by (tableName in the first example, verseId in the second).

Autopublish removed but why can I still retrieve data from db?

I have a simple Meteor/MongoDB project using the 'roles' package where I optain data from the db to the client. The roles package seems to work fine and the browser shows the right data depending on who is logged in, just like it should do. Then when running 'meteor remove autopublish' in the terminal inside my applications directory I get 'autopublish removed' just like it should. Still I can retrieve data from the server just as before(!?)
I have all of my db calls from the client/client.js.
The server/server.js does nothing (I do have publish/subscribe code but uncomment for now) and same goes for the common js file in main directory.
How can this be? Am I perhaps retrieving data from minimongo somehow? I have also removed insecure even if I don't think that matters in this case(?) Thanks in advance.
EDIT: Here's the code:
client.js:
//when uncomment the subscribe's you should not get access to the server/db, but 'data' that holds all the inlogg info still shows. The 'movies' on the other hand doesn't, just like it shouldn't.
//Meteor.subscribe('data');
//Meteor.subscribe('movies');
/*############# Get User Data ###############*/
Template.userLoggedIn.id = function () {
return Meteor.userId();
};
Template.userLoggedIn.email = function () {
var email = Meteor.users.findOne({_id: Meteor.userId()});
return email.emails[0].address;
};
Template.userLoggedIn.profile = function () {
var profile = Meteor.users.findOne({_id: Meteor.userId()});
return profile.profile.name;
};
Template.userLoggedIn.role = function () {
var role = Meteor.users.findOne({_id: Meteor.userId()});
return role.roles[0];
};
/*############# ###############*/
Template.movies.movies = function() {
var movies = Movies.find().fetch();
return movies;
}
server.js:
Meteor.publish('data', function () {
return Meteor.users.find();
});
Meteor.publish('movies', function() {
return Movies.find();
});
Thanks for providing the code - I see how this could be confusing. The users section of the docs should be written to explicitly say this, but what's happening is the current user is always published. So even if you don't write a publish function for users (or your have your subscribe commented out), you should expect to see the current user on the client. Because your template code only looks for Meteor.userId(), I would expect it to still work.
Assuming you have other users in the database, you can quickly check that they are not being published by running: Meteor.users.find().count() in your browser console. If it returns 1 then you are only publishing the current user (or 0 if you are logged out).

Updating MongoDB in Meteor Router Filter Methods

I am currently trying to log user page views in meteor app by storing the userId, Meteor.Router.page() and timestamp when a user clicks on other pages.
//userlog.js
Meteor.methods({
createLog: function(page){
var timeStamp = Meteor.user().lastActionTimestamp;
//Set variable to store validation if user is logging in
var hasLoggedIn = false;
//Checks if lastActionTimestamp of user is more than an hour ago
if(moment(new Date().getTime()).diff(moment(timeStamp), 'hours') >= 1){
hasLoggedIn = true;
}
console.log("this ran");
var log = {
submitted: new Date().getTime(),
userId: Meteor.userId(),
page: page,
login: hasLoggedIn
}
var logId = Userlogs.insert(log);
Meteor.users.update(Meteor.userId(), {$set: {lastActionTimestamp: log.submitted}});
return logId;
}
});
//router.js This method runs on a filter on every page
'checkLoginStatus': function(page) {
if(Meteor.userId()){
//Logs the page that the user has switched to
Meteor.call('createLog', page);
return page;
}else if(Meteor.loggingIn()) {
return 'loading';
}else {
return 'loginPage';
}
}
However this does not work and it ends up with a recursive creation of userlogs. I believe that this is due to the fact that i did a Collection.find in a router filter method. Does anyone have a work around for this issue?
When you're updating Meteor.users and setting lastActionTimestamp, Meteor.user will be updated and send the invalidation signal to all reactive contexts which depend on it. If Meteor.user is used in a filter, then that filter and all consecutive ones, including checkLoginStatus will rerun, causing a loop.
Best practices that I've found:
Avoid using reactive data sources as much as possible within filters.
Use Meteor.userId() where possible instead of Meteor.user()._id because the former will not trigger an invalidation when an attribute of the user object changes.
Order your filters so that they run with the most frequently updated reactive data source first. For example, if you have a trackPage filter that requires a user, let it run after another filter called requireUser so that you are certain you have a user before you track. Otherwise if you'd track first, check user second then when Meteor.logginIn changes from false to true, you'd track the page again.
This is the main reason we switched to meteor-mini-pages instead of Meteor-Router because it handles reactive data sources much easier. A filter can redirect, and it can stop() the router from running, etc.
Lastly, cmather and others are working on a new router which is a merger of mini-pages and Meteor.Router. It will be called Iron Router and I recommend using it once it's out!

Incrementally update Kendo UI autocomplete

I have a Kendo UI autocomplete bound to a remote transport that I need to tweak how it works and am coming up blank.
Currently, I perform a bunch of searches on the server and integrate the results into a JSON response and then return this to the datasource for the autocomplete. The problem is that this can take a long time and our application is time sensitive.
We have identified which searches are most important and found that 1 search accounts for 95% of the chosen results. However, I still need to provide the data from the other searches. I was thinking of kicking off separate requests for data on the server and adding them the autocomplete as they return. Our main search returns extremely fast and would be the first items added to the list. Then as the other searches return, I would like them to add dynamically to the list.
Our application uses knockout.js and I thought about making the datasource part of our view model, but from looking around, Kendo doesn't update based on changes to your observables.
I am currently stumped and any advice would be welcomed.
Edit:
I have been experimenting and have had some success simulating what I want with the following datasource:
var dataSource = new kendo.data.DataSource({
transport: {
read: {
url: window.performLookupUrl,
data: function () {
return {
param1: $("#Input").val()
};
}
},
parameterMap: function (options) {
return {
param1: options.param1
};
}
},
serverFiltering: true,
serverPaging: true,
requestEnd: function (e) {
if (e.type == "read") {
window.setTimeout(function() {
dataSource.add({ Name: "testin1234", Id: "X1234" })
}, 2000);
}
}
});
If the first search returns results, then after 2 seconds, a new item pops into the list. However, if the first search fails, then nothing happens. Is it proper to use (abuse??) the requestEnd like this? My eventual goal is to kick off the rest of the searches from this function.
I contacted Telerik and they gave me the following jsbin that I was able to modify to suit my needs.
http://jsbin.com/ezucuk/5/edit