Meteor.methods vs Allow & Deny

Those are plain old server methods called from the client. You don’t get latency compensation there. Your call still has to go to the server and nothing happens on the client until the server responds.

I am not sure that this is correct. From the docs:
“Stubs are run for their side-effects: they are intended to simulate the result of what the server’s method will do, but without waiting for the round trip delay. … the database mutators (insert, update, remove) are implemented as methods. When you call any of these functions on the client, you’re invoking their stub version that update the local cache, and sending the same write request to the server. When the server responds, the client updates the local cache with the writes that actually occurred on the server.”

It seems to me that with stubs you do get latency compensation.

4 Likes

My bad, didn’t notice in the docs the part about methods in the client results being ignored but having side effects. I’ve always used them exclusively on the server.

@sacha Not quite. I already use a similar system with my own Obj.fireCallbacks('<event>', data). It helps maintain separation between packages, and helps making denormalization more manageable.

What I’m talking about, is layering my API to have “trusted” and “untrusted” entry points. Also, having multiple entrypoints to call the same logic. I often have methods calling other methods, and needing to re-apply validation, or re-query data.

MyApi.doSomething = {
  internal: function(a){
     // do something directly with A
  },
  entryPoints: {
     'fromId': {
        validators: ['validUser'],
        trusted: function(aId){ // callers should know that user owns A
          // returned array is used as arguments for `internal`
          return [Stuff.A.find({_id: aId, ownerId: this.userId})];
        },
        untrusted: function(aId){ // callers don't need to check
         check(aId, String);
         var a = Stuff.A.find({_id: aId, ownerId: this.userId});
         if (a == null) throw new Meteor.Error(404);
         return [a];
        }
     },
     fromObj: {
        // callers should have an `a` that the users owns.
        trusted: function(a){ return [a]; }
     }
  }
}

With some sort of structure like this, you could:

  1. Calls from the client execute MyApi.doSomething.fromId run the untrusted version as a simulation, while calling the trusted version on the server.
  2. Calls from the server, can decide whether to enter at either trusted/ untrusted entry points for any function.
  3. Custom validators can be used to avoid repeated code.

eg. If I now wanted to implement an doSomethingForUser method, which operates on every object owned by a particular user-

MyApi.doSomethingForUser = {
   'internal': function(userId){
        Stuff.A.find({ownerId: userId}).forEach(function(a){
          MyApi.doSomething.fromObj(a);
        });
  },
  entryPoints: {
     'currentUser': {
        untrusted: function(){
           return this.userId;
        }
     },
     'anotherUser':{
        validators: ['isSuperUser'],
        untrusted: function(userId){
            return [userId];
        }
     }
  }
}

Anything you do on a client with a collection is implemented as a method (insert, update, remove).

I don’t understand the artificial distinction between using allow/deny on the server (which is a way to control what the native meteor method can do) and the desire to reinvent the wheel by writing your own methods.

You use methods all the time, because the database mutators (insert, update, remove) are implemented as methods. When you call any of these functions on the client, you’re invoking their stub version that update the local cache, and sending the same write request to the server. When the server responds, the client updates the local cache with the writes that actually occurred on the server.

1 Like

There are a few reasons, but the most important has to do with updates. insert and remove can be straightforward in many cases, but the mechanics of allow/deny simply don’t tell you what is being updated in any practical way. In order to have any hope of keeping a proper schema, you then need to use a community package like collection2… which is fine as long as you don’t have complex business rules around who can update a particular field and when. See the link in @Peppe_LG’s comment above.

1 Like