Pages

Tuesday, July 5, 2016

Learning Meteor.js [Part 33]: Use mixins with ValidatedMethods to reuse logic between methods

There is a lot of code duplication within the server methods to validate the parameters passed in. For example, all methods need to verify that user is authenticated and it has permissions to modify the requested resource before calling the database. The method below demonstrates a common implementation:
export const renameMeal = new ValidatedMethod({
   name: 'meal.rename',
   validate: new SimpleSchema({
       mealId: { type: String },
       name: { type: String }
   }).validator(),
   run({ mealId, name }) {
     if (!this.userId) {
        throw new Meteor.Error('unauthorized', 'You must be logged in to remove a meal.');
     } else if (!Meals.findOne({ _id: mealId, userId: this.userId})) {
         throw new Meteor.Error('notfound', 'The meal specified was not found for this user.');
     }
 
     Meals.update(mealId, { $set: { name: name }});
   }
});

Other frameworks handle this kind of thing by providing a way to execute logic across multiple server actions (for example, ActionFilters in ASP.NET MVC). Meteor has something similar as part of its ValidatedMethod package, they are called 'mixins'. They provide a way for users to chain the run() function of the method. For example:
export const authenticated = createMixin(function() {
    if (!this.userId) {
        throw new Meteor.Error('unauthorized', 'You must be logged in to access this method.');
    };
});

The 'createMixin' is a helper method that adds a new function into the chain of run() functions. In my opinion the platform should have provided something like this out of the box, here is my implementation:
function createMixin(callback) {
    var myMixin = function(methodOptions) {
        const runFunc = methodOptions.run;
        methodOptions.run = function() {
            callback.call(this, ...arguments);
            runFunc.call(this, ...arguments);
        }

        return methodOptions;
    }

    return myMixin;
}

With this in place, validated methods can be re-written to use the mixins:
export const renameMeal = new ValidatedMethod({
    name: 'meal.rename',
    validate: new SimpleSchema({
        mealId: { type: String },
        name: { type: String }
    }).validator(),
    mixins: [Mixins.authenticated, Mixins.mealOwner],
    run({ mealId, name }) {
        Meals.update(mealId, { $set: { name: name }});
    }
});

You can check the code up to this point by using this commit or visit the live demo.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.