How can I validate a document from a Meteor Collection?

Hello,

I have defined a route with FlowRouter like this:

protectedRoutes.route('/game/:id', {
  name: 'game',
  action() {
    BlazeLayout.render('Layout_protected', { main: 'Page_game' });
  }
});

Where and How can I validate the game document that I have to fetch from the Game Collection by the id param from the route? There is a small time in which the game is undefined until it is fetched from the subscription, is that normal? How I deal with that on meteor because I am used to promises?

import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { ReactiveDict } from 'meteor/reactive-dict';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { TAPi18n } from 'meteor/tap:i18n';
import { Games } from '../../../api/games/games.js';

import './game.html';

Template.Page_game.onCreated(function() {
  this.errors = new ReactiveDict();
  this.state = new ReactiveDict();
  this.state.setDefault({
    currentQuestion: 0
  });

  this.getGameId = () => FlowRouter.getParam('id');

  this.autorun(() => {
    this.subscribe('games.getById', this.getGameId());
  });

  this.getCurrentQuestion = () => {
    let id = FlowRouter.getParam('id');
    let game = Games.findOne(id);
    let currentQuestion = this.state.get('currentQuestion');
    return game && game.questions[currentQuestion];
  };

  this.getGame = () => {
    let id = FlowRouter.getParam('id');
    return Games.findOne(id);
  };
});

Template.Page_game.onRendered(function() {
  let game = this.getGame();
  console.log('onRendered', game);
});

Template.Page_game.helpers({
  game() {
    let game = Template.instance().getGame();
    console.log('game', game);
    return game;
  },
  gameURL() {
    let game = Template.instance().getGame();
    if (game) {
      return FlowRouter.url('game', {id: game._id});
    }
  },
  isGameStatus(status) {
    let game = Template.instance().getGame();
    if (game) {
      return status == game.status;
    }
  },
  player1() {
    let game = Template.instance().getGame();
    if (game) {
      return Meteor.users.findOne(game.player1);
    }
  },
  currentQuestionClass(index) {
    let currentQuestion = Template.instance().state.get('currentQuestion');
    return index == currentQuestion && 'btn-primary text-white';
  },
  currentQuestion() {
    return Template.instance().getCurrentQuestion();
  }
});

Template.Page_game.events({
  'click .js-change-question'(event, instance) {
    let index = parseInt($(event.target).data('index'));
    instance.state.set('currentQuestion', index);
  },
  'click #js-solution-btn'(event, instance) {
    let question = instance.getCurrentQuestion();
    $('#js-answer-html input').each((i, input) => {
      $(input).val(question.answerParsed[question.answerHiddenWords[i]]).prop('readonly', true);
    });
  }
});

The short time when it’s undefined is normal. Using reactivity, you can handle the case when it changes

You can also show a loading screen in the meantime by using the built in helper Template.subscriptionsReady:

{{#if Template.subscriptionsReady }}
  // load everything normally
{{else}}
  <h2> Loading... </h2>
{{/if}}

Personally I would validate the document in a new autorun that fetches the document:

this.autorun(function() {
  const game = this.getGame();
  if (game) {
    check(game, { 
      //schema to check 
    });
  }
})
1 Like

Thanks for the reply,
where would you add the autorun? onCreated or onRendered?

in onCreated after this.getGame (because the function in an autorun runs immediately when created and it needs that function to exist)

1 Like

One more question please.

I should have 2 different autorun blocks at onCreated, right?
Because the subscription is based on a different param and I don’t want it to rerun unnecessary?

Yes, two separate ones. It’s not the most elegant, but it will ensure that it only re-runs when something relevant to the operation changes (route or document)

1 Like