A second method

We might as well look for other method candidates. Our team edit event code also looks like it should be on the server. I can spot two long term problems with this remaining on the client.

client/views/team.js

...
  "submit form.form-edit": function(e, tpl){
    e.preventDefault();

    var teamName = tpl.$("input[name='name']").val();
    var self = this;

    if(teamName.length){
      Teams.update(this._id, {$set: {name: teamName}}, function(error){

        if(!error){

          // Update games this team is a part of
          var games = Games.find({_id: {$in: self.gameIds}});
          if(games.count()){
            _(games.fetch()).each(function(game){
              var team = _(game.teams).findWhere({_id: self._id});
              if(team != null){
                team.name = teamName;
                Games.update({_id: game._id}, {$set: {teams: game.teams}})
              }
            });
          }
        }
      });

      Session.set('isEditingTeam', null);
    }
  },
...

Firstly, this code has to update all the games that the team resides in. What happens if we start paginating games on the client? If we only have 10 out of 100 teams published to the client, then Games.find() is not going to work as we expect - it will only find games in Minimongo, which contains 10 records, not all 100.

The second problem is that we allow users to perform Teams.update() without updating the associated games (if the user knows how to use the browser console that is). Updating a team's name without updating the games should be forbidden in our application (both in UI and the browser console) - and this is only possible if we disallow Teams.update() and create a method to do the job.

I suggest trying to migrate the code to a method yourself. Here's what I ended up with:

both/collections/teams.js

...
Meteor.methods({
  teamUpdate: function(teamId, newName){
    check(Meteor.userId(), String);
    check(teamId, String);
    check(newName, String);

    var team = Teams.findOne(teamId);
    if(team){
      Teams.update(teamId, {$set: {name: newName}}, function(error){
        if(!error){
          if(team.gameIds){
            var games = Games.find({_id: {$in: team.gameIds}});

            games.fetch().forEach(function(game){
              game.teams.map(function(team){
                if(team._id == teamId){
                  team.name = newName;
                }

                Games.update({_id: game._id}, {$set: {teams: game.teams}});
              })
            });
          }

          return teamId;
        }
      });
    } else {
      throw new Meteor.Error("team-does-not-exist", "This team doesn't exist in the database");
    };
  }
});

client/views/team.js

...
  "submit form.form-edit": function(e, tpl){
    e.preventDefault();

    var teamName = tpl.$("input[name=name]").val();
    var self = this;

    if(teamName.length){
      Meteor.call("teamUpdate", this._id, teamName, function(error){
        if(error){
          alert(error.reason);
          Session.set('editedTeamId', self._id);
          Tracker.afterFlush(function(){
            tpl.$("input[name=name]").val(teamName);
            tpl.$("input[name=name]").focus();
          });
        }
      });

      Session.set('isEditingTeam', null);
    }
  },
...

forEach

If you look closly you may have noticed I changed from using Underscore's each() function (_(games.fetch()).each() to using Javascript's forEach() function (games.fetch().forEach(). The reason for this is that forEach is supported in Node (where our server code runs), but does not have full support in browsers (where our client code runs). Underscore is well supported and hence is a good substitute for our client code.