Multiple one-to-many relationship on mongoose - node.js

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' });

Related

Right way to store Schemas on Mongoose?

I started learning some NodeJS, and how to make a REST API from Academind on YouTube, and learned what a relational and non-relational database is, etc.
With MongoDB, writes are rather cheap, so I want to minimize the number of reads that I do. At the moment I am trying to see how I could make an API, that will be for an app that's similar to discord's, although it'll be for fun.
Is this the right way to make a Schema?
const mongoose = require('mongoose')
const userSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true, unique: true},
email: { type: String, required: true },
password: { type: String, required: true }, // TODO: Hashing, etc
guilds: [{
_id: mongoose.Schema.Types.ObjectId,
name: {type: String, required: true},
channels: [{
_id: mongoose.Schema.Types.ObjectId,
name: {type: String, required: true},
// Only the X most recent messages
messages: [{
_id: mongoose.Schema.Types.ObjectId,
message: {type: String, required: true},
user: {
_id: mongoose.Schema.Types.ObjectId,
name: {type: String, required: true}
}
}]
}],
// Only an X amount of users
users: [{
_id: mongoose.Schema.Types.ObjectId,
name: {type: String, required: true}
}]
}]
})
module.exports = mongoose.model('User', userSchema)
And then for the Guilds,
const mongoose = require('mongoose')
const guildSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: {type: String, required: true},
channels: [{
_id: mongoose.Schema.Types.ObjectId,
name: {type: String, required: true},
// Only an X amount of messages
messages: [{
_id: mongoose.Schema.Types.ObjectId,
message: {type: String, required: true},
user: {
_id: mongoose.Schema.Types.ObjectId,
name: {type: String, required: true}
}
}]
}],
// All the users
users: [{
_id: mongoose.Schema.Types.ObjectId,
name: {type: String, required: true}
}]
})
module.exports = mongoose.model('Guild', guildSchema)
Channel Schema
const mongoose = require('mongoose')
const channelSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true },
guild: {
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true },
channels: [{
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true }
}],
// The users of the guild, or just the channel?
// Could add a users object outisde of the guild object
users: [{
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true }
}]
}
})
module.exports = mongoose.model('Channel', channelSchema)
And finally for the messages
const mongoose = require('mongoose')
const messageSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
user: {
_id: mongoose.Schema.Types.ObjectId,
name: {type: String, required: true}
},
message: {type: String, required: true},
channel: {
guild: {
_id: mongoose.Schema.Types.ObjectId,
name: {type: String, required: true}
// Store more data for each message?
}
}
})
module.exports = mongoose.model('Message', messageSchema)
I am not sure if this is how a non-relational schema should look like. If it's not, how would I go about to store the data that I need?
Also, let's say that I POST a message on channel X on guild Y with the users A B and C, how would I go about to update all the entries, to add a message?
I've only used the User.find({_id: id}).exec().then().catch() so far, so I am not sure how to go about to update them.
Thanks in advance!
The messages collection should be on its own, do not embed it into any collection. This is not a good idea to embed data that will grow without limit.
The idea to store the last 5 messages into other collection looks painful to implement.
Embed denormalised data from all collections into the users collection seems like a problem when you will have to update guilds, channels, guilds users.
You may embed channels into guilds. Channels would not grow without a limit, should be a reasonable amount, less than 100 of channels per guild and probably it always used with a guild that they belong to. If not, consider not to embed channels into guilds.
The power of mongodb is to build the schema that reflects how your app is using data. I would recommend starting with normalized data. And when problems with creating, reading, updating, deleting data will occur then make appropriate changes in your mongoose schema to solve the problem. Premature optimization will only hurts in the long run.
As always an answer depends on details. Since I do not know all details I would recommend three part article by William Zola, Lead Technical Support Engineer at MongoDB. part 1 part 2 part 3

Mongoose remove document & the documents referred

I have written an app in nodejs + mongodb, in my app I have two models, both relate and what I need it is that when I delete a document from mongo automatically delete documents that are referred to within it ...
User model:
var userSchema = new mongoose.Schema({
'email': {type: String, unique: true},
'password': String,
'name': String,
'phone': String,
'photos': [ { type: mongoose.Schema.Types.ObjectId, ref: 'Photo' } ],
'createdAt': {type: Date, default: Date.now}
});
Photo Model:
var photoSchema = new mongoose.Schema({
'image': String,
'description': String,
'uploadedAt': {type: Date, default: Date.now},
'uploadedBy': {type: mongoose.Schema.Types.ObjectId, ref: 'User'}
});
I just need the simplest way for a user to be removed when all documents belonging to it model photos are deleted.
I'm looking for a simpler or easier way, as currently would:
UserModel.findByIdAndRemove(req.params.id, function(err, response) {
PhotoModel.find({'uploadedBy': req.params.id}).remove().exec();
});

MongoDB reference using Mongoose

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.

Mongoose reference between attributes

I have some Schemas in mongoose as follows:
var tournamentSchema = new Schema({
name: {type: String},
teams: [teamSchema],
poules: [pouleSchema],
createdAt: {type: Date, default: Date.now}
})
var pouleSchema = new Schema({
teams: [teamSchema],
createdAt: {type: Date, default: Date.now}
})
var teamSchema = new Schema({
name: {type: String},
createdAt: {type: Date, default: Date.now}
})
I will push some teams in my tournamentSchema. When ready, I want to create the poules with the teams.
Is it possible to make a relation between the 'tournamentSchema.teams' and the 'tournamentSchema.poules'? I think it is not that difficult to push teams in the poules attribute, but when I want to change a name of a team, I want to change it in the poules also. Should I do this manually or is there some relation possible?
Or is this only possible with different Models?
Thank you in advance,
Ronald.
I think mongoose population can help you. Try something like this:
var tournamentSchema = new Schema({
name: {type: String},
teams: [teamSchema],
poules: [pouleSchema],
createdAt: {type: Date, default: Date.now}
})
var pouleSchema = new Schema({
teams: [teamSchema],
createdAt: {type: Date, default: Date.now}
})
var teamSchema = new Schema({
team: {
type: mongoose.Schema.Types.ObjectId,
ref: 'teamsSchema'
}
})
var teamsSchema = new Schema({
name: {type: String},
createdAt: {type: Date, default: Date.now}
})
So every object in your teams array referencing to ObjectId in teamsSchema, and when you change name of team it will automatically affects on teams arrays. Hope it will help you.

How to reference another schema in my Mongoose schema?

I'm building a Mongoose schema for a dating app.
I want each person document to contain a reference to all the events they've been to, where events is another schema with its own models in the system. How can I describe this in the schema?
var personSchema = mongoose.Schema({
firstname: String,
lastname: String,
email: String,
gender: {type: String, enum: ["Male", "Female"]}
dob: Date,
city: String,
interests: [interestsSchema],
eventsAttended: ???
});
You can do so by using Population
Population is the process of automatically replacing the specified
paths in the document with document(s) from other collection(s). We
may populate a single document, multiple documents, plain object,
multiple plain objects, or all objects returned from a query.
Suppose your Event Schema is defined as follows:
var mongoose = require('mongoose')
, Schema = mongoose.Schema
var eventSchema = Schema({
title : String,
location : String,
startDate : Date,
endDate : Date
});
var personSchema = Schema({
firstname: String,
lastname: String,
email: String,
gender: {type: String, enum: ["Male", "Female"]}
dob: Date,
city: String,
interests: [interestsSchema],
eventsAttended: [{ type: Schema.Types.ObjectId, ref: 'Event' }]
});
var Event = mongoose.model('Event', eventSchema);
var Person = mongoose.model('Person', personSchema);
To show how populate is used, first create a person object,
aaron = new Person({firstname: 'Aaron'})
and an event object,
event1 = new Event({title: 'Hackathon', location: 'foo'}):
aaron.eventsAttended.push(event1);
aaron.save(callback);
Then, when you make your query, you can populate references like this:
Person
.findOne({ firstname: 'Aaron' })
.populate('eventsAttended') // only works if we pushed refs to person.eventsAttended
.exec(function(err, person) {
if (err) return handleError(err);
console.log(person);
});
To reference the ObjectId of one table in another table refer below code
const mongoose = require('mongoose'),
Schema=mongoose.Schema;
const otpSchema = new mongoose.Schema({
otpNumber:{
type: String,
required: true,
minlength: 6,
maxlength: 6
},
user:{
type: Schema.Types.ObjectId,
ref: 'User'
}
});
const Otp = mongoose.model('Otp',otpSchema);
// Joi Schema For Otp
function validateOtp(otp) {
const schema = Joi.object({
otpNumber: Joi.string().max(6).required(),
userId: Joi.objectId(), // to validate objectId we used 'joi-objectid' npm package
motive: Joi.string().required(),
isUsed: Joi.boolean().required(),
expiresAt: Joi.Date().required()
});
// async validate function for otp
return schema.validateAsync(otp);
}
exports.Otp = Otp;
exports.validateOtp = validateOtp;
List item
var personSchema = mongoose.Schema({
firstname: String,
lastname: String,
email: String,
gender: {
type: String,
enum: ["Male", "Female"]
}
dob: Date,
city: String,
interests: [interestsSchema],
eventsAttended[{
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: "Place"
}],
**//ref:"Places"...you have put the other model name**
*OR*
eventsAttended[{
type: mongoose.Types.ObjectId,
required: true,
ref: "Place"
}],
});

Resources