A Look At Meteor Collection Hooks


#1

Our latest blog post is about the always useful collection-hooks package:

https://www.discovermeteor.com/blog/a-look-at-meteor-collection-hooks/


#2

Nice post!
Love the logos you have at the top of all your articles.

When I first played around with Meteor I was all for using Allow/Deny and collection hooks was a big part of that. However now I have moved away from that and am using Methods I don’t even install the package any more :frowning:. You did point this out in the article, that collection hooks are less useful when you use Methods, but I guess that there are probably some use cases where the hooks might be useful.

It would be interesting to hear from others who primarily use Methods but still use collection hooks.


#3

@cstrat I use meteor methods exclusively and i’ve been using collection hooks to keep my models clean and only doing one thing.

I tend to make one method "Post.update" and then ensure that it only mutates the post. I keep a comment in the model to make it easier to track as well.

If a resource has heavy denormalizing or a lot of method calls, I create a service like denormalizePosts and store it in a services folder.

The downside is that it creates a lot of files but it makes it easier to test and mock out. Using something like TernJS to view/jump to the method definition also makes life easier!


#4

Also worth mentioning, i’ve been experimenting putting the collection in the model file which kind of eliminates the file hopping when using collection hooks. In this case I always use a denormalizePosts service so it’s not in that file.

One perk to doing this in the hooks (and not in the method) is that running hooks won’t make the model insert/update fail if something throws. Also making it easier to test is a win.

Example file:

/*global db, Post:true, User */

db.posts = new Meteor.Collection('posts');

db.posts.after.insert(function (userId, userDoc) {
  denormalizeSomethingIntoAuthors(userDoc);
  denormalizeSomethingElse(userDoc);
});



// CRUD facade to call Meteor methods more elegantly
Post = {
  create: function(data, callback) {
    return Meteor.call('Post.create', data, callback);
  },

  update: function(docId, data, callback) {
    return Meteor.call('Post.update', docId, data, callback);
  },

  destroy: function(docId, callback) {
    return Meteor.call('Post.destroy', docId, callback);
  }
};



Meteor.methods({
  /**
   * Creates a Post document
   * @method
   * @param {object} data - data to insert
   * @returns {string} of document id
   */
  "Post.create": function(data) {
    var docId;
    if (User.loggedOut()) throw new Meteor.Error(401, "Login required");

    data.ownerId = User.id();
    data.createdAt = new Date();
    data.updatedAt = new Date();

    // TODO plug in your own schema
    check(data, {
      createdAt: Date,
      updatedAt: Date,
      ownerId: String,
      // XXX temp fields
      foo: String,
      bar: String
    });

    docId = db.posts.insert(data);

    console.log("[Post.create]", docId);
    return docId;
  },


  /**
   * Updates a Post document using $set
   * @method
   * @param {string} docId - The doc id to update
   * @param {object} data - data to update
   * @returns {number} of documents updated (0|1)
   */
  "Post.update": function(docId, data) {
    var optional = Match.Optional;
    var count, selector;

    check(docId, String);
    if (User.loggedOut()) throw new Meteor.Error(401, "Login required");
    data.updatedAt = new Date();

    // TODO plug in your own schema
    check(data, {
      createdAt: Date,
      updatedAt: Date,
      ownerId: String,
      // XXX temp fields
      foo: optional(String),
      bar: String
    });

    // if caller doesn't own doc, update will fail because fields won't match
    selector = {_id: docId, ownerId: User.id()};

    count = db.posts.update(selector, {$set: data});

    console.log("  [Post.update]", count, docId);
    return count;
  },


  /**
   * Destroys a Post
   * @method
   * @param {string} docId - The doc id to destroy
   * @returns {number} of documents destroyed (0|1)
   */
  "Post.destroy": function(docId) {
    var count;
    check(docId, String);

    if (User.loggedOut()) throw new Meteor.Error(401, "Login required");

    // if caller doesn't own doc, destroy will fail because fields won't match
    count = db.posts.remove({_id: docId, ownerId: User.id()});

    console.log("[Post.destroy]", count);
    return count;
  }
});