Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mongoose error - TypeError: _arr[i].emit is not a function #6723

Closed
iagowp opened this issue Jul 14, 2018 · 8 comments
Closed

Mongoose error - TypeError: _arr[i].emit is not a function #6723

iagowp opened this issue Jul 14, 2018 · 8 comments
Milestone

Comments

@iagowp
Copy link
Contributor

iagowp commented Jul 14, 2018

Do you want to request a feature or report a bug?
Bug
What is the current behavior?
After updating from mongoose 4 to 5, code that worked and doesn't seem to be included on changelog breaking changes stopped working
If the current behavior is a bug, please provide the steps to reproduce.

This is my the code with problem:

Product.findOne({ _id: req.body.idProduct }).exec(function(err, product){
 if(err){
   return err;
 }

 _.extend(product, req.body);
 product = formatDescription(product);

 product.featured = req.body.featured === 'on'
 product.active = req.body.active === 'on'

 product.save(function(err, product){
   console.log(err)
 });

   return res.render('admin/products/edit', { title: 'Edit successfully', product: product, itens: itens, products: products, categories: categories});
});

The error I'm getting

TypeError: _arr[i].emit is not a function
  at EventEmitter.notify (/Users/iagowp/Desktop/trampos/frutacor/node_modules/mongoose/lib/types/documentarray.js:337:19)
  at EventEmitter.emit (events.js:187:15)
  at EventEmitter.emit (domain.js:442:20)
  at model.Document.(anonymous function) [as emit] (/Users/iagowp/Desktop/trampos/frutacor/node_modules/mongoose/lib/document.js:146:42)
  at model.Model.$__handleSave (/Users/iagowp/Desktop/trampos/frutacor/node_modules/mongoose/lib/model.js:233:10)
  at model.Model.$__save (/Users/iagowp/Desktop/trampos/frutacor/node_modules/mongoose/lib/model.js:243:8)
  at /Users/iagowp/Desktop/trampos/frutacor/node_modules/kareem/index.js:278:20
  at _next (/Users/iagowp/Desktop/trampos/frutacor/node_modules/kareem/index.js:102:16)
  at process.nextTick (/Users/iagowp/Desktop/trampos/frutacor/node_modules/kareem/index.js:452:38)
  at process._tickCallback (internal/process/next_tick.js:61:11)

My object product looks like this

{ photos:
   { photo1: '',
     photo2: '' },
  name: 'LANCHE CAIXA LIVRO',
  type: 'cesta',
  related_products:
   [ 58d953db02cbeb1000c2252c,
     5a9704cfc85a33130048d03b ],
  addons:
   [ 58c98ff147d68f1000dc0aa4,
     59cb9de1d554581100b6a63d ],
  featured: false,
  active: false,
  _id: 59c41f5020a98825003bc026,
  slug: 'lanche-caixa-livro',
  code: '2475',
  inventory: null,
  descricao_avulsa: '',
  price: 7900,
  discount: null,
  category: 583f0ec84feeca1000835804,
  description:
   [ { _id: 5b490101bad42c1a2c93e074,
       item: 59c4325720a98825003bc078,
       quantity: null },
     { _id: 5b490101bad42c1a2c93e075,
       item: 59c52510a9685111001c2fa1,
       quantity: 1 },
     { _id: 5b490101bad42c1a2c93e07c,
       item: 58eb8d51147a3b1a000e0d5d,
       quantity: null } ],
  __v: 18 }

And lastly, my model looks like this

var ProductSchema = new Schema({
  name: { type: String, default: '' },
  inventory: { type: Number },
  type: { type: String, default: 'cesta' },
  price: { type: Number, required: true },
  discount: { type: Number},
  description: [{
    item: { type : Schema.ObjectId, ref : 'Item' },
    quantity: Number
  }],
  photos: {
    photo1: String,
    photo2: String
  },
  related_products: [{ type : Schema.ObjectId, ref : 'Product' }],
  addons: [{ type : Schema.ObjectId, ref : 'Product' }],
  category: { type: Schema.ObjectId, ref: 'Category' },
  code: { type: String }, 
  descricao_avulsa: String,
  slug: String,
  featured: {type: Boolean, default: false,},
  active: {type: Boolean, default: false}
});

What is the expected behavior?
I expected it to just update instead of getting an error, I'm not sure this is a bug I had initially posted on stack overflow, but if it isn't, the migration guide might be missing something.

Please mention your node.js, mongoose and MongoDB version.
Node 10.3, Mongoose 5.2.2, MongoDB 3.6.5

@lineus
Copy link
Collaborator

lineus commented Jul 14, 2018

@iagowp In order for me to better replicate this, can you provide your formatDescription function and the value of req.body when this route fails?

@iagowp
Copy link
Contributor Author

iagowp commented Jul 14, 2018

It fails whenever I have a description array, but here its an example before I do the extend:

{ idProduct: '5b4984e921efa02d5658504e',
  type: 'cesta',
  name: 'psodkapo',
  code: '123',
  inventory: '1',
  description:
   [ '5849bbdc57d6331000bd3781',
     '5849bc4f57d6331000bd3785',
     '587e6bc21f56ca24009076c6' ],
  'product-587e6bc21f56ca24009076c6': '1',
  'product-5849bc4f57d6331000bd3785': '2',
  'product-5849bbdc57d6331000bd3781': '1',
  descricao_avulsa: '',
  price: '8600',
  discount: '',
  category: '56a123c07d21baf91382035b',
  related_products: '58c9896947d68f1000dc0a5c',
  addons: '58e4e6d298a41810000865bb',
  active: 'on' }

formatDescription
Format description just relate the ammounts to the ids and delete these "product-ids"

var formatDescription = function(product){
  product.description = [];
  if(product.type == 'cesta'){
    var id;
    for(var key in product){
      if(key.indexOf('product-') !== -1){
        id = key.split('product-')[1];
        product.description.push({
          item: mongoose.Types.ObjectId(id),
          quantity: product[key]
        });
        delete product[key];
      }
    }
  }

  return product;
};

I thought just the final product before saving would be enough, my bad

@lineus
Copy link
Collaborator

lineus commented Jul 14, 2018

@iagowp what specific version of mongoose were you on before you upgraded to 5?

@iagowp
Copy link
Contributor Author

iagowp commented Jul 14, 2018

4.0.6

@iagowp
Copy link
Contributor Author

iagowp commented Jul 15, 2018

@lineus Do you have any idea of what the problem might be, so I can get my application to work again on the current version?

@lineus
Copy link
Collaborator

lineus commented Jul 15, 2018

I couldn't get this to work with any version of mongoose including 4.0.6.

Here is the repro code:

6723.js

#!/usr/bin/env node
'use strict';

const _ = require('lodash');
const mongoose = require('mongoose');
let uri = 'mongodb://localhost:27017/test';
let opts = {};

if (mongoose.version.charAt(0) === '4') {
  mongoose.Promise = global.Promise;
  opts.useMongoClient = true;
} else {
  opts.useNewUrlParser = true;
}

mongoose.connect(uri, opts);
const conn = mongoose.connection;
const Schema = mongoose.Schema;

var productSchema = new Schema({
  name: { type: String, default: '' },
  inventory: { type: Number },
  type: { type: String, default: 'cesta' },
  price: { type: Number, required: true },
  discount: { type: Number },
  description: [{
    item: { type: Schema.ObjectId, ref: 'Item' },
    quantity: Number
  }],
  photos: {
    photo1: String,
    photo2: String
  },
  related_products: [{ type: Schema.ObjectId, ref: 'Product' }],
  addons: [{ type: Schema.ObjectId, ref: 'Product' }],
  category: { type: Schema.ObjectId, ref: 'Category' },
  code: { type: String },
  descricao_avulsa: String,
  slug: String,
  featured: { type: Boolean, default: false, },
  active: { type: Boolean, default: false }
});

const Product = mongoose.model('product', productSchema);

const test = new Product({
  photos:
  {
    photo1: '',
    photo2: ''
  },
  name: 'LANCHE CAIXA LIVRO',
  type: 'cesta',
  related_products:
    ['58d953db02cbeb1000c2252c',
      '5a9704cfc85a33130048d03b'],
  addons:
    ['58c98ff147d68f1000dc0aa4',
      '59cb9de1d554581100b6a63d'],
  featured: false,
  active: false,
  slug: 'lanche-caixa-livro',
  code: '2475',
  inventory: null,
  descricao_avulsa: '',
  price: 7900,
  discount: null,
  category: '583f0ec84feeca1000835804',
  description:
    [{
      _id: '5b490101bad42c1a2c93e074',
      item: '59c4325720a98825003bc078',
      quantity: null
    },
    {
      _id: '5b490101bad42c1a2c93e075',
      item: '59c52510a9685111001c2fa1',
      quantity: 1
    },
    {
      _id: '5b490101bad42c1a2c93e07c',
      item: '58eb8d51147a3b1a000e0d5d',
      quantity: null
    }]
});

var formatDescription = function (product) {
  product.description = [];
  if (product.type == 'cesta') {
    var id;
    for (var key in product) {
      if (key.indexOf('product-') !== -1) {
        id = key.split('product-')[1];
        product.description.push({
          item: mongoose.Types.ObjectId(id),
          quantity: product[key]
        });
        delete product[key];
      }
    }
  }

  return product;
};

const mockBody = {
  idProduct: '5b4984e921efa02d5658504e',
  type: 'cesta',
  name: 'psodkapo',
  code: '123',
  inventory: '1',
  description:
    ['5849bbdc57d6331000bd3781',
      '5849bc4f57d6331000bd3785',
      '587e6bc21f56ca24009076c6'],
  'product-587e6bc21f56ca24009076c6': '1',
  'product-5849bc4f57d6331000bd3785': '2',
  'product-5849bbdc57d6331000bd3781': '1',
  descricao_avulsa: '',
  price: '8600',
  discount: '',
  category: '56a123c07d21baf91382035b',
  related_products: '58c9896947d68f1000dc0a5c',
  addons: '58e4e6d298a41810000865bb',
  active: true // had to change this ( 'on' can't be cast to a boolean )
};

async function run() {
  console.log(mongoose.version);
  await conn.dropDatabase();
  await test.save();

  let saved = await Product.findOne()
    .then((product) => {
      _.extend(product, mockBody);
      return product;
    })
    .then((extendedProduct)=> {
      return formatDescription(extendedProduct);
    })
    .then((formattedProduct) => {
      return formattedProduct.save();
    });

  console.log(saved);
  return conn.close();
}

run().catch(console.error);

Output on 5.2.3

issues: ./6723.js
5.2.3
/Users/lineus/dev/Help/mongoose5/node_modules/mongoose/lib/types/documentarray.js:336
          _arr[i].emit(event, val);
                  ^

TypeError: _arr[i].emit is not a function
    at EventEmitter.notify (/Users/lineus/dev/Help/mongoose5/node_modules/mongoose/lib/types/documentarray.js:336:19)
    at emitOne (events.js:121:20)
    at EventEmitter.emit (events.js:211:7)
    at model.Document.(anonymous function) [as emit] (/Users/lineus/dev/Help/mongoose5/node_modules/mongoose/lib/document.js:146:42)
    at model.Model.$__handleSave (/Users/lineus/dev/Help/mongoose5/node_modules/mongoose/lib/model.js:236:10)
    at model.Model.$__save (/Users/lineus/dev/Help/mongoose5/node_modules/mongoose/lib/model.js:246:8)
    at /Users/lineus/dev/Help/mongoose5/node_modules/kareem/index.js:278:20
    at _next (/Users/lineus/dev/Help/mongoose5/node_modules/kareem/index.js:102:16)
    at process.nextTick (/Users/lineus/dev/Help/mongoose5/node_modules/kareem/index.js:452:38)
    at _combinedTickCallback (internal/process/next_tick.js:131:7)
    at process._tickCallback (internal/process/next_tick.js:180:9)
issues:

The description in the req.body ( mockBody in my example ) is an array of strings, which mongoose will never be able to cast in the shape of the path in the schema ( nor could it if it was actually an array of objectIds ).

Deleting description from req.body ( mockBody ) gets around the issue:

    .then((product) => {
      delete mockBody.description;
      _.extend(product, mockBody);
      return product;
    })

Output becomes:

issues: ./6723.js
5.2.3
{ photos: { photo1: '', photo2: '' },
  name: 'psodkapo',
  type: 'cesta',
  related_products: [ 58c9896947d68f1000dc0a5c ],
  addons: [ 58e4e6d298a41810000865bb ],
  featured: false,
  active: true,
  _id: 5b4b8cd0f58a5c33cbf22784,
  slug: 'lanche-caixa-livro',
  code: '123',
  inventory: 1,
  descricao_avulsa: '',
  price: 8600,
  discount: null,
  category: 56a123c07d21baf91382035b,
  description:
   [ { _id: 5b4b8cd0f58a5c33cbf22788,
       item: 587e6bc21f56ca24009076c6,
       quantity: 1 },
     { _id: 5b4b8cd0f58a5c33cbf22789,
       item: 5849bc4f57d6331000bd3785,
       quantity: 2 },
     { _id: 5b4b8cd0f58a5c33cbf2278a,
       item: 5849bbdc57d6331000bd3781,
       quantity: 1 } ],
  __v: 1 }
issues:

@iagowp I think it makes more sense to run formatDescription on the request object before extending the mongoose document.

if you call formatDescription on the request body instead:

async function run() {
  console.log(mongoose.version);
  await conn.dropDatabase();
  await test.save();

  let saved = await Product.findOne()
    .then((product) => {
      mockBody = formatDescription(mockBody);
      _.extend(product, mockBody);
      return product.save();
    });

  console.log(saved);
  return conn.close();
}

The Output becomes:

issues: ./6723.js
5.2.3
{ photos: { photo1: '', photo2: '' },
  name: 'psodkapo',
  type: 'cesta',
  related_products: [ 58c9896947d68f1000dc0a5c ],
  addons: [ 58e4e6d298a41810000865bb ],
  featured: false,
  active: true,
  _id: 5b4b94d47e5c7635a4c3676f,
  slug: 'lanche-caixa-livro',
  code: '123',
  inventory: 1,
  descricao_avulsa: '',
  price: 8600,
  discount: null,
  category: 56a123c07d21baf91382035b,
  description:
   [ { _id: 5b4b94d57e5c7635a4c36775,
       item: 587e6bc21f56ca24009076c6,
       quantity: 1 },
     { _id: 5b4b94d57e5c7635a4c36774,
       item: 5849bc4f57d6331000bd3785,
       quantity: 2 },
     { _id: 5b4b94d57e5c7635a4c36773,
       item: 5849bbdc57d6331000bd3781,
       quantity: 1 } ],
  __v: 1 }
issues:

all of that being said: I think there is something weird going here. I'll come up with a more reasonably sized repro script to demonstrate it.

@vkarpov15 vkarpov15 added this to the 5.2.4 milestone Jul 15, 2018
@iagowp
Copy link
Contributor Author

iagowp commented Jul 15, 2018

About the active: 'on' to true, yes, that was a change I had to implement in other places. Thanks for taking the time for helping me out, after I did as suggested and ran formatDescription before extending, it gave me an error I can work with and fix

{ ValidationError: Product validation failed: description: Cast to Array failed for value "[ 
  '5849bbdc57d6331000bd3781',
  '5849bc4f57d6331000bd3785',
  '587e6bc21f56ca24009076c6' ]" at path "description", active: Cast to Boolean failed for value "on" at path "active"

If you need some help to dig deeper in this, feel free to mention me. But my problem is solved, thank you.

@vkarpov15 vkarpov15 modified the milestones: 5.2.4, 5.2.5 Jul 16, 2018
vkarpov15 added a commit that referenced this issue Jul 18, 2018
@vkarpov15
Copy link
Collaborator

Weird bug but we found the root cause. Looks like when there was a cast error in a document array, we didn't clean up some event listeners that the document array registered on the document. Fix will be in 5.2.5 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants