Why are my values differing on client and server?


#1

I have a user collection where the json may look like so.

{
  _id: "someuserid",
  services: {},
  games: [{
    warcraft: {
      'days': [0,1,2,3,4,5,6],
      'times': [0,1,2]
    }
  }],
  roles: {
    'warcraft': ['pvp', 'raid']
  }

I had a little trickery because I had to create autoValues on my schema such that only a subset of days could be selected. That is:

"games.$.times": {
  type: [Number],
  autoform: {
    options: function () {
      // there are set raid times you can participate in
      return [
        { label: "Morning", value: '0'},
        { label: "Afternoon": value '1' },
        { label: "Evening", value: '2' },
        { label: "Morning and Afternoon", value: '0;1' },
        { label: "Morning, Afternoon, and Evening", value: '0;1;2' },
      ]
    }
  },
  autoValue: function () {
    if (_.isArray(this.value)) {
      return this.value.map(Number);
    } else if (_.isNumber(this.value)) {
      return [this.value];
    // if it's from the form, split it on the delimiter
    } else if (_.isString(this.value)) {
      return this.value.split(';').map(Number);
    }
  }
}

So now if I go to a user’s profile and log the user, I get the expected value in the javascript console.

> Meteor.users.findOne().games.map(function (game) {
  return game.times;
});

[0, 1, 2];

However, when I call a meteor method and pass the user, it mutates the value.

var user = Meteor.users.findOne();
Meteor.call('Player.invite', user);
// on server
'Player.invite': function (playerDoc) {
  console.log(playerDoc.games.map(function (game) { return game.times }))
}

this gives me [';', 0, 1, 2]

What’s going on? Why is the server having different values than the client?


#2

My guess is that in the collection there is the ";"
The server has access to everything in the collections. Your client probably not (only numbers?)
Look at you publish and subscribe functions maybe.


#3

First thing to do is to check what’s actually in the DB. meteor mongo and then find out!
Also… usually problems like this happen when we’re going down a path that’s more difficult/complex than necessary. You have the full power of arrays at hand and you’re choosing to implement your own array handling, basically?
This is just not the best data model/data format. Represent those times with a string identifier and just make an array (or a set, in Mongo terms) out of them and let the user pick any combination of them. That’ll make the ; stuff go away and your code will be better off for it. And if you really need to restrict selections to certain valid combinations, then make a UI element with custom logic & validation. But this '0;1;2' is just a hack that has no good justification for being there.
But that’s just my view on things, how I’d handle this! Good luck!


#4

It’s mostly a workaround because I there are certain combinations they should not be able to choose. Eg, you can’t select “Breakfast” and “Lunch” when the only options are “Breakfast”, “Breakfast and Dinner”, “Dinner and Lunch”, and “Dinner”

Would be open to suggestions if you have any, though


#5

Well the simplest option would be to not make it an array then, but instead just a single (string) value that says whatever that combination is, and you have a list of allowedValues or whatever it’s called in autoform: “breakfast”, “dinner”, “breakfast_dinner”, “dinner_lunch”, “dinner”. Then you can still go with autoform and don’t have any hacks in the code, plus it’s very clear if you see it, what it means.
The other way would be to still make it an array and create your own UI element for selection, manually allowing only specific options, i.e. using event handlers to enable/disable other options based on what the user has selected so far, plus handling form submission and “manually” inserting / updating.


#6

Even better: add msavin:mongol to make it easier to view :smiley:


#7

You were definitely right about it being able to be simplified, but instead, I just added a custom validator.

SimpleSchema.messages({
  "invalidTimeCombination": "Invalid combination of times".
});

// ...
times: {
  type: [Number],
  label: "Available times",

  // just add a select multiple for the times
  // then, they can combine them together
  autoform: {
    options: function () {
      return [
        { label: "Morning", value: 0 },
        { label: "Afternoon", value: 1 },
        { label: "Evening", value: 2 }
      ]
    }
  },

  // after the fact, validate their input.
  // if the combination is invalid, return a different message.
  custom: function () {
    var notAllowed = [ [0,2] ];
    if (_.contains(notAllowed, this.value)) {
      return "invalidTimeCombination";
    }
  }
}