I have an Assignment schema which has references to Groups and Projects.
Assignment == Group [One-One Relationship]
Assignment == Projects [One-Many Relationship]
Below is my Asssignment Schema
var AssignmentSchema = new Schema({
name: String,
group: {
type: Schema.Types.ObjectId,
ref: 'Group'
},
projects: [{type: mongoose.Schema.Types.ObjectId, ref: 'Project'}],
});
If a Group/Project is removed, how can i update my Assignment Schema.
var ProjectSchema = new Schema({
name: String
});
var GroupSchema = new Schema({
name: String
});
From couple of answers in stackoverflow, i came to know about the remove middleware, but i am not sure how to implement it for one-one and one-many relationship. Can anyone show me an example of doing it.
ProjectSchema.pre('remove', function(next){
this.model('Assignment').update(
);
});
Relationships:
A one-to-one is a relationship such that a state has only one
capital city and a capital city is the capital of only one state
A one-to-many is a relationship such that a mother has many
children, and the children have only one mother
A many-to-many is a relationship such that a book can be written by
several authors or co-authors, while an author can write several
books.
one-one relationship - If a Project/Group is removed, how can i update my Assignment Schema.
Typically you will have one project mapped to one assignment and similarly one assignment mapped to one project. what you can do here is removing a project and then find the associated project in assignment model and remove their references.
delete: function(req, res) {
return Project.findById(req.params.id, function(err, project){
return project.remove(function(err){
if(!err) {
Assignment.update({_id: project.assignment}},
{$pull: {projects: project._id}},
function (err, numberAffected) {
console.log(numberAffected);
} else {
console.log(err);
}
});
});
});
}
one-many relationship - If a Project/Group is removed, how can i update my Assignment Schema.
In this scenario we are removing a project and then finding all the assignments which belongs to this project and removing its reference from them. Here the situation is, there can be many assignments for a single project.
delete: function(req, res) {
return Project.findById(req.params.id, function(err, project){
return project.remove(function(err){
if(!err) {
Assignment.update({_id: {$in: project.assingments}},
{$pull: {project: project._id}},
function (err, numberAffected) {
console.log(numberAffected);
} else {
console.log(err);
}
});
});
});
}
Remove middleware
You could achieve the same thing via middleware as pointed out by Johnny, just a correction on that..
ProjectSchema.pre('remove', function (next) {
var project = this;
project.model('Assignment').update(
{ projects: {$in: project.assignments}},
{ $pull: { project: project._id } },
{ multi: true },
next
);
});
Typically there can be many projects belonging to an assignment and many assignments belonging to the same project. You will have an assignment column in your Project Schema where one project will relate to multiple assignments.
Note: remove middleware won't work on models and it would only work on your documents. If you are going with remove middleware ensure in your delete function, you find project by id first and then on the returned document apply the remove method, so for the above to work... your delete function would look like this.
delete: function(req, res) {
return Project.findById(req.params.id, function(err, project){
return project.remove(function(err){
if(!err) {
console.log(numberAffected);
}
});
});
}
In the remove middleware, you're defining the actions to take when a document of the model for that schema is removed via Model#remove. So:
When a group is removed, you want to remove the group reference to that group's _id from all assignment docs.
When a project is removed, you want to remove the projects array element references to that project's _id from all assignment docs.
Which you can implement as:
GroupSchema.pre('remove', function(next) {
var group = this;
group.model('Assignment').update(
{ group: group._id },
{ $unset: { group: 1 } },
{ multi: true },
next);
});
ProjectSchema.pre('remove', function (next) {
var project = this;
project.model('Assignment').update(
{ projects: project._id },
{ $pull: { projects: project._id } },
{ multi: true },
next);
});
Related
I have a Documents in a Collection that have a field that is an Array (foo). This is an Array of other subdocuments. I want to set the same field (bar) for each subdocument in each document to the same value. This value comes from a checkbox.
So..my client-side code is something like
'click #checkAll'(e, template) {
const target = e.target;
const checked = $(target).prop('checked');
//Call Server Method to update list of Docs
const docIds = getIds();
Meteor.call('updateAllSubDocs', docIds, checked);
}
I tried using https://docs.mongodb.com/manual/reference/operator/update/positional-all/#positional-update-all
And came up with the following for my Server helper method.
'updateAllSubDocs'(ids, checked) {
Items.update({ _id: { $in: ids } }, { $set: { "foo.$[].bar": bar } },
{ multi: true }, function (err, result) {
if (err) {
throw new Meteor.Error('error updating');
}
});
}
But that throws an error 'foo.$[].bar is not allowed by the Schema'. Any ideas?
I'm using SimpleSchema for both the parent and subdocument
Thanks!
Try passing an option to bypass Simple Schema. It might be lacking support for this (somewhat) newer Mongo feature.
bypassCollection2
Example:
Items.update({ _id: { $in: ids } }, { $set: { "foo.$[].bar": bar } },
{ multi: true, bypassCollection2: true }, function (err, result) {
if (err) {
throw new Meteor.Error('error updating');
}
});
Old answer:
Since you say you need to make a unique update for each document it sounds like bulk updating is the way to go in this case. Here's an example of how to do this in Meteor.
if (docsToUpdate.length < 1) return
const bulk = MyCollection.rawCollection().initializeUnorderedBulkOp()
for (const myDoc of docsToUpdate) {
bulk.find({ _id: myDoc._id }).updateOne({ $set: update })
}
Promise.await(bulk.execute()) // or use regular await if you want...
Note we exit the function early if there's no docs because bulk.execute() throws an exception if there's no operations to process.
If your data have different data in the $set for each entry on array, I think you need a loop in server side.
Mongo has Bulk operations, but I don't know if you can call them using Collection.rawCollection().XXXXX
I've used rawCollection() to access aggregate and it works fine to me. Maybe work with bulk operations.
I'm attempting to eager load a belongs-to-many association where I am loading three nested associations. Here are the models, which result in three database tables programs, programDates and peopleProgramDates
program.js:
module.exports = function(sequelize, DataTypes) {
const Program = sequelize.define('program', {
name: DataTypes.STRING
});
Program.associate = ({programDate}) => {
Program.hasMany(programDate);
};
return Program;
};
program_date.js:
module.exports = function(sequelize, DataTypes) {
const ProgramDate = sequelize.define('programDate', {
date: DataTypes.DATEONLY,
volunteerLimit: DataTypes.INTEGER
}, {
indexes: [
{
unique: true,
fields: ['programId', 'date']
}
]
});
ProgramDate.associate = ({program, person}) => {
ProgramDate.belongsTo(program);
ProgramDate.belongsToMany(person, {through: 'peopleProgramDates'});
};
return ProgramDate;
};
In my controller, I want to return an object with all of the programs, programDates and peopleProgramDates:
const {bus, family, person, volunteerType, program, programDate} = require('../models');
exports.get = (request, response) => {
return Promise.all([
bus.findAll({ include: [{model: family, include: [person]}] })
.then(buses => buses.map(addBusCount)),
volunteerType.findAll({include: [person]})
.then(volunteerTypes => volunteerTypes.map(addVolunteerCount)),
// this query hangs the application
program.findAll( { include: [{ model: programDate, include: [{association: 'peopleProgramDates'}] }]} )
.then(programs => programs.map(processPrograms))
])
.then(([buses, volunteerTypes, programs]) =>
response.render('pages/register', {
buses,
volunteerTypes,
programs
})
);
};
At the moment, processPrograms() is a function that simply returns the same array of objects, and so should not be relevant here. addBusCount and addVolunteerCount should similarly not be relevant.
I think the issue may be that peopleProgram dates is not a real sequelize model, but the result of the the belongsToMany through: association on ProgramDate.
This post seems to suggest I can use the association: property in order to load the data from the through association, however the query hangs the application.
If I remove the join table from the query, then the data loads fine:
program.findAll( { include: [programDate] } )
Bonus points: Ultimately what I really need is simply a count of peopleProgramDates returned with the programDate objects. Perhaps I can simply define such on the programDates model, however perhaps we can address that in a separate question. Nevertheless, if there is a compelling reason to use this approach, such as performance, then maybe we should go that way after all.
The solution was to add an alias to the belongsToMany through association:
// program_date.js
ProgramDate.belongsToMany(person, {through: 'peopleProgramDates', as: 'peopleProgDates'});
And then reference the alias in the include property:
program.findAll( { include: [{ model: programDate, include: [{association: 'peopleProgDates'}] }]} )
I want to recreate the models in database after dropping everything in it.
Mongoose (or Mongo itself )actually recreates the documents but not the indices. So is there a way to reset Mongoose so that it can recreate indices as if running the first time?
The reason why I'm using dropDatabase is because it seems easier while testing. Otherwise I would have to remove all collections one by one.
While not recommended for production use, depending on your scenario, you can add the index property to a field definition to specify you want an index created:
var animalSchema = new Schema({
name: String,
type: String,
tags: { type: [String], index: true } // field level
});
animalSchema.index({ name: 1, type: -1 }); // schema level
Or,
var s = new Schema({ name: { type: String, sparse: true })
Schema.path('name').index({ sparse: true });
Or, you can call ensureIndex on the Model (docs):
Animal.ensureIndexes(function (err) {
if (err) return handleError(err);
});
Just a simple query, for example with a double ref in the model.
Schema / Model
var OrderSchema = new Schema({
user: {
type : Schema.Types.ObjectId,
ref : 'User',
required: true
},
meal: {
type : Schema.Types.ObjectId,
ref : 'Meal',
required: true
},
});
var OrderModel = db.model('Order', OrderSchema);
Query
OrderModel.find()
.populate('user') // works
.populate('meal') // dont works
.exec(function (err, results) {
// callback
});
I already tried something like
.populate('user meal')
.populate(['user', 'meal'])
In fact only one of the populates works.
So, how do is get two populates working ?
You're already using the correct syntax of:
OrderModel.find()
.populate('user')
.populate('meal')
.exec(function (err, results) {
// callback
});
Perhaps the meal ObjectId from the order isn't in the Meals collection?
UPDATE:
This solution remains for the version 3.x of Mongoose http://mongoosejs.com/docs/3.8.x/docs/populate.html but is no longer documented for >= 4.x versions of Mongoose and so the answer from #JohnnyHK is the only valid one for now on.
ORIGINAL POST
If you're using Mongoose >= 3.6, you can pass a space delimited string of the path names to populate:
OrderModel.find()
.populate('user meal')
.exec(function (err, results) {
// callback
});
http://mongoosejs.com/docs/populate.html
This has probably been resolved already, but this is my take on multiple & deep population in Mongodb > 3.6:
OrderModel.find().populate([{
path: 'user',
model: 'User'
}, {
path: 'meal',
model: 'Meal'
}]).exec(function(err, order) {
if(err) throw err;
if(order) {
// execute on order
console.log(order.user.username); // prints user's username
console.log(order.meal.value); // you get the idea
}
});
There are probably other ways to do this, but this makes very readable code for beginners (like me)
The best solution in my opinion is arrays when you are populating more than one foreign field on the same level. My code shows that I have multiple populates for different levels.
const patients = await Patient.find({})
.populate([{
path: 'files',
populate: {
path: 'authorizations',
model: 'Authorization'
},
populate: {
path: 'claims',
model: 'Claim',
options: {
sort: { startDate: 1 }
}
}
}, {
path: 'policies',
model: 'Policy',
populate: {
path: 'vobs',
populate: [{
path: 'benefits'
}, {
path: 'eligibility',
model: 'Eligibility'
}]
}
}]);
As you can see, wherever I needed more than one field of a document populated, I encased the populate key in an array and provided an array of objects, each object having a different path. Most robust and concise way to do it, in my opinion.
You can use array syntax:
let results = await OrderModel.find().populate(['user', 'meal']);
You can also select which properties you want from each populate:
let results = await OrderModel.find().populate([{path: 'user', select: 'firstname'}, {path: 'meal', select: 'name'}]);
Latest mongoose v5.9.15
has ability to take array of populate fields
so you can do,
.populate([ 'field1', 'field2' ])
You can try:
OrderModel.find()
.populate('user')
.populate('meal')
.exec(function (err, results) {
// callback
});
or with array options
OrderModel.find()
.populate([
{
path: "path1",
select: "field",
model: Model1
},
{
path: "path2",
select: "field2",
model: Model2
}
])
.exec(function (err, results) {
// callback
});
In model file do something like:-
doctorid:{
type:Schema.Types.ObjectId, ref:'doctor'
},
clinicid:{
type:Schema.Types.ObjectId, ref:'baseClinic'
}
In js file for adding operator use Something like:-
const clinicObj = await BaseClinic.findOne({clinicId:req.body.clinicid})
const doctorObj = await Doctor.findOne({ doctorId : req.body.doctorid}) ;
**and add data as:-**
const newOperator = new Operator({
clinicid:clinicObj._id,
doctorid: doctorObj._id
});
Now, while populating
apiRoutes.post("/operator-by-id", async (req, res) => {
const id = req.body.id;
const isExist = await Operator.find({ _id: id }).populate(['doctorid','clinicid'])
if (isExist.length > 0) {
res.send(isExist)
} else {
res.send("No operator found");
}
});
i have same problem , but my mistake not in populate , i have an error in Model
if you do this
uncorrected
user: {
type: [Schema.Types.ObjectId],
ref: 'User'
}
correct
user: [{
type: Schema.Types.ObjectId,
ref: 'User'
}]
you must put array around of object like this
To populate multiple fields with array of objects in controller/action function, model of both is already referred in schema of post
post.find({}).populate('user').populate('comments').exec(function (err,posts)
{
if(err)
{
console.log("error in post");
}
return res.render('home',{
h1:"home Page",
posts:posts,
});
});
I think you are trying to the nested population you can visit official docs
User.
findOne({ name: 'Val' }).
populate({
path: 'friends',
// Get friends of friends - populate the 'friends' array for every friend
populate: { path: 'friends' }
});
I have a basic document with a 'checked_in' flag in my express app:
module.exports = Book= mongoose.model('Book', new Schema({
name : String,
checked_in : Boolean
},{ collection : 'Book' }));
I wanted to keep a log of when books are checked in and out so I came up with another schema:
var action = new Schema({
checked_in: Boolean,
});
module.exports = Activity = mongoose.model('Activity', new Schema({
book_id: String,
actions: [action]
},{ collection : 'Activity' }));
The 'book_id' should be the document id of a book and when I update a book I need to either create or update the activity log for that book with a new item inside of actions:
exports.update = function(req, res){
return Book.findById(req.params.id, function(err, book) {
var activity = new Activity({book_id: book.id});
activity.actions.push({
checked_in: req.body.checked_in,
});
Activity.update({ book_id: book.id}, activity.toObject(), { upsert: true }));
book.checked_in = req.body.checked_in;
return device.save(function(err) {
return res.send(book);
});
});
};
The problem I am having is that nothing gets inserted into the Activity collection. If I use .save() then i just get lots of duplicates in the collection.
UPDATE
I've started re-working things with the advice given below but am still not having any luck with this. Here's what I have now:
module.exports = Activity = mongoose.model('Activity', new Schema({
book_id: Schema.ObjectId,
actions: [new Schema({
checked_in: Boolean,
last_user: String
})]
},{ collection : 'Activity' }));
Here's the update code now:
exports.update = function(req, res){
// TODO: Check for undefined.
return book.findById(req.params.id, function(err, book) {
if(!err) {
// Update the book.
book.checked_in = req.body.checked_in;
book.last_user = req.body.last_user;
book.save();
// If there's no associated activity for the book, create one.
// Otherwise update and push new activity to the actions array.
Activity.findById(book._id, function (err, activity) {
activity.actions.push({
checked_in: req.body.checked_in,
last_user: req.body.last_user
})
activity.save();
});
}
});
};
What I want to end up with is a document for each book with an array of check outs/ins that gets updated each time someone checks a book in or out. i.e:
{
book_id: "5058c5ddeeb0a3aa253cf9d4",
actions: [
{ checked_in: true, last_user: 'ralph' },
{ checked_in: true, last_user: 'gonzo' },
{ checked_in: true, last_user: 'animal' }
]
}
Eventually I will have a time stamp within each entry.
There are a couple problems:
You're trying to find the book's activity doc using findById using the book's id instead of the activity's id.
You're not handling the case where the book's activity doc doesn't exist yet.
Try this instead:
Activity.findOne({book_id: book._id}, function (err, activity) {
if (!activity) {
// No Activity doc for the book yet, create one.
activity = new Activity({book_id: book._id});
}
activity.actions.push({
checked_in: req.body.checked_in,
last_user: req.body.last_user
});
activity.save();
});
I see a few things that can be improved...
The book_id field in the Activity model should be Schema.ObjectId instead of a String. You will then be able to use populate if you wish.
You aren't doing any error checking in exports.update. If the user passes in an invalid id, you will want to check if book is undefined or not, as well as the common if (err) return next(err) (this requires your function params to be res, res, next).
When you create the activity in exports.update, you want to use book._id instead of book.id
All the return statements are not needed
The device variable is not declared anywhere, I'm not sure what you are trying to save... I think you meant book there.
You can then just .save() the activity instead of doing the Activity.update.