1

I'm looking for a way to determine if Meteor.user() is set in a function that can be called both from the server and client side, without raising an error when it is not.

In my specific case I use Meteor server's startup function to create some dummy data if none is set. Furthermore I use the Collection2-package's autoValue -functions to create some default attributes based on the currently logged in user's profile, if they are available.

So I have this in server-only code:

Meteor.startup(function() {
    if (Tags.find().fetch().length === 0) {
        Tags.insert({name: "Default tag"});
    }
});

And in Tags-collection's schema:

creatorName: {
    type: String,
    optional: true,
    autoValue: function() {
        if (Meteor.user() && Meteor.user().profile.name)
            return Meteor.user().profile.name;
        return undefined;
    }
}

Now when starting the server, if no tags exist, an error is thrown: Meteor.userId can only be invoked in method calls. Use this.userId in publish functions.

So in other words calling Meteor.user() on the server startup throws an error instead of returning undefined or null or something. Is there a way to determine whether it will do so prior to calling it?

I cannot solve this simply by wrapping the call with if (Meteor.isServer) within the autoValue function, as the autoValue functions are normally called from server side even when invoked by the user, and in these cases everything in my code works fine.

Note that this is related to How to get Meteor.user() to return on the server side?, but that does not address checking if Meteor.user() is available in cases where calling it might or might not result in an error.

Community
  • 1
  • 1
Waiski
  • 9,214
  • 3
  • 21
  • 30

2 Answers2

2

On the server, Meteor.users can only be invoked within the context of a method. So it makes sense that it won't work in Meteor.startup. The warning message is, unfortunately, not very helpful. You have two options:

try/catch

You can modify your autoValue to catch the error if it's called from the wrong context:

autoValue: function() {
  try {
    var name = Meteor.user().profile.name;
    return name;
  } catch (_error) {
    return undefined;
  }
}

I suppose this makes sense if undefined is an acceptable name in your dummy data.

Skip generating automatic values

Because you know this autoValue will always fail (and even if it didn't, it won't add a useful value), you could skip generating automatic values for those inserts. If you need a real name for the creator, you could pick a random value from your existing database (assuming you had already populated some users).

David Weldon
  • 63,632
  • 11
  • 148
  • 146
  • Thank you. These are both viable options in my use case. I'm not sure though if this is the best possible behavior from Meteor. I can imagine there are other cases where code that can be invoked by both the server and client would want to test if Meteor.user() is available, and doing a try - catch block each time does not seem ideal. Returning undefined from Meteor.user() when in server code outside methods and publish functions would seem reasonable to me. Perhaps I should submit an issue to Meteor about this. – Waiski Nov 19 '14 at 14:13
  • Yeah I'm not a fan of try/catch either. In practice I suspect you will only hit this issue when generating your initial data. I also agree that throwing an error seems like a harsh punishment - I suspect they did it so something would be printed to the console. It's **very** common for users to invoke `Meteor.user` in a publish function and I suspect MDG's intention was to hit the user over the head and say "Don't do that!". :) – David Weldon Nov 19 '14 at 14:20
2

Been stuck with this for two days, this is what finally got mine working:

Solution: Use a server-side session to get the userId to prevent

"Meteor.userId can only be invoked in method calls. Use this.userId in publish functions."

error since using this.userId returns null.

lib/schemas/schema_doc.js

//automatically appended to other schemas to prevent repetition
Schemas.Doc = new SimpleSchema({
createdBy: {
    type: String,
    autoValue: function () {
        var userId = '';

        try {
            userId = Meteor.userId();
        } catch (error) {
            if (is.existy(ServerSession.get('documentOwner'))) {
                userId = ServerSession.get('documentOwner');
            } else {
                userId = 'undefined';
            }
        }

        if (this.isInsert) {
            return userId;
        } else if (this.isUpsert) {
            return {$setOnInsert: userId};
        } else {
            this.unset();
        }
    },
    denyUpdate: true
},
// Force value to be current date (on server) upon insert
// and prevent updates thereafter.
createdAt: {
    type: Date,
    autoValue: function () {
        if (this.isInsert) {
            return new Date;
        } else if (this.isUpsert) {
            return {$setOnInsert: new Date};
        } else {
            this.unset();
        }
    },
    denyUpdate: true
},
//other fields here...
});

server/methods.js

Meteor.methods({
    createPlant: function () {
        ServerSession.set('documentOwner', documentOwner);

        var insertFieldOptions = {
            'name' : name,
            'type' : type
        };

        Plants.insert(insertFieldOptions);
    },
    //other methods here...
});

Note that I'm using the ff:

Woppi
  • 5,303
  • 11
  • 57
  • 81