How can I validate a document from a Meteor Collection?


#1

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);
    });
  }
});

#2

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 
    });
  }
})

#3

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


#4

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


#5

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?


#6

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)