Skip to content

Commit 98c9747

Browse files
committed
work in progress with chris' hooks
1 parent 30a508a commit 98c9747

File tree

6 files changed

+348
-26
lines changed

6 files changed

+348
-26
lines changed

counter-cache.js

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ _.mixin({
1212
});
1313

1414
var resolveForeignKey = function(doc, foreignKey) {
15+
// console.log(doc, foreignKey);
1516
return _.isFunction(foreignKey) ? foreignKey(doc) : _.dottedProperty(doc, foreignKey);
1617
};
1718

@@ -22,7 +23,7 @@ var applyFilter = function(doc, filter) {
2223
return true;
2324
};
2425

25-
Meteor.Collection.prototype.maintainCountOf = function(collection, foreignKey, counterField, filter) {
26+
Mongo.Collection.prototype.maintainCountOf = function(collection, foreignKey, counterField, filter) {
2627
var self = this;
2728

2829
// what is Meteor.users an instanceof ?
@@ -54,15 +55,17 @@ Meteor.Collection.prototype.maintainCountOf = function(collection, foreignKey, c
5455
self.update(_id, modifier);
5556
};
5657

57-
collection.after.insert(function(userId, doc) {
58+
collection.afterInsert(function(doc) {
5859
var foreignKeyValue = resolveForeignKey(doc, foreignKey);
5960
if (foreignKeyValue && applyFilter(doc, filter))
6061
increment(foreignKeyValue);
6162
});
6263

63-
collection.after.update(function(userId, doc, fieldNames, modifier, options) {
64+
collection.afterUpdate({ needsPrevious: true }, function(_id, modifier) {
6465
var self = this;
65-
var oldDoc = self.previous;
66+
var oldDocs = this.previous;
67+
68+
// console.log(oldDoc.fetch()[0]);
6669

6770
// console.log(modifier);
6871
// console.log(fieldNames);
@@ -71,25 +74,32 @@ Meteor.Collection.prototype.maintainCountOf = function(collection, foreignKey, c
7174

7275
// LocalCollection._modify(doc, modifier);
7376

74-
var oldDocForeignKeyValue = resolveForeignKey(oldDoc, foreignKey);
75-
var newDocForeignKeyValue = resolveForeignKey(doc, foreignKey);
77+
_.each(oldDocs, function(oldDoc) {
78+
var doc = collection.findOne(_id);
79+
var oldDocForeignKeyValue = resolveForeignKey(oldDoc, foreignKey);
80+
var newDocForeignKeyValue = resolveForeignKey(doc, foreignKey);
7681

77-
var filterApplyOldValue = applyFilter(oldDoc, filter);
78-
var filterApplyNewValue = applyFilter(doc, filter);
82+
var filterApplyOldValue = applyFilter(oldDoc, filter);
83+
var filterApplyNewValue = applyFilter(doc, filter);
7984

80-
if (oldDocForeignKeyValue === newDocForeignKeyValue && filterApplyOldValue === filterApplyNewValue)
81-
return;
85+
if (oldDocForeignKeyValue === newDocForeignKeyValue && filterApplyOldValue === filterApplyNewValue)
86+
return;
8287

83-
if (oldDocForeignKeyValue && filterApplyOldValue)
84-
decrement(oldDocForeignKeyValue);
88+
if (oldDocForeignKeyValue && filterApplyOldValue)
89+
decrement(oldDocForeignKeyValue);
8590

86-
if (newDocForeignKeyValue && filterApplyNewValue)
87-
increment(newDocForeignKeyValue);
91+
if (newDocForeignKeyValue && filterApplyNewValue)
92+
increment(newDocForeignKeyValue);
93+
});
8894
});
8995

90-
collection.after.remove(function(userId, doc) {
91-
var foreignKeyValue = resolveForeignKey(doc, foreignKey);
92-
if (foreignKeyValue && applyFilter(doc, filter))
93-
decrement(foreignKeyValue);
96+
collection.afterRemove({ needsPrevious: true }, function(_id) {
97+
var docs = this.previous;
98+
99+
_.each(docs, function(doc) {
100+
var foreignKeyValue = resolveForeignKey(doc, foreignKey);
101+
if (foreignKeyValue && applyFilter(doc, filter))
102+
decrement(foreignKeyValue);
103+
});
94104
});
95105
};

counter-cache_tests.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Tinytest.add('Counter cache - foreignKey works', function(test) {
2-
Authors = new Meteor.Collection('authors' + test.id);
3-
Books = new Meteor.Collection('books' + test.id);
2+
Authors = new Mongo.Collection('authors' + test.id);
3+
Books = new Mongo.Collection('books' + test.id);
44

55
Authors.maintainCountOf(Books, 'authorId', 'booksCount');
66

hooks.js

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
var Collection = Mongo.Collection;
2+
3+
var capitalize = function (string) {
4+
  return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
5+
};
6+
7+
var needsPrevious = function(klass, collectionName, method) {
8+
// grab the hooks
9+
10+
// console.log(collectionName);
11+
var hooks = Meteor._ensure(klass, '_hooks', collectionName);
12+
// console.log(hooks);
13+
// console.log(method);
14+
if (method === 'update')
15+
hooks = hooks.afterUpdate;
16+
if (method === 'remove')
17+
hooks = hooks.afterRemove;
18+
// if (hooks) console.log(hooks);
19+
// if (method === 'remove') console.log(hooks);
20+
return _.any(hooks, function(hook) { return hook.needsPrevious; });
21+
};
22+
23+
var runHooks = function (klass, hook, collection, args) {
24+
  var self = this
25+
    , hooks
26+
    , callbacks;
27+
28+
  hooks = Meteor._ensure(klass, '_hooks', collection);
29+
  callbacks = hooks[hook];
30+
31+
// args.push()
32+
33+
  // first arg might be the collection name
34+
35+
//   if (typeof args[0] === 'string')
36+
//     args = args.slice(1);
37+
38+
  if (_.isArray(callbacks)) {
39+
    _.each(callbacks, function (hook) {
40+
      hook.callback.apply(self, args);
41+
    });
42+
  }
43+
};
44+
45+
var defineHookMethod = function (klass, name) {
46+
  Collection.prototype[name] = function (options, fn) {
47+
// if options is a function, fn = options;
48+
fn = fn || _.isFunction(options) && options;
49+
var hook = _.extend({callback: fn}, options);
50+
51+
    var collectionName = this._name
52+
      , hooks = Meteor._ensure(klass, '_hooks', collectionName)
53+
      , callbacks;
54+
55+
    callbacks = hooks[name] = hooks[name] || [];
56+
    callbacks.push(hook);
57+
  };
58+
};
59+
60+
var wrapMethod = function (klass, method) {
61+
  var wrapped = klass.prototype[method];
62+
  klass.prototype[method] = function (/* args */) {
63+
    var ret
64+
      , args = _.toArray(arguments)
65+
      , collectionName = this._name || args[0];
66+
67+
68+
// if (Meteor.isServer)
69+
// args.shift(); // drop the first argument
70+
71+
if (needsPrevious(klass, collectionName, method))
72+
this.previous = this.find(args[0]).fetch();
73+
74+
// console.log(args);
75+
    runHooks.call(this,
76+
      klass,
77+
      'before' + capitalize(method),
78+
      collectionName,
79+
      args
80+
    );
81+
82+
    ret = wrapped.apply(this, arguments);
83+
84+
    runHooks.call(this,
85+
      klass,
86+
      'after' + capitalize(method),
87+
      collectionName,
88+
      args
89+
    );
90+
91+
    return ret;
92+
  };
93+
};
94+
95+
var wrapMutatorMethods = function (klass) {
96+
  _.each(['insert', 'update', 'remove'], function (method) {
97+
    defineHookMethod(klass, 'before' + capitalize(method));
98+
    defineHookMethod(klass, 'after' + capitalize(method));
99+
    wrapMethod(klass, method);
100+
  });
101+
};
102+
103+
// if (Meteor.isServer) {
104+
//   wrapMutatorMethods(MongoInternals.Connection);
105+
// }
106+
107+
// if (Meteor.isClient) {
108+
wrapMutatorMethods(Collection);
109+
// }

hooks_tests.js

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
var capitalize = function (string) {
2+
  return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
3+
};
4+
5+
Tinytest.add('Collection Hooks', function (test) {
6+
7+
  //XXX on client
8+
  //  - insert/update/remove invokes callbacks
9+
  //  - callbacks not invoked if inserted directly into the collection
10+
  //
11+
  //XXX on server
12+
  //  - insert/update/remove invokes callbacks
13+
  //  - callbacks ALSO invoked if you insert directly into the collection
14+
15+
16+
  if (Meteor.isClient) {
17+
    ClientItems = new Mongo.Collection('client_items', {connection: null});
18+
19+
    var hookCalls = {
20+
      total: 0
21+
    };
22+
23+
    _.each(['insert', 'update', 'remove'], function (method) {
24+
      var beforeHook = 'before' + capitalize(method);
25+
      var afterHook = 'after' + capitalize(method);
26+
27+
      hookCalls[beforeHook] = 0;
28+
      hookCalls[afterHook] = 0;
29+
30+
      ClientItems[beforeHook](function () {
31+
        hookCalls[beforeHook]++;
32+
        hookCalls.total++;
33+
      });
34+
35+
      ClientItems[afterHook](function () {
36+
        hookCalls[afterHook]++;
37+
        hookCalls.total++;
38+
      });
39+
    });
40+
41+
    var id = ClientItems._collection.insert({title: '1'});
42+
    test.equal(hookCalls.total, 0, 'Hooks should not be called for LocalCollection.prototype.insert');
43+
44+
    ClientItems._collection.update({_id: id}, {$set: {title: 'updated'}});
45+
    test.equal(hookCalls.total, 0, 'Hooks should not be called for LocalCollection.prototype.update');
46+
47+
    ClientItems._collection.remove({_id: id});
48+
    test.equal(hookCalls.total, 0, 'Hooks should not be called for LocalCollection.prototype.remove');
49+
50+
    var id = ClientItems.insert({title: '2'});
51+
    test.equal(hookCalls.total, 2, 'Hooks should be called for Collection.prototype.insert');
52+
    test.equal(hookCalls.beforeInsert, 1);
53+
    test.equal(hookCalls.afterInsert, 1);
54+
55+
    ClientItems.update({_id: id}, {$set: {title: 'updated'}});
56+
    test.equal(hookCalls.total, 4, 'Hooks should be called for Collection.prototype.update');
57+
    test.equal(hookCalls.beforeUpdate, 1);
58+
    test.equal(hookCalls.afterUpdate, 1);
59+
60+
    ClientItems.remove({_id: id});
61+
    test.equal(hookCalls.total, 6, 'Hooks should be called for Collection.prototype.remove');
62+
    test.equal(hookCalls.beforeRemove, 1);
63+
    test.equal(hookCalls.afterRemove, 1);
64+
  }
65+
66+
  if (Meteor.isServer) {
67+
    ServerItems = new Mongo.Collection('server_items');
68+
69+
    ServerItems.allow({
70+
      insert: function () { return true; },
71+
      update: function () { return true; },
72+
      remove: function () { return true; }
73+
    });
74+
75+
    var hookCalls = {
76+
      total: 0
77+
    };
78+
79+
    _.each(['insert', 'update', 'remove'], function (method) {
80+
      var beforeHook = 'before' + capitalize(method);
81+
      var afterHook = 'after' + capitalize(method);
82+
83+
      hookCalls[beforeHook] = 0;
84+
      hookCalls[afterHook] = 0;
85+
86+
      ServerItems[beforeHook](function () {
87+
        hookCalls[beforeHook]++;
88+
        hookCalls.total++;
89+
      });
90+
91+
      ServerItems[afterHook](function () {
92+
        hookCalls[afterHook]++;
93+
        hookCalls.total++;
94+
      });
95+
    });
96+
97+
    var id = ServerItems._collection.insert({title: '1'});
98+
    test.equal(hookCalls.total, 2, 'Hooks should be called for Mongo insert');
99+
    test.equal(hookCalls.beforeInsert, 1);
100+
    test.equal(hookCalls.afterInsert, 1);
101+
102+
    ServerItems._collection.update({_id: id}, {$set: {title: 'updated'}});
103+
    test.equal(hookCalls.total, 4, 'Hooks should be called for Mongo update');
104+
    test.equal(hookCalls.beforeUpdate, 1);
105+
    test.equal(hookCalls.afterUpdate, 1);
106+
107+
    ServerItems._collection.remove({_id: id});
108+
    test.equal(hookCalls.total, 6, 'Hooks should be called for Mongo remove');
109+
    test.equal(hookCalls.beforeRemove, 1);
110+
    test.equal(hookCalls.afterRemove, 1);
111+
112+
  }
113+
});

package.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
Package.describe({
2-
summary: 'Cache the counts of an associated collection.'
2+
summary: "Cache the counts of an associated collection",
3+
version: "1.0.0",
4+
git: "https://github.com/percolatestudio/meteor-counter-cache.git"
35
});
46

5-
Package.on_use(function(api) {
6-
api.use(['collection-hooks', 'underscore']);
7-
api.add_files('counter-cache.js', ['client', 'server']);
7+
Package.onUse(function(api) {
8+
api.use(['underscore', 'mongo']);
9+
api.add_files('hooks.js');
10+
api.add_files('counter-cache.js');
811
});
912

10-
Package.on_test(function(api) {
13+
Package.onTest(function(api) {
1114
api.use(['tinytest', 'counter-cache']);
12-
api.add_files('counter-cache_tests.js', 'server');
15+
api.add_files('counter-cache_tests.js');
1316
});

0 commit comments

Comments
 (0)