Mongoose: Cast to ObjectId failed for value \"aggregate\" at path \"_id\" for model Payment\"

Created on 17 Aug 2018  路  6Comments  路  Source: Automattic/mongoose

Do you want to request a feature or report a bug?
bug

What is the current behavior?
Cast to ObjectId failed for value emitting

If the current behavior is a bug, please provide the steps to reproduce.

What is the expected behavior?
It should accept the ref key as String

Please mention your node.js, mongoose and MongoDB version.
NODE: 10.0.0,
"mongoose": "^5.0.0-rc0"
v4.0.0

let mongoose = require("mongoose");
let Schema = mongoose.Schema;
let ObjectId = Schema.ObjectId;

let modelSchema = new Schema(
  {
    userId: {type: ObjectId},
    name: { type: String },
    amount: {
        type: Number
    },
    categoryId: {
        type: String,
        ref: 'Category'
    },
    cardId: {
      type: String,
      ref: 'Card'
    },
    paymentDate: {
      type: Date,
      default: new Date
    }
  },
  {
    timestamps: {}
  }
);



let modelObj = mongoose.model("Payment", modelSchema);
module.exports = modelObj;

Most helpful comment

@techyaura you can create a virtual on paymentSchema that defines specific local/foreign keys.

Here is the example from earlier updated with virtuals instead:

6879.js

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

const assert = require('assert');
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/test', { useNewUrlParser: true });
const conn = mongoose.connection;
const Schema = mongoose.Schema;

let paymentSchema = new Schema(
  {
    userId: { type: Schema.Types.ObjectId },
    name: { type: String },
    amount: {
      type: Number
    },
    categoryId: String,
    cardId: String,
    paymentDate: {
      type: Date,
      default: new Date
    }
  },
  {
    timestamps: {}
  }
);

paymentSchema.virtual('card', {
  ref: 'Card',
  localField: 'cardId',
  foreignField: 'slug',
  justOne: true
});

paymentSchema.virtual('category', {
  ref: 'Category',
  localField: 'categoryId',
  foreignField: 'slug',
  justOne: true
});

const cardSchema = new Schema(
  {
    userId: { type: Schema.Types.ObjectId },
    name: { type: String, unique: true },
    slug: { type: String, unique: true },
    isSystemDefined: {
      type: Boolean,
      default: false
    },
    status: {
      type: Boolean,
      default: false
    },
    isDeleted: {
      type: Boolean,
      default: false
    }
  },
  {
    timestamps: {}
  }
);

const categorySchema = new Schema(
  {
    userId: {
      type: Schema.Types.ObjectId
    },
    name: { type: String, unique: true },
    slug: { type: String, unique: true },
    status: {
      type: Boolean,
      default: false
    },
    isDeleted: {
      type: Boolean,
      default: false
    },
    isSystemDefined: {
      type: Boolean,
      default: false
    }
  },
  {
    timestamps: {}
  }
);

categorySchema.set('toObject', { virtuals: true });
categorySchema.set('toJSON', { virtuals: true });

const Category = mongoose.model('Category', categorySchema);
const Card = mongoose.model('Card', cardSchema);
const Payment = mongoose.model('Payment', paymentSchema);

const uid = new mongoose.Types.ObjectId();

const category = new Category({
  userId: uid,
  name: 'billy',
  slug: 'bugsAreCool'
});

const card = new Card({
  userId: uid,
  name: 'MyCard',
  slug: 'getTheSalt'
});

const payment = new Payment({
  userId: uid,
  name: 'salt',
  amount: 100,
  categoryId: category.slug,
  cardId: card.slug
});

async function run() {
  await conn.dropDatabase();
  await category.save();
  await card.save();
  await payment.save();
  let doc = await Payment.findOne({}).populate('card category');
  assert.strictEqual(doc.category.name, 'billy');
  assert.strictEqual(doc.card.name, 'MyCard');
  console.log('All assertions PASSED!');
  return conn.close();
}

run();

Output:

issues: ./6879.js
All assertions PASSED!
issues:

All 6 comments

@techyaura can you please include all of the relevant schema ('Category' and 'Card') as well as the query with populate.

Sure @lineus.

const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const ObjectId = Schema.ObjectId;

const modelSchema = new Schema(
  {
    userId: {type: ObjectId},
    name: { type: String, unique: true },
    slug: { type: String, unique: true },
    isSystemDefined: {
      type: Boolean,
      default: false
    },
    status: {
      type: Boolean,
      default: false
    },
    isDeleted: {
      type: Boolean,
      default: false
    }
  },
  {
    timestamps: {}
  }
);

const modelObj = mongoose.model("Card", modelSchema);
module.exports = modelObj;
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const ObjectId = Schema.ObjectId;

const modelSchema = new Schema(
  {
    userId: {
      type: ObjectId
    },
    name: { type: String, unique: true },
    slug: { type: String, unique: true },
    status: {
      type: Boolean,
      default: false
    },
    isDeleted: {
      type: Boolean,
      default: false
    },
    isSystemDefined: {
      type: Boolean,
      default: false
    }
  },
  {
    timestamps: {}
  }
);

modelSchema.set("toObject", { virtuals: true });
modelSchema.set("toJSON", { virtuals: true });

const CategoryModel = mongoose.model("Category", modelSchema);
module.exports = CategoryModel;

@techyaura I'm assuming you are calling something like Payment.findOne().populate('categoryId').

Mongoose is going to use the Model pointed to by the value of ref and the _id field of the docs in the corresponding collection to match the value of the path in the document being populated.

Your _id fields from the 'Card' and 'Category' schema are using the default of ObjectId.
A string like 'aggregate' is never going to successfully be cast as an ObjectId.

You need to store the _id of the Card or Category document in that field. If you store the _id of the foreign doc, it is fine to use String as the type.

Here's a complete repro script based on your schema:

6879.js

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

const assert = require('assert');
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/test', { useNewUrlParser: true });
const conn = mongoose.connection;
const Schema = mongoose.Schema;

let paymentSchema = new Schema(
  {
    userId: { type: Schema.Types.ObjectId },
    name: { type: String },
    amount: {
      type: Number
    },
    categoryId: {
      type: String,
      ref: 'Category'
    },
    cardId: {
      type: String,
      ref: 'Card'
    },
    paymentDate: {
      type: Date,
      default: new Date
    }
  },
  {
    timestamps: {}
  }
);

const cardSchema = new Schema(
  {
    userId: { type: Schema.Types.ObjectId },
    name: { type: String, unique: true },
    slug: { type: String, unique: true },
    isSystemDefined: {
      type: Boolean,
      default: false
    },
    status: {
      type: Boolean,
      default: false
    },
    isDeleted: {
      type: Boolean,
      default: false
    }
  },
  {
    timestamps: {}
  }
);

const categorySchema = new Schema(
  {
    userId: {
      type: Schema.Types.ObjectId
    },
    name: { type: String, unique: true },
    slug: { type: String, unique: true },
    status: {
      type: Boolean,
      default: false
    },
    isDeleted: {
      type: Boolean,
      default: false
    },
    isSystemDefined: {
      type: Boolean,
      default: false
    }
  },
  {
    timestamps: {}
  }
);

categorySchema.set('toObject', { virtuals: true });
categorySchema.set('toJSON', { virtuals: true });

const Category = mongoose.model('Category', categorySchema);
const Card = mongoose.model('Card', cardSchema);
const Payment = mongoose.model('Payment', paymentSchema);

const uid = new mongoose.Types.ObjectId();

const category = new Category({
  userId: uid,
  name: 'billy',
  slug: 'bugsAreCool'
});

const card = new Card({
  userId: uid,
  name: 'MyCard',
  slug: 'getTheSalt'
});

const payment = new Payment({
  userId: uid,
  name: 'salt',
  amount: 100,
  categoryId: category.id,
  cardId: card.id
});

async function run() {
  await conn.dropDatabase();
  await category.save();
  await card.save();
  await payment.save();
  let doc = await Payment.findOne({}).populate('cardId categoryId');
  assert.strictEqual(doc.categoryId.name, 'billy');
  assert.strictEqual(doc.cardId.name, 'MyCard');
  console.log('All assertions PASSED!');
  return conn.close();
}

run();

Output:

issues: ./6879.js
All assertions PASSED!
issues:

Hi @lineus,

You are right, if I am using _id as ref Id then therer is no issue at all. Previously I was using _id as refId, everything was working fine. But recently, i got to change ref id as slug because of my present requirement. So is there any way that i can use any field as forienkey.

@techyaura you can create a virtual on paymentSchema that defines specific local/foreign keys.

Here is the example from earlier updated with virtuals instead:

6879.js

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

const assert = require('assert');
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/test', { useNewUrlParser: true });
const conn = mongoose.connection;
const Schema = mongoose.Schema;

let paymentSchema = new Schema(
  {
    userId: { type: Schema.Types.ObjectId },
    name: { type: String },
    amount: {
      type: Number
    },
    categoryId: String,
    cardId: String,
    paymentDate: {
      type: Date,
      default: new Date
    }
  },
  {
    timestamps: {}
  }
);

paymentSchema.virtual('card', {
  ref: 'Card',
  localField: 'cardId',
  foreignField: 'slug',
  justOne: true
});

paymentSchema.virtual('category', {
  ref: 'Category',
  localField: 'categoryId',
  foreignField: 'slug',
  justOne: true
});

const cardSchema = new Schema(
  {
    userId: { type: Schema.Types.ObjectId },
    name: { type: String, unique: true },
    slug: { type: String, unique: true },
    isSystemDefined: {
      type: Boolean,
      default: false
    },
    status: {
      type: Boolean,
      default: false
    },
    isDeleted: {
      type: Boolean,
      default: false
    }
  },
  {
    timestamps: {}
  }
);

const categorySchema = new Schema(
  {
    userId: {
      type: Schema.Types.ObjectId
    },
    name: { type: String, unique: true },
    slug: { type: String, unique: true },
    status: {
      type: Boolean,
      default: false
    },
    isDeleted: {
      type: Boolean,
      default: false
    },
    isSystemDefined: {
      type: Boolean,
      default: false
    }
  },
  {
    timestamps: {}
  }
);

categorySchema.set('toObject', { virtuals: true });
categorySchema.set('toJSON', { virtuals: true });

const Category = mongoose.model('Category', categorySchema);
const Card = mongoose.model('Card', cardSchema);
const Payment = mongoose.model('Payment', paymentSchema);

const uid = new mongoose.Types.ObjectId();

const category = new Category({
  userId: uid,
  name: 'billy',
  slug: 'bugsAreCool'
});

const card = new Card({
  userId: uid,
  name: 'MyCard',
  slug: 'getTheSalt'
});

const payment = new Payment({
  userId: uid,
  name: 'salt',
  amount: 100,
  categoryId: category.slug,
  cardId: card.slug
});

async function run() {
  await conn.dropDatabase();
  await category.save();
  await card.save();
  await payment.save();
  let doc = await Payment.findOne({}).populate('card category');
  assert.strictEqual(doc.category.name, 'billy');
  assert.strictEqual(doc.card.name, 'MyCard');
  console.log('All assertions PASSED!');
  return conn.close();
}

run();

Output:

issues: ./6879.js
All assertions PASSED!
issues:

@lineus , simply awesome man!

Was this page helpful?
0 / 5 - 0 ratings