Interacting with non-published fields in a collection's helper method

o/

I’m in the thick of refactoring an app to reflect the structure of the new todos example, and I ran into a small point of confusion with the file structure. I have a new collection which copies the format of the Lists and Todos collections, called UserPosts. In the lists.js file, there is a helper function for editing a list of todos defined thus:

Lists.helpers({
    editableBy(userId) {
        if (!this.userId) {
            return true;
        }
        return this.userId === userId;
    },
    ...
});

My UserPosts’ function is nearly the same, except that a user must be signed in to edit a post:

editableBy(userId) {
    if (!this.userId) {
        return false;
    }
    return this.userId === userId;
},

This seems straightforward, except for the fact that this.userId always returns undefined because I removed userId from the publicFields object. Adding it back into the object solves my immediate issue, so my question is a more general one: what’s the point of limiting the “public fields” of a collection to those that you want the client to see, if you can’t also use them in a server-side method. Or am I mistaken in thinking that Lists.helpers or UserPosts.helpers are server-side methods at all?

Template Helpers are client side. You’d have to interact with a Meteor.Method to access the DB via the server.

Thanks for the reply, @Stan. My confusion is coming from the fact that it’s a collection helper, not a template helper, though (i.e. Lists and UserPosts are both Mongo collections, and the helpers are defined in the same file that the collections are defined). If an editableBy method’s purpose is to invalidate a fraudulent user before updating a document, then I don’t see why that method would be available for the client, as a different this.userId could be spoofed.

Ah okay. Missed the Collection helpers bit.

I might be completely wrong but are you running the code above on the client? It doesn’t matter where the code is defined, if its executed on the client it runs on the client, which won’t have access to userId in your case.

Where are you calling Lists.editableBy()?

I’m calling editableBy from inside a ValidatedMethod, prior to updating the document. From what I understand, Meteor first runs Method simulations on the client and then, assuming no errors, it runs the Method on the server. If editableBy returns false then I throw an error, so I assume what’s happening is the helper returns false on the client simulation and Meteor is stopping the execution, even though it ought to return true when the server runs it.

If this is all the case (meaning if my understanding of how Meteor is working is correct) then my issue makes sense. But in terms of how the Todos app was designed, I don’t see the point of calling the editableBy helper for this very reason. I just tried a dumb-looking hack which accomplishes what I want, except that it won’t throw a client-side error to a fraudulent user (not the end of the world since my UI won’t allow for that anyway, but it’d be nice):

if (Meteor.isClient) {
    UserPosts.helpers({
        editableBy(userId) {
            if (Meteor.user()) {
                return true;
            }
        },
    });
}
if (Meteor.isServer) {
    UserPosts.helpers({
        editableBy(userId) {
            if (!this.userId) {
                return false;
            }
            return this.userId === userId;
        },
    });
}

This works, but surely there is a more elegant way to do it?

I’m not familiar with how collection helpers work as I’ve not used that package but I know that methods are actually ran in parallel on the client and the server, not one after the other. The client version/stub will run faster and once results are received from the server’s method call that will overwrite the clients version.

http://docs.meteor.com/#/full/meteor_methods

You can refactor it a bit more by moving the logic into one version of the method that deals with both.

UserPosts.helpers({
    editableBy(userId) {
        if (Meteor.isClient && Meteor.user()){
            return true
        } else if (Meteor.isServer && this.userId) {
            return this.userId === userId;
        } else {
            return false
        }
    }
})
1 Like