JackAdams / meteor-transactions

App level transactions for Meteor + Mongo
http://transactions.taonova.com/
MIT License
113 stars 16 forks source link

Error "Cannot use 'in' operator to search for 'amount' in undefined" #35

Closed tab00 closed 9 years ago

tab00 commented 9 years ago

This error appears when there is an update using $inc in the modifier. It's fine without using $inc.

Here is some of the error output:

at _.extend._get (packages/meteor/helpers.js:23:1)
at [object Object].Transact._drillDown (packages/babrahams:transactions/lib/transactions-common.js:999:1)
at packages/babrahams:transactions/lib/transactions-common.js:1022:1
at Array.forEach (native)
at Function._.each._.forEach (packages/underscore/underscore.js:105:1)
at [object Object].Transact._inverseUsingSet (packages/babrahams:transactions/lib/transactions-common.js:1021:1)
at [object Object].Transact.update (packages/babrahams:transactions/lib/transactions-common.js:570:1)
at [object Object].Mongo.Collection.(anonymous function) [as update] (packages/babrahams:transactions/lib/transactions-common.js:1450:1)

Here are the packages I've added:

accounts-password           1.1.1  Password support for accounts
alanning:roles              1.2.13  Role-based authorization
aldeed:autoform             5.4.0  Easily create forms with automatic insert and update, and automatic reactive validation.
aldeed:collection2          2.3.3  Automatic validation of insert and update operations on the client and server.
babrahams:transactions      0.7.3  App level transactions for Meteor + Mongo
cfs:standard-packages       0.5.9  Filesystem for Meteor, collectionFS
dburles:collection-helpers  1.0.3  Transform your collections with helpers that you define
email                       1.0.6  Send email messages
fortawesome:fontawesome     4.4.0  Font Awesome (official): 500+ scalable vector icons, customizable via CSS, Retina friendly
iron:router                 1.0.9  Routing specifically designed for Meteor
less                        1.0.14  The dynamic stylesheet language
meteor-platform             1.2.2  Include a standard set of Meteor packages in your app
meteorhacks:npm             1.4.0  Use npm modules with your Meteor App
mrt:flash-messages          1.0.1  A package to display flash messages to the user
npm-container               1.1.0+ Contains all your npm dependencies
ongoworks:security          1.2.0  Logical security for client-originated MongoDB collection operations
twbs:bootstrap              3.3.5  The most popular front-end framework for developing responsive, mobile first projects on the web.
useraccounts:bootstrap      1.12.0  Accounts Templates styled for Twitter Bootstrap.
yogiben:admin               1.2.2  A complete admin dashboard solution
yogiben:autoform-file       0.2.7* File upload for AutoForm
yogiben:autoform-map        0.1.3  Edit location coordinates with autoForm
zimme:active-route          2.3.0  Active route helpers
JackAdams commented 9 years ago

Can I take a look at the full error output? I had a similar issue earlier today, but it turned out it was a problem with my app code.

JackAdams commented 9 years ago

I've just added some tests for the $inc operator and they all seem to be passing. Any chance of a minimal repro somewhere? Meteorpad would be great, but you'd need to add babrahams:undo-redo to force the latest version of babrahams:transactions to be added. (Adding babrahams:transactions gets an old version for some reason.)

tab00 commented 9 years ago

I've just tried some more tests and it appears that omitting $inc also produces an error message, but a different one.

Using { amount: 2 }, { tx: true }:

Started "trade" with transaction_id: AkTxAXtPZ6sSThofb
Executed instant insert
Pushed insert command to stack: AkTxAXtPZ6sSThofb
Pushed insert command to stack: AkTxAXtPZ6sSThofb
Exception while invoking method 'exchangeInstruments' TypeError: Object.keys called on non-object
    at Function.keys (native)
    at [object Object].Transact._inverseUsingSet (packages/babrahams:transactions/lib/transactions-common.js:1021:1)
    at [object Object].Transact.update (packages/babrahams:transactions/lib/transactions-common.js:570:1)
    at [object Object].Mongo.Collection.(anonymous function) [as update] (packages/babrahams:transactions/lib/transactions-common.js:1450:1)
    at [object Object].Meteor.methods.exchangeInstruments (app\server\methods.js:218:18)
    at maybeAuditArgumentChecks (packages/ddp/livedata_server.js:1617:1)
    at packages/ddp/livedata_server.js:648:1
    at [object Object]._.extend.withValue (packages/meteor/dynamics_nodejs.js:56:1)
    at packages/ddp/livedata_server.js:647:1
    at [object Object]._.extend.withValue (packages/meteor/dynamics_nodejs.js:56:1)
Transaction (AkTxAXtPZ6sSThofb) was cancelled after being inactive for 5 seconds.
Rolled back insert
Rollback reset transaction manager to clean state

Using { $inc: { amount: 1 } }, { tx: true }:

Started "trade" with transaction_id: zXPFisCM7EM7pEshD
Executed instant insert
Pushed insert command to stack: zXPFisCM7EM7pEshD
Pushed insert command to stack: zXPFisCM7EM7pEshD
Exception while invoking method 'exchangeInstruments' TypeError: Cannot use 'in' operator to search for 'amount' in undefined
    at _.extend._get (packages/meteor/helpers.js:23:1)
    at [object Object].Transact._drillDown (packages/babrahams:transactions/lib/transactions-common.js:999:1)
    at packages/babrahams:transactions/lib/transactions-common.js:1022:1
    at Array.forEach (native)
    at [object Object].Transact._inverseUsingSet (packages/babrahams:transactions/lib/transactions-common.js:1021:1)
    at [object Object].Transact.update (packages/babrahams:transactions/lib/transactions-common.js:570:1)
    at [object Object].Mongo.Collection.(anonymous function) [as update] (packages/babrahams:transactions/lib/transactions-common.js:1450:1)
    at [object Object].Meteor.methods.exchangeInstruments (app\server\methods.js:218:18)
    at Function._.each._.forEach (packages/underscore/underscore.js:105:1)
    at maybeAuditArgumentChecks (packages/ddp/livedata_server.js:1617:1)
Transaction (zXPFisCM7EM7pEshD) was cancelled after being inactive for 5 seconds.
Rolled back insert
Rollback reset transaction manager to clean state

Using { $inc: { amount: 1 } }, { tx: false }:

Started "trade" with transaction_id: dfMGJQqMrxCo67JKA
Executed instant insert
Pushed insert command to stack: dfMGJQqMrxCo67JKA
Pushed insert command to stack: dfMGJQqMrxCo67JKA
Pushed insert command to stack: dfMGJQqMrxCo67JKA
Pushed insert command to stack: dfMGJQqMrxCo67JKA
Beginning commit with transaction_id: dfMGJQqMrxCo67JKA
Executed insert
Executed insert
Executed insert
Executed insert
Commit reset transaction manager to clean state
JackAdams commented 9 years ago

I'm going to need to see a bit more surrounding code context to figure out what's going on. It looks like there's a field in the document that you're updating that has a value of undefined or something like that.

tab00 commented 9 years ago

I think you can disregard the "Object.keys called on non-object" error as it maybe because I didn't write "$set" in the modifier. But when I do write "$set" I get the same error as using "$inc".

Here's my trimmed-down code which still produces the same "Cannot use 'in' operator" error: In event handler:

    "click #submit": function () {
        Meteor.call("exchangeInstruments", function (error, result) {
                if (error) {
                FlashMessages.sendError('Transaction failed. ' + error.error);
            } else
            FlashMessages.sendSuccess('Transaction successful');
        });
    }

The server method:

    exchangeInstruments: function () {
        tx.start("trade");
        Holdings.update({ userId: "36i8wYect6o9j4fQ4", instrumentCode: "MMK" }, { $inc: { amount: 2 } }, { tx: true });
        Holdings.insert({ userId: "36i8wYect6o9j4fQ4", instrumentCode: "ZZZ", amount: 1 }, { tx: true });
        tx.commit();
    }

Using { $set: { amount: 2 } }, { tx: true }:

Started "trade" with transaction_id: AqFWCKtADN8xRtGik
Exception while invoking method 'exchangeInstruments' TypeError: Cannot use 'in' operator to search for 'amount' in undefined
    at _.extend._get (packages/meteor/helpers.js:23:1)
    at [object Object].Transact._drillDown (packages/babrahams:transactions/lib/transactions-common.js:999:1)
    at packages/babrahams:transactions/lib/transactions-common.js:1022:1
    at Array.forEach (native)
    at Function._.each._.forEach (packages/underscore/underscore.js:105:1)
    at [object Object].Transact.update (packages/babrahams:transactions/lib/transactions-common.js:570:1)
    at [object Object].Transact._inverseUsingSet (packages/babrahams:transactions/lib/transactions-common.js:1021:1)
    at [object Object].Mongo.Collection.(anonymous function) [as update] (packages/babrahams:transactions/lib/transactions-common.js:1450:1)
    at [object Object].Meteor.methods.exchangeInstruments (app\server\methods.js:233:18)
    at maybeAuditArgumentChecks (packages/ddp/livedata_server.js:1617:1)
Transaction (AqFWCKtADN8xRtGik) was cancelled after being inactive for 5 seconds.
Rollback reset transaction manager to clean state

Using { $inc: { amount: 2 } }, { tx: true }:

Started "trade" with transaction_id: ypEShp3E9BNn3bfMW
Exception while invoking method 'exchangeInstruments' TypeError: Cannot use 'in' operator to search for 'amount' in undefined
    at _.extend._get (packages/meteor/helpers.js:23:1)
    at [object Object].Transact._drillDown (packages/babrahams:transactions/lib/transactions-common.js:999:1)
    at packages/babrahams:transactions/lib/transactions-common.js:1022:1
    at Array.forEach (native)
    at Function._.each._.forEach (packages/underscore/underscore.js:105:1)
    at [object Object].Transact._inverseUsingSet (packages/babrahams:transactions/lib/transactions-common.js:1021:1)
    at [object Object].Transact.update (packages/babrahams:transactions/lib/transactions-common.js:570:1)
    at [object Object].Mongo.Collection.(anonymous function) [as update] (packages/babrahams:transactions/lib/transactions-common.js:1450:1)
    at [object Object].Meteor.methods.exchangeInstruments (app\server\methods.js:233:18)
    at maybeAuditArgumentChecks (packages/ddp/livedata_server.js:1617:1)
Transaction (ypEShp3E9BNn3bfMW) was cancelled after being inactive for 5 seconds.
Rollback reset transaction manager to clean state

Using { $inc: { amount: 2 } }, { tx: false }:

Started "trade" with transaction_id: pZYpuQ63qweTQqSma
Pushed insert command to stack: pZYpuQ63qweTQqSma
Beginning commit with transaction_id: pZYpuQ63qweTQqSma
Executed insert
Commit reset transaction manager to clean state

You can see that the insert is working fine.

tab00 commented 9 years ago

Here is the definition of the collection and schema:

Holdings = new Mongo.Collection("holdings");

Schemas.Holding = new SimpleSchema({
    userId: {
        type: String,
        regEx: SimpleSchema.RegEx.Id
    },

    instrumentCode: {
        type: String
    },

    amount: {
        type: Number,
        decimal: true
    }
});

Holdings.attachSchema(Schemas.Holding);
JackAdams commented 9 years ago

Ah... got it now. The selector { userId: "36i8wYect6o9j4fQ4", instrumentCode: "MMK" } doesn't have an _id value, which is the issue here. The transactions package needs one to function (for updates and removes).
Note: because this package doesn't support {multi:true}, using a selector like { userId: "36i8wYect6o9j4fQ4", instrumentCode: "MMK" } will only retrieve one document. I'm thinking about maybe having the package retrieve a document if no _id value is passed and using that one, but it could lead to unpredictable results if the selector matches several documents. As a workaround for the time being, this code should work for you:

exchangeInstruments: function () {
  tx.start("trade");
    var holding = Holdings.findOne({ userId: "36i8wYect6o9j4fQ4", instrumentCode: "MMK" });
    Holdings.update({ _id: holding._id }, { $inc: { amount: 2 } }, { tx: true });
    Holdings.insert({ userId: "36i8wYect6o9j4fQ4", instrumentCode: "ZZZ", amount: 1 }, { tx: true });
  tx.commit();
}