diff --git a/.versions b/.versions index 17163cc..8a8a36f 100644 --- a/.versions +++ b/.versions @@ -29,12 +29,13 @@ fetch@0.1.1 geojson-utils@1.0.10 html-tools@1.0.11 htmljs@1.0.11 +jagi:astronomy@2.7.2 id-map@1.1.0 insecure@1.0.7 inter-process-messaging@0.1.0 -jagi:astronomy@2.7.1 +jagi:astronomy@2.7.2 jquery@1.11.10 -local-test:jagi:astronomy@2.7.1 +local-test:jagi:astronomy@2.7.2 logging@1.1.20 mdg:validation-error@0.5.1 meteor@1.9.3 diff --git a/lib/core/class.js b/lib/core/class.js index 6da81ff..054eae5 100644 --- a/lib/core/class.js +++ b/lib/core/class.js @@ -5,6 +5,7 @@ import _has from "lodash/has"; import _includes from "lodash/includes"; import _intersection from "lodash/intersection"; import _isNumber from "lodash/isNumber"; +import _isUndefined from "lodash/isUndefined"; import config from "./config"; import throwParseError from "../modules/core/utils/throw_parse_error"; import cloneDefinition from "../modules/core/utils/cloneDefinition"; @@ -75,6 +76,9 @@ class Class { // Set values in a document. _each(Class.getFieldsNames(), fieldName => { doc.set(fieldName, rawDoc[fieldName], options); + if (_isUndefined(doc.get(fieldName))) { + delete doc[fieldName]; + } }); // Trigger the "afterInit" event handlers. diff --git a/lib/core/ejson.js b/lib/core/ejson.js index 9ac1048..275c17c 100644 --- a/lib/core/ejson.js +++ b/lib/core/ejson.js @@ -3,6 +3,16 @@ import Event from '../modules/events/event.js'; EJSON.addType('Astronomy', function(json) { let Class = AstroClass.get(json.class); + + // This sometimes happens on the client if a document arrives from a sub before all + // classes have been initialized in the app. + // One such sub can be the null-sub for user document, which sometimes can arrive before the + // app is fully ready. + if (!Class) { + console.warn(`[Astronomy] a document of class ${json.class} arrived before class was initialized`); + return EJSON.parse(json.values); + } + let doc = new Class(); // Trigger the "fromJSONValue" event handlers. diff --git a/lib/modules/storage/class_prototype_methods/remove.js b/lib/modules/storage/class_prototype_methods/remove.js index f6701ff..b833890 100644 --- a/lib/modules/storage/class_prototype_methods/remove.js +++ b/lib/modules/storage/class_prototype_methods/remove.js @@ -13,7 +13,8 @@ function remove(args = {}, callback) { } // Get variables from the first argument. let { - simulation = true + simulation = true, + ...removeOptions } = args; // If we are dealing with a remote collection and we are not on the server. @@ -57,6 +58,7 @@ function remove(args = {}, callback) { let methodArgs = { doc, simulation, + options: removeOptions, trusted: true }; let result = documentRemove(methodArgs); diff --git a/lib/modules/storage/class_prototype_methods/save.js b/lib/modules/storage/class_prototype_methods/save.js index d450fdf..32eabc2 100644 --- a/lib/modules/storage/class_prototype_methods/save.js +++ b/lib/modules/storage/class_prototype_methods/save.js @@ -126,14 +126,15 @@ function save(options = {}, callback) { } } + const { forceUpdate, ...methodOptions } = options; + // If we can just insert a document without calling the meteor method. We may // be on the server or the collection may be local. try { // Prepare arguments. let methodArgs = { doc, - stopOnFirstError: options.stopOnFirstError, - simulation: options.simulation, + options: methodOptions, trusted: true }; if (inserting) { diff --git a/lib/modules/storage/class_static_methods/insert.js b/lib/modules/storage/class_static_methods/insert.js index c6f737a..27cb94e 100644 --- a/lib/modules/storage/class_static_methods/insert.js +++ b/lib/modules/storage/class_static_methods/insert.js @@ -2,14 +2,23 @@ import isRemote from '../utils/is_remote.js'; import callMeteorMethod from '../utils/call_meteor_method.js'; import classInsert from '../utils/class_insert.js'; -function insert(rawDoc, callback) { +function insert(rawDoc, options, callback) { const Class = this; const Collection = Class.getCollection(); + // If we omit options argument then it may be a callback function. + if (options instanceof Function) { + callback = options; + options = {}; + } + // Make sure that options is at least an empty object. + options = options || {}; + // Prepare arguments. const args = { className: Class.getName(), - rawDoc + rawDoc, + options, }; // Generate ID if not provided. diff --git a/lib/modules/storage/utils/class_insert.js b/lib/modules/storage/utils/class_insert.js index e1d0db5..54a9820 100644 --- a/lib/modules/storage/utils/class_insert.js +++ b/lib/modules/storage/utils/class_insert.js @@ -7,6 +7,7 @@ function classInsert(args = {}) { rawDoc, stopOnFirstError, fields, + options = {}, simulation = true, trusted = false, } = args; @@ -27,6 +28,7 @@ function classInsert(args = {}) { stopOnFirstError, simulation, trusted, + options, }); }; diff --git a/lib/modules/storage/utils/class_remove.js b/lib/modules/storage/utils/class_remove.js index 9e5858f..99f59b1 100644 --- a/lib/modules/storage/utils/class_remove.js +++ b/lib/modules/storage/utils/class_remove.js @@ -35,7 +35,8 @@ function classRemove(args = {}) { result += documentRemove({ doc, simulation, - trusted + trusted, + options, }); }); diff --git a/lib/modules/storage/utils/class_update.js b/lib/modules/storage/utils/class_update.js index e15bc57..d48b5fe 100644 --- a/lib/modules/storage/utils/class_update.js +++ b/lib/modules/storage/utils/class_update.js @@ -86,6 +86,7 @@ function classUpdate(args = {}) { simulation, fields, trusted, + options, oldDoc }); }); diff --git a/lib/modules/storage/utils/class_upsert.js b/lib/modules/storage/utils/class_upsert.js index fd73597..88512fe 100644 --- a/lib/modules/storage/utils/class_upsert.js +++ b/lib/modules/storage/utils/class_upsert.js @@ -99,6 +99,7 @@ function classUpsert(args = {}) { // Insert a document. result.insertedId = documentInsert({ doc, + options, stopOnFirstError, simulation, trusted, diff --git a/lib/modules/storage/utils/document_insert.js b/lib/modules/storage/utils/document_insert.js index 4c51729..40b78c2 100644 --- a/lib/modules/storage/utils/document_insert.js +++ b/lib/modules/storage/utils/document_insert.js @@ -12,6 +12,7 @@ function documentInsert(args = {}) { doc, stopOnFirstError, fields, + options, simulation = true, trusted = false } = args; @@ -69,6 +70,8 @@ function documentInsert(args = {}) { }); values = _omitBy(values, (value) => value === undefined); + const { multi, clone, isInsert, ...insertOptions } = options; + // Insert a document. try { // There is a difference in what the insert method returns depending on the @@ -76,7 +79,23 @@ function documentInsert(args = {}) { // server it returns array of inserted documents. So we always return the // generated id. We can't send an entire document because it could be a // security issue if we are not subscribed to all fields of a document. - Collection._collection.insert(values); + if (Meteor.isServer) { + // Use mongo's normal collection methods, to allow interop with other packages + // that override it. + + if (Object.keys(insertOptions).length) { + // If we are providing extra options to insert, pass them on. + // This is not supported by the original mongo insert, so it + // assumes an overriding package that adds this option. + Collection.insert(values, insertOptions); + } else { + Collection.insert(values); + } + } else { + // Avoid Meteor calling insert method from client, + // we do this ourselves + Collection._collection.insert(values); + } // Change the "_isNew" flag to "false". Mark a document as not new. doc._isNew = false; diff --git a/lib/modules/storage/utils/document_remove.js b/lib/modules/storage/utils/document_remove.js index e3decee..0a8bf32 100644 --- a/lib/modules/storage/utils/document_remove.js +++ b/lib/modules/storage/utils/document_remove.js @@ -4,6 +4,7 @@ import triggerAfterRemove from './trigger_after_remove.js'; function documentRemove(args = {}) { const { doc, + options = {}, simulation = true, trusted = false } = args; @@ -30,11 +31,32 @@ function documentRemove(args = {}) { // Trigger before events. triggerBeforeRemove(args); + const { multi, ...removeOptions } = options; + // Remove a document. try { - const result = Collection._collection.remove({ - _id: doc._id - }); + let result; + + if (Meteor.isServer) { + // Use mongo's normal collection methods, to allow interop with other packages + // that override it. + + if (Object.keys(removeOptions).length) { + result = Collection.remove({ + _id: doc._id + }, removeOptions); + } else { + result = Collection.remove({ + _id: doc._id + }); + } + } else { + // Avoid Meteor calling insert method from client, + // we do this ourselves + result = Collection._collection.remove({ + _id: doc._id + }); + } // Mark a document as new, so it will be possible to save it again. doc._isNew = true; diff --git a/lib/modules/storage/utils/document_update.js b/lib/modules/storage/utils/document_update.js index b71610b..1ec1de7 100644 --- a/lib/modules/storage/utils/document_update.js +++ b/lib/modules/storage/utils/document_update.js @@ -1,5 +1,6 @@ import _omit from "lodash/omit"; import _size from "lodash/size"; +import _isUndefined from "lodash/isUndefined"; import castNested from "../../fields/utils/castNested"; import triggerBeforeSave from "./trigger_before_save"; import triggerBeforeUpdate from "./trigger_before_update"; @@ -17,6 +18,7 @@ function documentUpdate(args = {}) { simulation = true, forceUpdate = false, trusted = false, + options = {}, oldDoc } = args; @@ -97,12 +99,28 @@ function documentUpdate(args = {}) { } // Update a document. try { - const result = Collection._collection.update( - { - _id: doc._id - }, - modifier - ); + let result; + + if (Meteor.isServer) { + // Use mongo's normal collection methods, to allow interop with other packages + // that override it. + result = Collection.update( + { + _id: doc._id + }, + modifier, + options + ); + } else { + // Avoid Meteor calling insert method from client, + // we do this ourselves + result = Collection._collection.update( + { + _id: doc._id + }, + modifier + ); + } // Trigger after events. triggerAfterUpdate(args);