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;
@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:
#!/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();
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:
#!/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();
issues: ./6879.js
All assertions PASSED!
issues:
@lineus , simply awesome man!
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
Output: