MongoDB reference using Mongoose - node.js

I'm trying to reference one model to other with Mongoose doing this:
//product model
var productSchema = new mongoose.Schema({
id: {type: Number, unique: true, required: 'product_id'},
sku: {type: String, unique: true, required: 'product_sku'},
name: {type: String, required: 'product_name'},
short_description: {type: String},
details: [{
type: String
}],
categories: [{
type: String
}, {min: 1, max: 5}],
});
module.exports = mongoose.model('products', productSchema);
//order model
var orderSchema = new mongoose.Schema({
id: {type: String, unique: true},
date: {type: Date, default: Date.now},
products_sold: {type: mongoose.Schema.Types.ObjectId, ref: 'products'},
});
module.exports = mongoose.model('orders', orderSchema);
I've already created a product with id: 1:
"_id": ObjectId('55a1ce54b058d142051ca61d'),
"id": 1,
"sku": "p0001",
"name": "test1",
"short_description": "test",
//etc
If I do:
var newOrder = new Order({
id: 'order001',
products_sold: 1,
//etc
});
newOrder.save(function(err, data) {
if(err){
console.log(err);
}else{
console.log(data);
}
});
It fails with error:
message: 'orders validation failed',
name: 'ValidationError',
errors:
{ products_sold:
{ [CastError: Cast to ObjectID failed for value "1" at path "products_sold"]
How can I have products_sold in my order filled with product data that has id 1?
Thanks in advance

You are specifying that the products_sold field is of type ObjectId, but you are trying to set a Number as its value. In order to store references to documents in other collections, you must store the _id of the referenced document, not an arbitrary id field that you have in the Schema. You will either need to change the type for _id in your products database to use integers (and then manually specify what that _id field's value is when creating a new product), or you need to store the product's _id value in the order document's product_sold field.
Response to comment:
Here is updated code for specifying your own unique _id field:
Product Model
var productSchema = new mongoose.Schema({
_id: {type: Number, unique: true, required: 'product_id'},
sku: {type: String, unique: true, required: 'product_sku'},
name: {type: String, required: 'product_name'},
short_description: {type: String},
details: [String],
categories: [{
type: String
min: 1,
max: 5
}],
}, {_id: false});
module.exports = mongoose.model('products', productSchema);
In the productSchema, you will see that there is an _id specified, and it is set to required, and has no default value. This should make it so that Mongo will use whatever Number you specify as it's _id.

You need to pass objectId of the product in product_sold , rather than number.

Related

Pivot table for Mongoose Schema ObjectID

I have the following List schema:
const ListSchema = mongoose.Schema({
title: { type: String, required: true, max: 100 },
items: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Item'
}],
});
Everything works fine when I populate it with items. However, I want to have an additional column for each item in the list so I changed the schema:
const ListSchema = mongoose.Schema({
title: { type: String, required: true, max: 100 },
items: [{
item: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Item'
},
quantity: 'String'
}],
});
The populate method doesn't work with the above approach, unfortunately.
For a relational database I'd use a pivot table to save list id, item id and quantity but I really don't know how MongoDB treats such cases. Any suggestions are welcomed.
I figured it out.
const ListSchema = mongoose.Schema({
title: { type: String, required: true, max: 100 },
items: [{
type: mongoose.Schema.Types.Mixed, ref: 'Item', quantity: 'String'
}],
});
Basically I've changed the item's schema type from mongoose.Schema.Types.ObjectId to mongoose.Schema.Types.Mixed.
This way when you .populate('items._id') you'll get everything from items document AND your additional column (quantity)

Mongoose: I need subdocuments to generate "_id" instead of "id" to search through array of documents

I'm following along with the book "Getting MEAN" by Simon Holmes, however, I have stumbled on an issue regarding subdocument ids. I am trying to request a "review document" inside a "Location document" using the "MongooseDocumentArray.id(id) helper function provided by the mongoose docs. However, all my subdocuments generate "id" but the previously mentioned function requires "_id" instead thus returning "null" object.
Function description: Searches array items for the first document with a matching _id.
What is the issue here? My parent document being "Location" generates its id with "_id", which baffles me even more... The nested schema setup, the returned location object from a database containing the review sub documents and the controller function for the route doing the querying are illustrated in following code snippets respectively.
var mongoose = require("mongoose");
var reviewSchema = new mongoose.Schema({
author: String,
rating: {type: Number, required: true, min: 0, max: 5},
reviewText: String,
createdOn: {type: Date, "default": Date.now}
});
var locationSchema = new mongoose.Schema({
name: {type: String, required: true},
address: String,
rating: {type: Number, "default": 0, min: 0, max: 5},
facilities: [String],
coords: {type: [Number], index: "2dsphere"},
reviews: [reviewSchema]
});
mongoose.model('Location', locationSchema);
Returned Location object showing subdocuments containing id instead of _id
For the following snippet, this is the relevant route:
router.get("/locations/:locationid/reviews/:reviewid", ctrlReviews.reviewsReadOne);
module.exports.reviewsReadOne = function(req, res){
if(req.params && req.params.locationid && req.params.reviewid){
Loc.findById(req.params.locationid)
.select("name reviews")
.exec(function(err, location) {
var response, review;
if(location.reviews && location.reviews.length > 0){
// This returns zero, because the function requires _id
// but all subdocuments have path/properties called id
review = location.reviews.id(req.params.reviewid);
console.log(review);
if(!review){
sendJsonResponse(res, 404, {
"message": "reviewid not found"
});
} else {
response = {
location: {
name: location.name,
id : req.params.locationid
},
review: review
};
sendJsonResponse(res, 200, response);
}
}
})
}
};
Change your schema to the following.
var locationSchema = new mongoose.Schema({
name: {type: String, required: true},
address: String,
rating: {type: Number, "default": 0, min: 0, max: 5},
facilities: [String],
coords: {type: [Number], index: "2dsphere"},
reviews: [{
author: String,
rating: {type: Number, required: true, min: 0, max: 5},
reviewText: String,
createdOn: {type: Date, "default": Date.now
}]
});
Actually I'm reading this book, the book is awesome, give a lot of information about a simple and optimize architecture for a MEAN stack, the problem was in the code in the book when use:
$push:{reviews:{author: 'Simon Holmes',id:Objectid(),...
in this code use id instead of _id, so the correct script is:
db.locations.update({name:'Starcups'},{$push:{reviews:{author: 'Simon Holmes',_id:ObjectId(),rating:5,timestamp:new Date("Jul 16,2013"),reviewText:"What a great place. I can't say enougn good about it"}}});
"When adding a new sub-doc through the Mongo shell you need to specify the id as _id. " (Holmes S.)
https://forums.manning.com/posts/list/35152.page

How to name a foreign key different from db name in mongoose

Is there a way in mongoose + Node.js/Express to define the relation between the foreign key field and what I refer to that field in the model is? My issue is that I have a mongo database where my foreign keys are all formatted like 'exampleId' instead of 'example'. I could just call out 'exampleId' directly but then it leads to weird things like when I populate 'exampleId' instead of 'example' (which is confusing because once populated, it is now the 'example' itself instead of its id).
Here is how I do it now and it works with my graphQL server, but only if my field in the database is 'course' while my database's field is 'courseId'
const CourseSchema = new Schema({
_id: { type: String },
sections: [{
type: Schema.Types.String,
ref: 'Section'
}],
});
const SectionType = new GraphQLObjectType({
name: 'SectionType',
fields: () => ({
id: { type: GraphQLID },
courseId: {
type: require('./course_type'),
resolve(parentValue) {
return Section.findById(parentValue)
.populate('course')
.then(section => section.course);
}
},
}),
});
I figured it out! With the newest version of mongoose, you actually can use virtual fields to accomplish what I wanted to do and this technique allows for flexibility in laying out your schema. Say that my MongoDB collections look like the following:
Courses { _id, sectionIds }
Lectures { _id, courseId }
I can use the following schema in mongoose and it will allow me to refer to course.lectures or lecture.course instead of the usual course.lectureIds or section.courseId:
const CourseSchema = new Schema({
_id: { type: String },
});
CourseSchema.virtual('sections', {
type: Schema.Types.String,
ref: 'Section',
localField: 'sectionIds',
foreignField: '_id',
justOne: false,
});
CourseSchema.statics.findSections = function(id) {
return this.findById(id)
.populate('sections')
.then(course => course.sections);
}
const SectionSchema = new Schema({
_id: { type: String },
});
SectionSchema.virtual('course', {
type: Schema.Types.String,
ref: 'Course',
localField: 'courseId',
foreignField: '_id',
justOne: true,
});
Actually MongoDB isn't a relational database. You can alter the field and its name whatever you like. Ex I Have an Owner(Meteor.users) table and Patient Table with this column
ownerid : {type: String, min: 1},
firstname: {type: String, min: 1},
lastname: {type: String, min: 1},
middlename: {type: String, min: 1, optional: true},
createdbyid: { type: String },
createdbyname: { type: String },
createdat: { type: Date, defaultValue: new Date() },
updatedbyid: { type: String, optional: true },
updatedbyname : { type: String, optional: true },
updatedat: { type: Date, defaultValue: new Date() },
I can easily stamp the value of my {Meteor.Users()._id} to ownerid of my designated patient by just processing them at meteor.methods. You don't have to worry about foreign keys mongo doesn't do relational databases you can customize your database whatever you like. I Hope this helps ;)
Mongoose Documentation posits that _id has to be used in refs and that[i]t is important to match the type of _id to the type of ref. , e.g.:
var personSchema = Schema({
_id : Number, //it is `Number`
name : String,
age : Number,
stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
var storySchema = Schema({
_creator : { type: Number, ref: 'Person' },
title : String,
fans : [{ type: Number, ref: 'Person' }] // use `Number` to match
});
I also wonder if by 'database' you mean 'collection'.

Multiple one-to-many relationship on mongoose

I'm using mongoose to store three models of documents, sometimes I have to update references between then, for this I'm using mongoose-relationship plugin,
My need is reference then like this:
One customer have many schedules,
One costumer have many orders,
One order have many schedules
When I create an order I need to push schedules id's into order to reference then. But I can only reference one childPath per collection, my models are mapped like this;
Customers:
var CustomerSchema = new Schema({
name: {type: String, required: true},
email: {type: String, required: true},
shedules: [{ type:mongoose.Schema.Types.ObjectId, ref:"Schedule" }],
orders: [{ type:mongoose.Schema.Types.ObjectId, ref:"Order" }]
}
Schedules:
var ScheduleSchema = new Schema({
customer: {type:mongoose.Schema.Types.ObjectId, ref:"Customer", childPath:"shedules"}, //shedule id
order: {type:mongoose.Schema.Types.ObjectId, ref:"Order", childPath:"shedules"}, //order Id
sequence: {type: Number, default: 0},
creation_date: {type: Date, default: Date.now}
}
SheduleSchema.plugin(relationship, {relationshipPathName:['customer','order']});
Orders:
var OrderSchema = new Schema({
customer: {type:mongoose.Schema.Types.ObjectId, ref:"Customer", childPath:"order"},
shedules: [{type:mongoose.Schema.Types.ObjectId, ref:"Shedule" }],// <-- this field doesn't update.
price: {type: Number, default: 0}
}
OrderSchema.plugin(relationship, { relationshipPathName:'customer' });

mongoose populate field without ref option

I have 3 schema:
var User1Schema = new Schema({
name: {type:String , trim: true, required: true },
status: {type:Number, required: true, default:1}
},{collection:"user_1"});
,
var User2Schema = new Schema({
name: {type:String , trim: true, required: true },
email: {type: String, required: true, index: {unique: true} },
status: {type:Number, required: true, default:1}
},{collection:"user_2"});
and
var ConversationSchema = new Schema( {
participants: [{user:{type: ObjectId, required: true}],
creator: {type: ObjectId, required: true},
status: { type:Number, required: true, default:1 }
}, { collection:"conversation" } );
In ConversationSchema I have creator field whic has now ref option, because it can be type of User1Schema or User2Schema.
How can I populate creator field using mongoose
Conversation.find().populate('creator', null, 'User2').exec(callback)
The docs aren't clear. Will update soon.
Also, the syntax for this will be simpler in v3.6.
As of 27th March 2019 the way to do so is:
1) Setting the field you want to populate as a ObjectId without providing a ref.
var eventSchema = new Schema({
name: String,
// The id of the corresponding conversation
// Notice there's no ref here!
conversation: ObjectId
});
2) While making the query pass the model for the schema along with the query:
Event.
find().
populate({ path: 'conversation', model: Conversation }).
exec(function(error, docs) { /* ... */ });
REF: https://mongoosejs.com/docs/populate.html#cross-db-populate

Resources