[SOLVED] I cannot encapsulate fileReadSync in a Meteor synchronous method call


#1

I’m using fileReadSync to read a file that includes a list of domains and populate an array. Then I want to use that array as a dictionary to check whether a the domain of my user email in that list or not.

I created a server side class like this:

ProvidersDictionary = function() {
  var start = new Date().getTime();
  this.providers = fs.readFileSync(process.env.PWD + '/server/dictionaries/list.txt').toString().split('\n');
  this.lookUp = function(domain) {
    return (this.providers.indexOf(domain) !== -1)? true : false;
  }
}

and then a Meteor method like this:

Meteor.methods({'isAFreeProviderEmailDomain' : function(domain) {
    var freeProviders = new ProvidersDictionary();
    var res = freeProviders.lookUp(domain);
    return res;
  }
});

I want to call the Meteor method from a template helper in this way:

var evalDomain = function() {
var userDomain = getUserDomain(Meteor.userId());
var res Meteor.call(‘isAFreeProviderEmailDomain’, userDomain);
return res;
}

Template.userRegistrationForm.helpers({
  'isAFreeEmailProvider' : function() {
    var res = evalDomain();
    return res;
  }
});

The Meteor method returns undefined, even if debugging on the server I found out that it should return the right value.

If I use an async method call it works but the server method completes its task after the helper returns the (wrong) value to the screen.


#2

Hi, http://docs.meteor.com/#/full/meteor_call

On the client, if you do not pass a callback and you are not inside a stub, call will return undefined, and you will have no way to get the return value of the method. That is because the client doesn’t have fibers, so there is not actually any way it can block on the remote execution of a method.


#3

Hi @nxcong and thanks for your reply. You are absolutely right.
I solved my problem putting everything inside a Meteor method and calling it from my template helper, and it worked.

The only issue still opened is the filename parameter I pass to:

fs.readFileSync(process.env.PWD + '/server/dictionaries/list.txt').toString().split('\n');

Once deployed to *.meteor.com the parameter process.env.PWD + '/server/dictionaries/list.txt' seems to be incorrect and I got an error on the server - it couldn’t find the file where it is supposed to be. Pretty weird, actually.
I couldn’t find anyone on the Internet who solved this problem. Can that be a Meteor bug?
I also tried to move the .txt file under the public folder, but I got the same problem. When deployed, the app can’t find it – process.en.PWD is valued as ‘/’.
Any hints?


#4

Hi, my English is very bad…hope you can understand.
Why do not you try this way :

/project-folder/private/dictionaries/list.txt

Your server side class like this :

ProvidersDictionary = function() {
    var start = new Date().getTime();
    this.providers = Assets.getText('dictionaries/list.txt');
    this.lookUp = function(domain) {
        return (this.providers.indexOf(domain) !== -1)? true : false;
    }
}

#5

Hi,
Many thanks @nxcong . It worked perfectly. It’s incredible, I read Meteor documentation tens of times, but I didn’t remember the asset management part.

For completeness, my method reworked to avoid the call outside a stub, is now like this:

Meteor.methods({
  'isItACompanyDomain' : function() {
    var userEmail = Meteor.user().emails[0].address;
    var atIndex =userEmail.indexOf('@') + 1;
    var userDomain = userEmail.substring(atIndex).toLowerCase();
    var providersArray = Assets.getText('free-email-providers.txt');
    var providerFound = (providersArray.indexOf(userDomain ) !== -1)? true : false;

    return providerFound === true ? false : true;
  }
});

I call this – asynchronously – in the template onCreated and I save it in a session variable:

Template.userRegistrationForm.onCreated(function(){
  this.subscribe('companies');
  this.subscribe('channels');
  Meteor.call('isUserFromACompanyDomain', function(err, res) {
    if (! err) {
      Session.set('is-it-from-a-company', res);
    }
  });
});

Then, I retrieve this session variable in the helper where I need it.
Nice.
Max


#6

Maybe this is not answering the question so much, but why not create a socket on the file system that listens for changes to that file and then updates your mongo database manually?

That way, you can synchronously just execute database operations without reading the entire file every time.

You could probably even put that in a fairly simple package.


#7

I don’t really grasp the socket thing, but in my case the list of domains that I have on the file takes a few milliseconds to load (3,400 records). I could try to optimize putting the loading phase in a meteor startup stub.

A second way could be isolating this small component in an external service. It seems like that there is anything out there to easily verify if given an email address it comes from a company or from a personal account on a free email provider.

The package thing is interesting though. I’ll think about it – never created a package before.


#8

Hi @massimosgrelli,

As you hinted at yourself, I would recommend you go back to your previous implementation re: reading the file to some degree. As it stands you are reading the free-email-providers.txt on the server every single time a client calls that method. This will likely cause you problems if you get more than a few users on your site simultaneously. Your previous implementation read the file just once when initialising ProvidersDictionary, which is a lot more efficient.

But as @corvid suggests, an even better solution longer-term would be to read the file once when starting your server, and to save each provider into a mongo collection that you can later search. This would also save your server from blocking every time you do an .indexOf of the large string of providers.

Packages are easy to get a grasp of and are quite useful to make your code more maintainable (with the added benefit of versioning, and having control over the load-order of your files).

Wish you the best of luck,
Geordie


#9

You are right guys, I know. I’m going to fix this as soon as I get a stable MVP :smile:
I suppose in meteor I can create server side collections that I won’t publish to the client. In my case I don’t want to move this list items back and forth.

Bookmarked and added to my todo list.
Thanks