diff --git a/grouping.coffee b/grouping.coffee index 306b70d..78fd5ed 100644 --- a/grouping.coffee +++ b/grouping.coffee @@ -5,7 +5,10 @@ Grouping contains _id: userId and groupId: groupId ### -Partitioner = {} +Partitioner = + _documentGroupIdProperty: "_groupId" + _returnGroupIdAsField: false + Grouping = new Mongo.Collection("ts.grouping") # Meteor environment variables for scoping group operations @@ -16,6 +19,13 @@ Partitioner._directOps = new Meteor.EnvironmentVariable() Public API ### +# Allow to change the document property for groupId +Partitioner.setGroupIdPropertyName = (name) -> + Partitioner._groupIdProperty = name + +Partitioner.setReturnGroupIdAsField = (b) -> + Partitioner._returnGroupIdAsField = b + Partitioner.setUserGroup = (userId, groupId) -> check(userId, String) check(groupId, String) @@ -59,7 +69,8 @@ Partitioner.directOperation = (func) -> Partitioner._isAdmin = (userId) -> Meteor.users.findOne(userId).admin is true getPartitionedIndex = (index) -> - defaultIndex = { _groupId : 1 } + defaultIndex = {} + defaultIndex[Partitioner._documentGroupIdProperty] = 1 return defaultIndex unless index return _.extend( defaultIndex, index ) @@ -156,22 +167,25 @@ findHook = (userId, selector, options) -> # if object (or empty) selector, just filter by group unless selector? - @args[0] = { _groupId : groupId } + @args[0] = {} + @args[0][Partitioner._documentGroupIdProperty] = groupId; else - selector._groupId = groupId + selector[Partitioner._documentGroupIdProperty] = groupId # Adjust options to not return _groupId - unless options? - @args[1] = { fields: {_groupId: 0} } - else - # If options already exist, add {_groupId: 0} unless fields has {foo: 1} somewhere - options.fields ?= {} - options.fields._groupId = 0 unless _.any(options.fields, (v) -> v is 1) + if Partitioner._returnGroupIdAsField == false + unless options? + @args[1] = {fields: {}} + @args[1]['fields'][Partitioner._documentGroupIdProperty] = 0; + else + # If options already exist, add {_groupId: 0} unless fields has {foo: 1} somewhere + options.fields ?= {} + options.fields[Partitioner._documentGroupIdProperty] = 0 unless _.any(options.fields, (v) -> v is 1) return true insertHook = (userId, doc) -> - # Don't add group for direct inserts +# Don't add group for direct inserts return true if Partitioner._directOps.get() is true groupId = Partitioner._currentGroup.get() @@ -180,20 +194,20 @@ insertHook = (userId, doc) -> groupId = Grouping.findOne(userId)?.groupId throw new Meteor.Error(403, ErrMsg.groupErr) unless groupId - doc._groupId = groupId + doc[Partitioner._documentGroupIdProperty] = groupId return true # Sync grouping needed for hooking Meteor.users Grouping.find().observeChanges added: (id, fields) -> - unless Meteor.users.update(id, $set: {"group": fields.groupId} ) + unless Meteor.users.update(id, $set: {"group": fields.groupId}) Meteor._debug "Tried to set group for nonexistent user #{id}" return changed: (id, fields) -> - unless Meteor.users.update(id, $set: {"group": fields.groupId} ) + unless Meteor.users.update(id, $set: {"group": fields.groupId}) Meteor._debug "Tried to change group for nonexistent user #{id}" removed: (id) -> - unless Meteor.users.update(id, $unset: {"group": null} ) + unless Meteor.users.update(id, $unset: {"group": null}) Meteor._debug "Tried to unset group for nonexistent user #{id}" TestFuncs = diff --git a/grouping_client.coffee b/grouping_client.coffee index 3c11c35..5bc1a17 100644 --- a/grouping_client.coffee +++ b/grouping_client.coffee @@ -1,4 +1,5 @@ -Partitioner = {} +Partitioner = + _documentGroupIdProperty: "_groupId" ### Client selector modifiers @@ -10,7 +11,7 @@ Partitioner.group = -> return Meteor.users.findOne(userId, fields: {group: 1})?.group userFindHook = (userId, selector, options) -> - # Do the usual find for no user or single selector +# Do the usual find for no user or single selector return true if !userId or Helpers.isDirectUserSelector(selector) # No hooking needed for regular users, taken care of on server @@ -31,15 +32,18 @@ insertHook = (userId, doc) -> throw new Meteor.Error(403, ErrMsg.userIdErr) unless userId groupId = Partitioner.group() throw new Meteor.Error(403, ErrMsg.groupErr) unless groupId - doc._groupId = groupId + doc[Partitioner._documentGroupIdProperty] = groupId return true # Add in groupId for client so as not to cause unexpected sync changes Partitioner.partitionCollection = (collection) -> - # No find hooks needed if server side filtering works properly - +# No find hooks needed if server side filtering works properly collection.before.insert insertHook +# Allow to change the document property for groupId +Partitioner.setGroupIdPropertyName = (name) -> + Partitioner._groupIdProperty = name + TestFuncs = userFindHook: userFindHook insertHook: insertHook diff --git a/package.js b/package.js index 2e971d9..07449ba 100644 --- a/package.js +++ b/package.js @@ -1,60 +1,60 @@ Package.describe({ - name: "mizzao:partitioner", - summary: "Transparently divide a meteor app into different instances shared between groups of users.", - version: "0.5.9", - git: "https://github.com/mizzao/meteor-partitioner.git" + name: "mizzao:partitioner", + summary: "Transparently divide a meteor app into different instances shared between groups of users.", + version: "0.5.9", + git: "https://github.com/mizzao/meteor-partitioner.git" }); Package.onUse(function (api) { - api.versionsFrom("1.2.0.1"); + api.versionsFrom("1.2.0.1"); - // Client & Server deps - api.use([ - 'accounts-base', - 'underscore', - 'coffeescript', - 'check', - 'ddp', // Meteor.publish available - 'mongo' // Mongo.Collection available - ]); + // Client & Server deps + api.use([ + 'accounts-base', + 'underscore', + 'coffeescript', + 'check', + 'ddp', // Meteor.publish available + 'mongo' // Mongo.Collection available + ]); - api.use("matb33:collection-hooks@0.7.15"); + api.use("matb33:collection-hooks@0.7.15"); - api.addFiles('common.coffee'); + api.addFiles('common.coffee'); - api.addFiles('grouping.coffee', 'server'); - api.addFiles('grouping_client.coffee', 'client'); + api.addFiles('grouping.coffee', 'server'); + api.addFiles('grouping_client.coffee', 'client'); - api.export(['Partitioner', 'Grouping']); + api.export(['Partitioner', 'Grouping']); - // Package-level variables that should not be exported - // See http://docs.meteor.com/#/full/coffeescript - api.export(['ErrMsg', 'Helpers'], {testOnly: true}); + // Package-level variables that should not be exported + // See http://docs.meteor.com/#/full/coffeescript + api.export(['ErrMsg', 'Helpers'], {testOnly: true}); - api.export('TestFuncs', {testOnly: true}); + api.export('TestFuncs', {testOnly: true}); }); Package.onTest(function (api) { - api.use("mizzao:partitioner"); - - api.use([ - 'accounts-base', - 'accounts-password', // For createUser - 'coffeescript', - 'underscore', - 'ddp', // Meteor.publish available - 'mongo', // Mongo.Collection available - 'tracker' // Deps/Tracker available - ]); - - api.use([ - 'tinytest', - 'test-helpers' - ]); - - api.addFiles("tests/insecure_login.js"); - - api.addFiles('tests/hook_tests.coffee'); - api.addFiles('tests/grouping_index_tests.coffee', 'server'); - api.addFiles('tests/grouping_tests.coffee'); + api.use("mizzao:partitioner"); + + api.use([ + 'accounts-base', + 'accounts-password', // For createUser + 'coffeescript', + 'underscore', + 'ddp', // Meteor.publish available + 'mongo', // Mongo.Collection available + 'tracker' // Deps/Tracker available + ]); + + api.use([ + 'tinytest', + 'test-helpers' + ]); + + api.addFiles("tests/insecure_login.js"); + + api.addFiles('tests/hook_tests.coffee'); + api.addFiles('tests/grouping_index_tests.coffee', 'server'); + api.addFiles('tests/grouping_tests.coffee'); }); diff --git a/tests/grouping_tests.coffee b/tests/grouping_tests.coffee index 36bbf5d..fd673f5 100644 --- a/tests/grouping_tests.coffee +++ b/tests/grouping_tests.coffee @@ -53,10 +53,12 @@ if Meteor.isServer Partitioner.directOperation -> twoGroupCollection.insert _groupId: myGroup + _otherGroupId: myGroup a: 1 twoGroupCollection.insert _groupId: otherGroup + _otherGroupId: otherGroup a: 1 Meteor._debug "collections configured" @@ -102,6 +104,38 @@ if Meteor.isServer basicInsertCollection.insert { foo: "bar"} test.ok() + Tinytest.add "partitioner - collections - insert with custom groupIdProperty", (test) -> + Partitioner.setGroupIdPropertyName '_custom' + Partitioner.bindGroup "overridden", -> + basicInsertCollection.insert { foo: "bar"} + inserted = basicInsertCollection.findOne({foo:"bar"}) + test.isFalse inserted._groupId? + test.isFalse inserted._custom? + test.ok() + Partitioner.setGroupIdPropertyName '_groupId' + + Tinytest.add "partitioner - collections - return groupIdProperty", (test) -> + Partitioner.setReturnGroupIdAsField true + Partitioner.bindGroup "overridden", -> + basicInsertCollection.insert { foo: "bar"} + inserted = basicInsertCollection.findOne({foo:"bar"}) + test.isTrue inserted._groupId? + test.equal inserted._groupId, "overridden" + test.ok() + Partitioner.setReturnGroupIdAsField false + + Tinytest.add "partitioner - collections - return groupIdProperty with custom groupIdProperty", (test) -> + Partitioner.setGroupIdPropertyName '_custom' + Partitioner.setReturnGroupIdAsField true + Partitioner.bindGroup "overridden", -> + basicInsertCollection.insert { foo: "bar"} + inserted = basicInsertCollection.findOne({foo:"bar"}) + test.isTrue inserted._custom? + test.equal inserted._custom, "overridden" + test.ok() + Partitioner.setGroupIdPropertyName '_groupId' + Partitioner.setReturnGroupIdAsField false + if Meteor.isClient ### These tests need to all async so they are in the right order @@ -153,6 +187,18 @@ if Meteor.isClient test.isFalse basicInsertCollection.findOne(a: 1)._groupId? ] + testAsyncMulti "partitioner - collections - basic insert with custom groupIdProperty", [ + (test, expect) -> + Partitioner.setGroupIdPropertyName '_custom' + id = basicInsertCollection.insert { a: 1 }, expect (err, res) -> + test.isFalse err, JSON.stringify(err) + test.equal res, id + , (test, expect) -> + test.equal basicInsertCollection.find({a: 1}).count(), 1 + test.isFalse basicInsertCollection.findOne(a: 1)._groupId? + Partitioner.setGroupIdPropertyName '_groupId' + ] + testAsyncMulti "partitioner - collections - find from two groups", [ (test, expect) -> test.equal twoGroupCollection.find().count(), 1 diff --git a/tests/hook_tests.coffee b/tests/hook_tests.coffee index a5e2360..63d7b64 100644 --- a/tests/hook_tests.coffee +++ b/tests/hook_tests.coffee @@ -191,6 +191,17 @@ if Meteor.isServer test.equal ctx.args[1].fields.foo, 0 test.equal ctx.args[1].fields._groupId, 0 + Tinytest.add "partitioner - hooks - find with custom groupIdProperty", (test) -> + Partitioner.setGroupIdPropertyName "_custom" + ctx = + args: [{ }] + + TestFuncs.findHook.call(ctx, userId, ctx.args[0]) + + test.equal ctx.args[0]._custom, testGroupId + + Partitioner.setGroupIdPropertyName "_groupId" + Tinytest.add "partitioner - hooks - insert doc", (test) -> ctx = args: [ { foo: "bar" } ]