Skip to content

rclai/meteor-collection-hooks

 
 

Repository files navigation

Meteor Collection Hooks Build Status

Extends Mongo.Collection with before/after hooks for insert, update, remove, find, and findOne.

Works across both client, server or a mix. Also works when a client initiates a collection method and the server runs the hook, all while respecting the collection validators (allow/deny).

Please refer to History.md for a summary of recent changes.

Getting Started

Installation:

meteor add matb33:collection-hooks

.before.insert(userId, doc)

Fired before the doc is inserted.

Gives you an opportunity to modify doc as needed, or run additional functionality

  • this.transform() obtains transformed version of document, if a transform was defined.
var test = new Mongo.Collection("test");

test.before.insert(function (userId, doc) {
  doc.createdAt = Date.now();
});

.before.update(userId, doc, fieldNames, modifier, options)

Fired before the doc is updated.

Gives you an opportunity to change the modifier as needed, or run additional functionality.

  • this.transform() obtains transformed version of document, if a transform was defined.
test.before.update(function (userId, doc, fieldNames, modifier, options) {
  modifier.$set = modifier.$set || {};
  modifier.$set.modifiedAt = Date.now();
});

Important: Note that we are changing modifier, and not doc. Changing doc won't have any effect as the document is a copy and is not what ultimately gets sent down to the underlying update method.


.before.remove(userId, doc)

Fired just before the doc is removed.

Gives you an opportunity to affect your system while the document is still in existence -- useful for maintaining system integrity, such as cascading deletes.

  • this.transform() obtains transformed version of document, if a transform was defined.
test.before.remove(function (userId, doc) {
  // ...
});

.after.insert(userId, doc)

Fired after the doc was inserted.

Gives you an opportunity to run post-insert tasks, such as sending notifications of new document insertions.

  • this.transform() obtains transformed version of document, if a transform was defined;
  • this._id holds the newly inserted _id if available.
test.after.insert(function (userId, doc) {
  // ...
});

.after.update(userId, doc, fieldNames, modifier, options)

Fired after the doc was updated.

Gives you an opportunity to run post-update tasks, potentially comparing the previous and new documents to take further action.

  • this.previous contains the document before it was updated.
    • The optional fetchPrevious option, when set to false, will not fetch documents before running the hooks. this.previous will then not be available. The default behavior is to fetch the documents.
  • this.transform() obtains transformed version of document, if a transform was defined. Note that this function accepts an optional parameter to specify the document to transform — useful to transform previous: this.transform(this.previous).
test.after.update(function (userId, doc, fieldNames, modifier, options) {
  // ...
}, {fetchPrevious: true/false});

Important: If you have multiple hooks defined, and at least one of them does not specify fetchPrevious: false, then the documents will be fetched and provided as this.previous to all hook callbacks. All after-update hooks for the same collection must have fetchPrevious: false set in order to effectively disable the pre-fetching of documents.

It is instead recommended to use the collection-wide options (e.g. MyCollection.hookOptions.after.update = {fetchPrevious: false};).


.after.remove(userId, doc)

Fired after the doc was removed.

doc contains a copy of the document before it was removed.

Gives you an opportunity to run post-removal tasks that don't necessarily depend on the document being found in the database (external service clean-up for instance).

  • this.transform() obtains transformed version of document, if a transform was defined.
test.after.remove: function (userId, doc) {
  // ...
});

.before.find(userId, selector, options)

Fired before a find query.

Gives you an opportunity to adjust selector/options on-the-fly.

test.before.find: function (userId, selector, options) {
  // ...
});

.after.find(userId, selector, options, cursor)

Fired after a find query.

Gives you an opportunity to act on a given find query. The cursor resulting from the query is provided as the last argument for convenience.

test.after.find: function (userId, selector, options, cursor) {
  // ...
});

.before.findOne(userId, selector, options)

Fired before a findOne query.

Gives you an opportunity to adjust selector/options on-the-fly.

test.before.findOne: function (userId, selector, options) {
  // ...
});

.after.findOne(userId, selector, options, doc)

Fired after a findOne query.

Gives you an opportunity to act on a given findOne query. The document resulting from the query is provided as the last argument for convenience.

test.after.findOne: function (userId, selector, options, doc) {
  // ...
});

Direct access (circumventing hooks)

All compatible methods have a direct version that circumvent any defined hooks. For example:

collection.direct.insert({_id: "test", test: 1});
collection.direct.update({_id: "test"}, {$set: {test: 1}});
collection.direct.find({test: 1});
collection.direct.findOne({test: 1});
collection.direct.remove({_id: "test"});

Default options

As of version 0.7.0, options can be passed to hook definitions. Default options can be specified globally and on a per-collection basis for all or some hooks, with more specific ones having higher specificity.

Examples (in order of least specific to most specific):

CollectionHooks.defaults.all.all = {exampleOption: 1};

CollectionHooks.defaults.before.all = {exampleOption: 2};
CollectionHooks.defaults.after.all = {exampleOption: 3};

CollectionHooks.defaults.all.update = {exampleOption: 4};
CollectionHooks.defaults.all.remove = {exampleOption: 5};

CollectionHooks.defaults.before.insert = {exampleOption: 6};
CollectionHooks.defaults.after.remove = {exampleOption: 7};

Similarly, collection-wide options can be defined (these have a higher specificity than the global defaults from above):

var testCollection = new Mongo.Collection("test");

testCollection.hookOptions.all.all = {exampleOption: 1};

testCollection.hookOptions.before.all = {exampleOption: 2};
testCollection.hookOptions.after.all = {exampleOption: 3};

testCollection.hookOptions.all.update = {exampleOption: 4};
testCollection.hookOptions.all.remove = {exampleOption: 5};

testCollection.hookOptions.before.insert = {exampleOption: 6};
testCollection.hookOptions.after.remove = {exampleOption: 7};

Currently (as of 0.7.0), only fetchPrevious is implemented as an option, and is only relevant to after-update hooks.


Additional notes

  • Returning false in any before hook will prevent the underlying method (and subsequent after hooks) from executing. Note that all before hooks will still continue to run even if the first hook returns false.

  • If you wish to make userId available to a find query in a publish function, try the technique detailed in this comment userId is available to find and findOne queries that were invoked within a publish function.

  • All hook callbacks have this._super available to them (the underlying method) as well as this.context, the equivalent of this to the underlying method. Additionally, this.args contain the original arguments passed to the method and can be modified by reference (for example, modifying a selector in a before hook so that the underlying method uses this new selector).

  • It is quite normal for userId to sometimes be unavailable to hook callbacks in some circumstances. For example, if an update is fired from the server with no user context, the server certainly won't be able to provide any particular userId.

  • If, like me, you transform Meteor.users through a round-about way involving find and findOne, then you won't be able to use this.transform(). Instead, grab the transformed user with findOne.


Maintainers

Contributors

About

Meteor Collection Hooks

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages

  • JavaScript 100.0%