To many Meteor.call


#1

Hi!
I tried to apply the DRY method, and I ended creating a lot of many little Meteor function. But I’m facing a problem I cannot solve.

I want to add a notification when a user join an event. So I Meteor.call the userRegistration method. Inside this method, I call the addNotification method. Inside this method, I add a notification in the DB and then call the getUsersToNotify method (that get the user that active the mail notification). But in the callback of this method, I can not access anymore to the notificationID to send… -_-

Do I have to make all the DB request in one meteor method to avoid calling Meteor.call and losing access to the object out of the callback function?

Another (better I think) solution but I cannot make it work, it’s running the notification part only in server side to be able to call all the meteor.call synchronously… But How can I do that?

Thanks!!


#2

We don’t see code, so its hard to see why you do all of that stuff (and what goes wrong aswell).
I don’t recall any situation when you cannot do things you do inside two methods(or calls) with one.

Clould you explain, what are you trying to achive? Why so many methods? What do they do?

I feel like you passed making “how it should work” plan.


#3

DRY does not mean that you should not make clear interfaces for your software components. In your example you have, as far as I can extract:

Event, called with: userRegistration
Notification, called with: addNotification
Email, called with: getUsersToNotify

Based on this:

Shared methods: client + server

events.userRegistration(eventId)
-> calls: notifications.add(eventId, Meteor.userId())

Server methods

notifications.add(eventId, userId)
-> calls: emails.sendNotification(this notification)
emails.sendNotification(notification)
-> Sends out e-mail

On the server side there is no real need to use Meteor methods. We use plain Javascript objects for that. Meteor methods don’t add value here since you don’t need something shared with the client.

What you for example could do in: events.userRegistration is something like:

class Notification {
  constructor(eventId, userId) {
    this.eventId = eventId;
    this.userId = userId;
  }
  sendEmail() {
    let email = new Email();
    email.send(to, body, etc)
  }
}

Meteor.methods({'userRegistration'(eventId){
  Events.update({_id: eventId}, { $push: { attendees: {userId: Meteor.userId() } } });
  let notification = new Notification(eventId, Meteor.userId());
  notification.sendEmail();
});

DRY in itself is interesting but you also need to take care of other principles of good software. One of them is: SRP, Single Responsibility Principle. Your method takes care of one thing, your Notification class handles the notification, Email handles the email etc. You will likely expand with for example: EmailTemplate class which handles formatting of the mail.

Otherwise you end up with a spaghetti of method calls which don’t seem related anymore.

This is just a basic example but hope it makes some of the concepts clear.


#4

Thanks for your answers!
I am completely agree with you @lucfranken ! I am more interested in the SRP (but I didn’t knew the name) than the DRY and it’s for that reason that I was multiplying the Meteor.methods.
How to make a method “server-side”? For the moment, I put all my “objects” under a directory imports/api and they have the same skeleton. For example the /imports/api/notifications/notifications.js

export const Notifications = new Mongo.Collection('notifications');

import { Sessions } from '../sessions/sessions.js';

Meteor.methods({
    'notification.add': function(doc){
      var currentUserID = Meteor.userId();
      if(currentUserID){

        check(doc, {
          sessionID: String,
          userID: Match.Maybe(String),
          message: String,
          type: String
        });

        doc.date = new Date();
        doc.read = false;

        doc._id = Notifications.insert(doc);

        //TODO send notifications

        return doc._id;
      }
    }
});

I think that notification.add needs to be accessible from client to add to the db for the optimistic UI.
So the “notification.add” could call a server reserved method : user.notify? For this method, that it will only called on server, what is the best way to make this only available for server? Make a user.js file into the server directory and create a class as you the notification one you made in your example @lucfranken?

I’m a little confused… I think it’s because I was always using the Meteor.method and for this part is not posible, so I have to learn :stuck_out_tongue:

Thanks for you advices!


#5

I think the whole Notifications part is only useful on the server (since the client cannot send anything to other clients) so the only api I would share is:

events.userRegistration(eventId)

The rest is behind the scenes stuff which can all be server only.

/imports/api/notifications/notifications.js

I would put in here only an object, like the Notification object in my example. No stuff with Meteor methods etc. just a plain object.


#6

Just maybe a very last clear question : What happens if I call a function defined only server side in a function defined both in client and server ?

Meteor.method("myFunction", function(doc){
   doc._id = Notifications.insert(doc);
   notifier.send(doc);
});

Where notifier is an object defined in a server only JS.

Thanks! :wink:


#7

OK thanks. We post at the same time. Thanks for this reply. I will modify my code with your recommandation! Thanks!


#8

That’s a nice case where you can use: Meteor.isServer. So for example:

Meteor.method("events.userRegistration", function(eventId){
  Events.update({_id: eventId}, { $push: { attendees: {userId: Meteor.userId() } } });
  if(Meteor.isServer) {
    let notification = new Notification(eventId, Meteor.userId());
    notification.sendEmail();
  }
});

#9

Hi @lucfranken again!
I cannot manage to access a JS class in another file.

I have my imports/api/events/events.js where I do:

Meteor.method("events.userRegistration", function(eventId){
  Events.update({_id: eventId}, { $push: { attendees: {userId: Meteor.userId() } } });
  if(Meteor.isServer) {
    let notification = new Notification(eventId, Meteor.userId());
    notification.sendEmail();
  }
});

And I have my Notification file in /imports/api/notifications/server/notifications.js

class Notification {

  constructor(userId) {
    this.sessionID = doc.sessionID;
    this.notification = doc.notification;
    this.message = doc.message;
    this.userId = userId;
  }

  notify(){
   // code
  }

};

this.Notification = Notification;

But I get the error that Notification is not defined. How can I import/export the Notification class to be able to use it in my others scripts?

I tried to export Notification as a const and import it back in my other script, with no luck… I don’t understand how to play with those class… :frowning:


#10

Mostly you need to change this:

class Notification {

into:

Notification = class Notification {

That will allow it to get out of the file.


#11

It’s not working :frowning: I’m frustrated to don’t understand why I cannot call this object… Maybe a problem in my app structure…

- imports/
  - api/
    - notification/
      - server/
        - notification.js /* the class */
    - event/
      - event.js /* the file containing the Meteor.method for adding a user to an event */
  - ui/
    - event/
      - event.html
      - event.js /* the file containing the event on the template that call the Meteor.method */
      - event.scss

I read about the export for an ES6 class object in Meteor. I tried the export constant and the import. I tried the global method (without the var or let to define the class, the method you just advise me).


#12

YES !
Sorry, I was trying to import the notifications.js in a script that was share between server and client! I put the file import in the main.js of the server directory and it’s working! Thanks for your patience :wink:
I’m learning the server/client separation :stuck_out_tongue:
Thanks!!!


#13

Great to hear that, good luck with your project!