Skip to content

Commit

Permalink
Merge pull request #248 from sebakerckhof/modernize
Browse files Browse the repository at this point in the history
Remove underscore + modernize code base
  • Loading branch information
StorytellerCZ committed Oct 21, 2019
2 parents c2001e7 + a931457 commit 69a95ab
Show file tree
Hide file tree
Showing 50 changed files with 849 additions and 942 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ node_modules
npm-debug.log
*.sublime-workspace
.vscode

.idea/
.idea
.npm
4 changes: 4 additions & 0 deletions History.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## v1.0.0
* Modernization of the package
* BREAKING CHANGE: Minimum required Meteor version is now 1.7.0.5

## v0.9.1
* Fixed selector not being able to be modified when String or MongoID is used
* Add `npm prune --production` to publication script to prevent addition of dev only packages to the bundle. Fixes issue [#246](https://github.com/Meteor-Community-Packages/meteor-collection-hooks/issues/246)
Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Meteor Collection Hooks [![Build Status](https://travis-ci.org/matb33/meteor-collection-hooks.png?branch=master)](https://travis-ci.org/matb33/meteor-collection-hooks)
# Meteor Collection Hooks [![Build Status](https://travis-ci.org/Meteor-Community-Packages/meteor-collection-hooks.png?branch=master)](https://travis-ci.org/matb33/meteor-collection-hooks)

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

Expand Down Expand Up @@ -27,7 +27,8 @@ functionality
defined.

```javascript
var test = new Mongo.Collection("test");
import { Mongo } from 'meteor/mongo';
const test = new Mongo.Collection("test");

test.before.insert(function (userId, doc) {
doc.createdAt = Date.now();
Expand Down Expand Up @@ -252,6 +253,8 @@ with more specific ones having higher specificity.
Examples (in order of least specific to most specific):

```javascript
import { CollectionHooks } from 'meteor/matb33:collection-hooks';

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

CollectionHooks.defaults.before.all = {exampleOption: 2};
Expand All @@ -268,7 +271,8 @@ Similarly, collection-wide options can be defined (these have a higher
specificity than the global defaults from above):

```javascript
var testCollection = new Mongo.Collection("test");
import { Mongo } from 'meteor/mongo';
const testCollection = new Mongo.Collection("test");

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

Expand Down
9 changes: 9 additions & 0 deletions advices.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import './insert.js'
import './update.js'
import './remove.js'
import './upsert.js'
import './find.js'
import './findone.js'

// Load after all advices have been defined
import './users-compat.js'
23 changes: 23 additions & 0 deletions client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Meteor } from 'meteor/meteor'
import { Tracker } from 'meteor/tracker'
import { CollectionHooks } from './collection-hooks.js'

import './advices'

CollectionHooks.getUserId = function getUserId () {
let userId

Tracker.nonreactive(() => {
userId = Meteor.userId && Meteor.userId()
})

if (userId == null) {
userId = CollectionHooks.defaultUserId
}

return userId
}

export {
CollectionHooks
}
140 changes: 58 additions & 82 deletions collection-hooks.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { EJSON } from 'meteor/ejson';
import { LocalCollection } from 'meteor/minimongo';

/* global Package Meteor Mongo LocalCollection CollectionHooks _ EJSON */
/* eslint-disable no-proto, no-native-reassign, no-global-assign */

// Relevant AOP terminology:
// Aspect: User code that runs before/after (hook)
// Advice: Wrapper code that knows when to call user code (aspects)
// Pointcut: before/after
const advices = {}

var advices = {}
var Tracker = (Package.tracker && Package.tracker.Tracker) || Package.deps.Deps
var publishUserId = Meteor.isServer && new Meteor.EnvironmentVariable()

CollectionHooks = {
export const CollectionHooks = {
defaults: {
before: { insert: {}, update: {}, remove: {}, upsert: {}, find: {}, findOne: {}, all: {} },
after: { insert: {}, update: {}, remove: {}, find: {}, findOne: {}, all: {} },
all: { insert: {}, update: {}, remove: {}, find: {}, findOne: {}, all: {} }
},
directEnv: new Meteor.EnvironmentVariable(),
directOp: function directOp(func) {
directOp(func) {
return this.directEnv.withValue(true, func)
},
hookedOp: function hookedOp(func) {
hookedOp(func) {
return this.directEnv.withValue(false, func)
}
}

CollectionHooks.getUserId = function getUserId() {
var userId
let userId

if (Meteor.isClient) {
Tracker.nonreactive(function () {
Expand Down Expand Up @@ -57,28 +59,28 @@ CollectionHooks.getUserId = function getUserId() {
CollectionHooks.extendCollectionInstance = function extendCollectionInstance(self, constructor) {
// Offer a public API to allow the user to define aspects
// Example: collection.before.insert(func);
_.each(['before', 'after'], function (pointcut) {
_.each(advices, function (advice, method) {
['before', 'after'].forEach(function (pointcut) {
Object.entries(advices).forEach(function ([method, advice]) {
if (advice === 'upsert' && pointcut === 'after') return

Meteor._ensure(self, pointcut, method)
Meteor._ensure(self, '_hookAspects', method)

self._hookAspects[method][pointcut] = []
self[pointcut][method] = function (aspect, options) {
var len = self._hookAspects[method][pointcut].push({
aspect: aspect,
const len = self._hookAspects[method][pointcut].push({
aspect,
options: CollectionHooks.initOptions(options, pointcut, method)
})

return {
replace: function (aspect, options) {
replace(aspect, options) {
self._hookAspects[method][pointcut].splice(len - 1, 1, {
aspect: aspect,
options: CollectionHooks.initOptions(options, pointcut, method)
})
},
remove: function () {
remove() {
self._hookAspects[method][pointcut].splice(len - 1, 1)
}
}
Expand All @@ -92,31 +94,30 @@ CollectionHooks.extendCollectionInstance = function extendCollectionInstance(sel
self.hookOptions = EJSON.clone(CollectionHooks.defaults)

// Wrap mutator methods, letting the defined advice do the work
_.each(advices, function (advice, method) {
var collection = Meteor.isClient || method === 'upsert' ? self : self._collection
Object.entries(advices).forEach(function ([method, advice]) {
const collection = Meteor.isClient || method === 'upsert' ? self : self._collection

// Store a reference to the original mutator method
var _super = collection[method]
const _super = collection[method]

Meteor._ensure(self, 'direct', method)
self.direct[method] = function () {
var args = arguments
self.direct[method] = function (...args) {
return CollectionHooks.directOp(function () {
return constructor.prototype[method].apply(self, args)
})
}

collection[method] = function () {
collection[method] = function (...args) {
if (CollectionHooks.directEnv.get() === true) {
return _super.apply(collection, arguments)
return _super.apply(collection, args)
}

// NOTE: should we decide to force `update` with `{upsert:true}` to use
// the `upsert` hooks, this is what will accomplish it. It's important to
// realize that Meteor won't distinguish between an `update` and an
// `insert` though, so we'll end up with `after.update` getting called
// even on an `insert`. That's why we've chosen to disable this for now.
// if (method === "update" && _.isObject(arguments[2]) && arguments[2].upsert) {
// if (method === "update" && Object(args[2]) === args[2] && args[2].upsert) {
// method = "upsert";
// advice = CollectionHooks.getAdvice(method);
// }
Expand All @@ -132,46 +133,38 @@ CollectionHooks.extendCollectionInstance = function extendCollectionInstance(sel
} : self._hookAspects[method] || {},
function (doc) {
return (
_.isFunction(self._transform)
? function (d) { return self._transform(d || doc) }
: function (d) { return d || doc }
typeof self._transform === 'function'
? function (d) { return self._transform(d || doc) }
: function (d) { return d || doc }
)
},
_.toArray(arguments),
args,
false
)
}
})
}

CollectionHooks.defineAdvice = function defineAdvice(method, advice) {
CollectionHooks.defineAdvice = (method, advice) => {
advices[method] = advice
}

CollectionHooks.getAdvice = function getAdvice(method) {
return advices[method]
}
CollectionHooks.getAdvice = method => advices[method];

CollectionHooks.initOptions = function initOptions(options, pointcut, method) {
return CollectionHooks.extendOptions(CollectionHooks.defaults, options, pointcut, method)
}
CollectionHooks.initOptions = (options, pointcut, method) =>
CollectionHooks.extendOptions(CollectionHooks.defaults, options, pointcut, method);

CollectionHooks.extendOptions = function extendOptions(source, options, pointcut, method) {
options = _.extend(options || {}, source.all.all)
options = _.extend(options, source[pointcut].all)
options = _.extend(options, source.all[method])
options = _.extend(options, source[pointcut][method])
return options
}
CollectionHooks.extendOptions = (source, options, pointcut, method) =>
({...options, ...source.all.all, ...source[pointcut].all, ...source.all[method], ...source[pointcut][method]});

CollectionHooks.getDocs = function getDocs(collection, selector, options) {
var findOptions = { transform: null, reactive: false } // added reactive: false
CollectionHooks.getDocs = function getDocs (collection, selector, options) {
const findOptions = {transform: null, reactive: false} // added reactive: false

/*
// No "fetch" support at this time.
if (!this._validators.fetchAllFields) {
findOptions.fields = {};
_.each(this._validators.fetch, function(fieldName) {
this._validators.fetch.forEach(function(fieldName) {
findOptions.fields[fieldName] = 1;
});
}
Expand All @@ -187,8 +180,8 @@ CollectionHooks.getDocs = function getDocs(collection, selector, options) {
if (!options.multi) {
findOptions.limit = 1
}

_.extend(findOptions, _.omit(options, 'multi', 'upsert'))
const { multi, upsert, ...rest } = options
Object.assign(findOptions, rest);
}

// Unlike validators, we iterate over multiple docs, so use
Expand All @@ -213,9 +206,9 @@ CollectionHooks.normalizeSelector = function (selector) {
// case this code changes.
CollectionHooks.getFields = function getFields(mutator) {
// compute modified fields
var fields = []
const fields = []
// ====ADDED START=======================
var operators = [
const operators = [
'$addToSet',
'$bit',
'$currentDate',
Expand All @@ -232,19 +225,19 @@ CollectionHooks.getFields = function getFields(mutator) {
]
// ====ADDED END=========================

_.each(mutator, function (params, op) {
Object.entries(mutator).forEach(function ([op, params]) {
// ====ADDED START=======================
if (_.contains(operators, op)) {
// ====ADDED END=========================
_.each(_.keys(params), function (field) {
if (operators.includes(op)) {
// ====ADDED END=========================
Object.keys(params).forEach(function (field) {
// treat dotted fields as if they are replacing their
// top-level part
if (field.indexOf('.') !== -1) {
field = field.substring(0, field.indexOf('.'))
}

// record the field we are trying to change
if (!_.contains(fields, field)) {
if (!fields.includes(field)) {
fields.push(field)
}
})
Expand All @@ -258,10 +251,9 @@ CollectionHooks.getFields = function getFields(mutator) {
return fields
}

CollectionHooks.reassignPrototype = function reassignPrototype(instance, constr) {
var hasSetPrototypeOf = typeof Object.setPrototypeOf === 'function'

if (!constr) constr = typeof Mongo !== 'undefined' ? Mongo.Collection : Meteor.Collection
CollectionHooks.reassignPrototype = function reassignPrototype (instance, constr) {
const hasSetPrototypeOf = typeof Object.setPrototypeOf === 'function'
constr = constr || Mongo.Collection

// __proto__ is not available in < IE11
// Note: Assigning a prototype dynamically has performance implications
Expand All @@ -276,11 +268,11 @@ CollectionHooks.wrapCollection = function wrapCollection(ns, as) {
if (!as._CollectionConstructor) as._CollectionConstructor = as.Collection
if (!as._CollectionPrototype) as._CollectionPrototype = new as.Collection(null)

var constructor = ns._NewCollectionContructor || as._CollectionConstructor
var proto = as._CollectionPrototype
const constructor = ns._NewCollectionContructor || as._CollectionConstructor
const proto = as._CollectionPrototype

ns.Collection = function () {
var ret = constructor.apply(this, arguments)
ns.Collection = function (...args) {
const ret = constructor.apply(this, args)
CollectionHooks.extendCollectionInstance(this, constructor)
return ret
}
Expand All @@ -290,10 +282,8 @@ CollectionHooks.wrapCollection = function wrapCollection(ns, as) {
ns.Collection.prototype = proto
ns.Collection.prototype.constructor = ns.Collection

for (var prop in constructor) {
if (constructor.hasOwnProperty(prop)) {
ns.Collection[prop] = constructor[prop]
}
for (let prop of Object.keys(constructor)) {
ns.Collection[prop] = constructor[prop]
}

// Meteor overrides the apply method which is copied from the constructor in the loop above. Replace it with the
Expand All @@ -310,22 +300,8 @@ if (typeof Mongo !== 'undefined') {
CollectionHooks.wrapCollection(Meteor, Meteor)
}

if (Meteor.isServer) {
var _publish = Meteor.publish
Meteor.publish = function (name, handler, options) {
return _publish.call(this, name, function () {
// This function is called repeatedly in publications
var ctx = this
var args = arguments
return publishUserId.withValue(ctx && ctx.userId, function () {
return handler.apply(ctx, args)
})
}, options)
}

// Make the above available for packages with hooks that want to determine
// whether they are running inside a publish function or not.
CollectionHooks.isWithinPublish = function isWithinPublish() {
return publishUserId.get() !== undefined
}
// Make the above available for packages with hooks that want to determine
// whether they are running inside a publish function or not.
CollectionHooks.isWithinPublish = function isWithinPublish() {
return publishUserId.get() !== undefined
}
Loading

0 comments on commit 69a95ab

Please sign in to comment.