MongoDB/mongoose creating a relation between two models? - node.js

I am looking to create a one-to-many relationship following this pattern http://docs.mongodb.org/manual/tutorial/model-referenced-one-to-many-relationships-between-documents/
I have an Exercise.js schema, which will contain a collection of exercises.
var exerciseSchema = new mongoose.Schema({
_id: String,
title: String,
description: String,
video: String,
sets: Number,
reps: String,
rest: Number
});
Then I have a workout plan BeginnerWorkout.js schema
var workoutDaySchema = new mongoose.Schema({
_id: String,
day: Number,
type: String,
exercises: Array
});
I want to associate an array of exercises to the workoutDaySchema, this contains a collection of workout days for a particular workout, each day has a collection of exercises.
I have a seeder function that generates the workout for me.
check: function() {
// builds exercises
Exercise.find({}, function(err, exercises) {
if(exercises.length === 0) {
console.log('there are no beginner exercises, seeding...');
var newExercise = new Exercise({
_id: 'dumbbell_bench_press',
title: 'Dumbbell Bench Press',
description: 'null',
video: 'null',
sets: 3, // needs to be a part of the workout day!!
reps: '12,10,8',
rest: 1
});
newExercise.save(function(err, exercises) {
console.log('successfully inserted new workout exercises: ' + exercises._id);
});
} else {
console.log('found ' + exercises.length + ' existing beginner workout exercises!');
}
});
// builds a beginner workout plan
BeginnerWorkout.find({}, function(err, days) {
if(days.length === 0) {
console.log('there are no beginner workous, seeding...');
var newDay = new BeginnerWorkout({
day: 1,
type: 'Full Body',
exercises: ['dumbbell_bench_press'] // here I want to pass a collection of exercises.
});
newDay.save(function(err, day) {
console.log('successfully inserted new workout day: ' + day._id);
});
} else {
console.log('found ' + days.length + ' existing beginner workout days!');
}
});
}
So my question is inside building a workout plan, how can I associate the exercises into the exercises key using mongoose?

Try this:
exercises: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Exercise', required: false }]
And add an exercise to the workout using the exercise._id (In your code above you'd need to put this in the relevant callback such as the callback for .save on the exercise):
newDay.exercises.push(newExercise._id);
_id is generally a generated number so I don't know if you can set it to the text string you suggest.
When you .find() a workout you'll need to populate the exercises too. Something like:
BeginnerWorkout.find({}).
.populate('exercises')
.exec(function(err, exercises) {
//etc

Related

How do I create a number of objects with insertMany with existing data from the db?

I'm currently trying to insert a large number of models through insertMany, but I can't seem to figure out how to populate the array when creating an object. I'm relatively new to Mongoose and any help would be appreciated, here is the code I have right now.
const ProgramsSchema = new mongoose.Schema({
program_id: {
type: String,
required: true
},
description: {
type: String
},
});
const schoolsSchema = new mongoose.Schema({
inst_url: {
type: String
},
programs: {
type: [{type: ProgramsSchema, ref: "Programs"}]
}
});
And here's the code where I try to create a number of schools and add it to the database.
let new_schools = []
for (let i = 0; i < schools.length; i++) {
let school = schools[i]
let p_arr = []
for (let p_index = 0; p_index < school["PROGRAMS"].length; p_index++) {
let p_id = school["PROGRAMS"][p_index]
Programs.find({program_id: p_id}).populate('Programs').exec(function(err, data) {
if (err) {
console.log(err);
} else {
p_arr.push(data[0])
}
})
}
let newSchool = {
inst_url: school["INSTURL"],
programs: p_arr,
}
new_schools.push(newSchool);
}
Schools.insertMany(new_schools);
I can basically add all of the school data into the db, but none of the programs are being populated. I was wondering if there was a way to do this and what the best practice was. Please let me know if you guys need more info or if my question wasn't clear.
There are a few problems with your mongoose schemas. The operation you are trying to do in find is not available, based on your mongoose schemas. You cannot populate from "Programs" to "Schools". You can populate from "Schools" to "Programs", for instance:
Schools.find().populate(programs)
And to do that, several changes in your schemas are necessary. The idea is to store the programs _id in your programs array in School collection and be able to get the programs info through populate(), either regular populate or 'custom populate' (populate virtuals).
Regular populate()
I would change the schoolsSchema in order to store an array of _id into programs:
const schoolsSchema = new mongoose.Schema({
inst_url: {
type: String
},
programs: [
{type: String, ref: "Programs"}
]
});
You should change ProgramsSchema as well:
const ProgramsSchema = new mongoose.Schema({
_id: Schema.Types.ObjectId, // that's important
description: {
type: String
},
});
And now, you can do:
Programs.find({_id: p_id}).exec(function(err, data) {
if (err) {
console.log(err);
} else {
p_arr.push(data[0]._id)
}
})
Your documents should be inserted correctly. And now you can populate programs when you are performing a query over School, as I indicated above:
Schools.find().populate(programs)
Populate Virtual
The another way. First of all, I have never tried this way, but I think it works as follows:
If you want to populate over fields that are not ObjectId, you can use populate virtuals (https://mongoosejs.com/docs/populate.html#populate-virtuals). In that case, your schemas should be:
const ProgramsSchema = new mongoose.Schema({
program_id: String,
description: {
type: String
},
});
const schoolsSchema = new mongoose.Schema({
inst_url: {
type: String
},
programs: [
{type: String, ref: "Programs"}
]
});
Enable virtual in your School schema:
Schools.virtual('programs', {
ref: 'Programs',
localField: 'programs',
foreignField: 'program_id'
});
Then, you should store the program_id.
Programs.find({program_id: p_id}).exec(function(err, data) {
if (err) {
console.log(err);
} else {
p_arr.push(data[0].program_id)
}
})
And as before, you can populate() when you need.
I hope I helped

Mongoose delete records after certain time

I have a mongoose Schema which looks like this:
const USERS_DATA = new Schema({
_id: Number,
name: String,
img: String,
date: Date,
phone: String,
article: String,
createdAt: {
type: Date,
required: true,
default: Date.now,
index: { expires: '3d' }
}
},
{
collection: "users",
_id: false,
}
);
I need to push data to this schema.
const User = mongoose.model("users", USERS_DATA);
function pushToDB() {
const newUser = new User({
name: INPUT.name,
img: INPUT.img,
date: INPUT.date,
phone: INPUT.phone,
article: INPUT.article,
});
newUser.save(function (err) {
mongoose.disconnect();
if (err) return console.log(err);
});
}
This data have to be deleted after 3 days when it was pushed to database. How to implement it in node.js? I found it really confusing and tried lots of code. Any answers are appreciated! Thanks
P.S. I use mongoDb Atlas
You should separate the process to push the data into the database from the process to delete it after 3 days. You have already the first part :).
For the second part, you can write a function deleteOldDocument. This function will query for documents in DB that are created for 3 days or more, and delete them. Then, you can run this function periodically, 1 time per day for example.
The pseudo-code, in case you need it :
async function deleteOldDocument() {
const 3DaysAgo = ...; // here you can subtract 3 days from now to obtain the value
// search for documents that are created from 3 days or more, using $lt operator
const documentToDelete = await User.find({"created_at" : {$lt : 3DaysAgo }});
// delete documents from database
.....
// recall the function after 1 days, you can change the frequence
setTimeOut(async function() {
await deleteOldDocument();
}), 86400);
}
// call deleteOldDocument to start the loop
deleteOldDocument();

How to create field that will auto increment after insertion of new record in MongoDB?

I'm using Mongoose and Node.js
The Schema of the model is as follows:
let orderSchema = new Schema({
'product': String,
'orderNumber': Number,
'totalPrice': Number,
'customer': {
'type': Schema.Types.ObjectId,
'ref': 'Users'
});
I want to set the orderNumber as an incrementing integer.
Is there any way to do it in MongoDB?
I don't want to use the pre-hook technique to do it
You need to create a collection with counters and a plugin with two hooks inside:
schema.pre - to get the current value of counter
schema.post - to save new value of counter
Counter schema will look like this:
const conterSchema = new Schema({
name: String,
value: Number
});
While the plugin will can be defined like this:
function incrementOrderNumber (schema) {
schema.pre('save', next => {
CounterModel.findOne({ name: 'orderNumberCounter' })
.then(counterDoc => counterDoc.toObject())
.then(({ value}) => {
this.orderNumber = value;
next();
});
});
schema.post('save', next => {
CounterModel.findOneAndUpdate({ name: 'orderNumberCounter' }, { $inc: { value: 1 }}).exec();
});
}
After creating such plugin function you will need to plug it into your schema:
orderSchema.plugin(incrementOrderNumber);
Do not forget to insert orderNumberCounter into counters collection.

Save two referenced documents simultaneously

I've got an stock application where I want to set some details about the stock and then insert all the items of the stock. I want to insert the stock details and the items in two different collection so then I can filter the items. I'm using the MEAN Stack where I've modified the crud module to accept some extra fields and also made the UI for filling the items array.This what I have so far:
scope.stockItems = [];
$scope.createStockItem = function () {
$scope.stockItems.push(
{
brand: $scope.brand,
style: $scope.style,
amount: $scope.amount
}
);
$scope.brand = false;
$scope.style = false;
$scope.amount = '';
};
// Create new Stock
$scope.create = function() {
// Create new Stock object
var stock = new Stocks ({
name: this.name,
details: this.details,
stockDate: this.stockDate
});
// Redirect after save
stock.$save(function(response) {
$location.path('stocks/' + response._id);
// Clear form fields
$scope.name = '';
}, function(errorResponse) {
$scope.error = errorResponse.data.message;
});
};
The stock model:
var StockSchema = new Schema({
name: {
type: String,
default: '',
required: 'Please fill Stock name',
trim: true
},
details: {
type: String,
default: '',
required: 'Please fill Stock details'
},
stockDate: Date
created: {
type: Date,
default: Date.now
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
and the method in the server controller:
exports.create = function(req, res) {
var stock = new Stock(req.body);
stock.user = req.user;
stock.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(stock);
}
});
};
How can I send into the request and save the stockItems also?
By saying 'simultaneously' I think you are requiring transaction feature, which is really an RDBMS thing, and is not supported by MongoDB. If your application strongly relies on such features, I'm afraid MongoDB is not the right choice for you.
So back to your question, I don't understand why you have to store stock and stock item in 2 different collections. Store them in one collection would be a better choice. You can refer to the Data Model Design of MongoDB Manual for more information. If it's just to filter all the stock items, aggregation framework is designed for such purpose. As well as Map/Reduce. Here aggregation framework suits better for your issue. You would have something like:
db.stock.aggregate([
{$match: {...}}, // same as find criteria. to narrow down data range
{$unwind: "$items"}, // unwind items.
... // probably some other $match to filter the items
]);

Upserting subdocument in for loop

Not sure why this isn't working, I've tried a few different variations of syntax, but no go. I'm also unsure if having an update inside a for loop is a good idea.
Basically I'm updating a user's inventory by passing an object that contains two arrays (item[], and item_qty[]) that resembles as such:
var itemset = {}
itemset.item['Gold','Silver','Bronze']
itemset.item_qty[1,3,5]
itemset.qty = itemset.item.length
and the arrays can have varying lengths as such:
var itemset = {}
itemset.item['Platinum','Bronze']
itemset.item_qty[1,1]
itemset.qty = itemset.item.length
The goal is to keep adding to a user's inventory item quantity if the item exists, or add it (along with the quantity) if it doesn't exist. So using the two updated examples, the user will have Platinum(1), Gold(1), Silver(3) and Bronze(6) after they have both passed through the function.
The schema for the USER is:
var UserSchema = new mongoose.Schema({
name: {type: String, required: true},
mail: {type: String, required: true},
inv: {
item: {type: String},
iqty: {type: Number}
}
})
And here is the function:
function addInv(userId,itemset) {
for(i=0;i<itemset.qty;i++){
console.log('Updating:' + itemset.item[i])
db.User.update(
//Criteria
{
_id: userId,
inv: {item: itemset.item[i]}
},
{
$set:
{
inv:
{
'$item': itemset.item[i],
$inc: {'$iqty': itemset.item_count[i]}
}
}
},
{upsert:true},
function(err,i)
{
console.log('Error: ' + err)
console.log('Db: ' + i)
}) //End Update()
}
}
This works, in the sense of syntax, but it never updates the data...
I'd also like to see if there's a way to do this without having the for loop & multiple db calls.

Resources