Mongoose nested schemas - node.js

I'm creating an app where you log workouts and I'm having some problems with Mongoose.
I have two schemas, one for workouts and one for exercises. When the user adds a new exercise, I want it to be stored inside the workout, and I've been trying this in a bunch of ways.
For now, the exercises are saved in a different collection in my MongoDB (don't know if this is the best way to do it), and I thought that it should save the exercise inside the workout.exercises, but there is only the objectID. How do I resolve this? Have looked at the populate function, but can't figure out how to get it to work.
addExercises
export function addExercise(req, res) {
if (!req.body.exercise.title) {
res.status(403).end();
}
const newExercise = new Exercise(req.body.exercise);
// Let's sanitize inputs
newExercise.title = sanitizeHtml(newExercise.title);
newExercise.cuid = cuid();
newExercise.sets = [];
newExercise.save((err, saved) => {
if (err) res.status(500).send(err);
});
Workout
.findOneAndUpdate(
{cuid: req.body.exercise.workoutCUID},
{$push: {exercises: newExercise}},
{upsert: true, new: true},
function (err, data) {
if (err) console.log(err);
});
}
getExercises
export function getExercises(req, res) {
Workout.findOne({cuid: req.params.cuid}).exec((err, workout) => {
if (err) {
res.status(500).send(err);
}
console.log(workout);
let exercises = workout.exercises;
res.json({exercises});
});
}
Workout
import mongoose from "mongoose";
const Schema = mongoose.Schema;
var Exercise = require('./exercise');
const workoutSchema = new Schema({
title: {type: 'String', required: true},
cuid: {type: 'String', required: true},
slug: {type: 'String', required: true},
userID: {type: 'String', required: true},
exercises: [{ type: Schema.Types.ObjectId, ref: 'Exercise' }],
date: {type: 'Date', default: Date.now, required: true},
});
export default mongoose.model('Workout', workoutSchema);
Exercise
import mongoose from "mongoose";
const Schema = mongoose.Schema;
var Workout = require('./workout');
const exerciseSchema = new Schema({
title: {type: 'String', required: true},
cuid: {type: 'String', required: true},
workoutCUID: {type: 'String', required: true},
sets: {type: 'Array', "default": [], required: true}
});
export default mongoose.model('Exercise', exerciseSchema);

Based on your Workout schema, you declare the type of the Exercises field to be [{ type: Schema.Types.ObjectId, ref: 'Exercise' }]. This means that this field should be an array of Mongoose ObjectId's.
It appears that you are attempting to add the whole exercise object to the workout's exercises field, rather than just the ObjectId. Try modifying it this way:
const newExercise = new Exercise(req.body.exercise);
// Let's sanitize inputs
newExercise.title = sanitizeHtml(newExercise.title);
newExercise.cuid = cuid();
newExercise.sets = [];
newExercise.save((err, saved) => {
if (err) res.status(500).send(err);
// Nest the Workout update in here to ensure that the new exercise saved correctly before proceeding
Workout
.findOneAndUpdate(
{cuid: req.body.exercise.workoutCUID},
// push just the _id, not the whole object
{$push: {exercises: newExercise._id}},
{upsert: true, new: true},
function (err, data) {
if (err) console.log(err);
});
});
Now that you correctly have the ObjectId saved in the exercises field, .populate should work when you query the workout:
Workout.findById(id).populate("exercises").exec((err, workout) => {
// handle error and do stuff with the workout
})

Workout.findById(req.params.id).populate("exercises").exec((err,workout) =>{
res.status(200).json(workout);
})
It should work this way

Related

How to store reference data using mongoose refs?

I'm making a note app, each note will have multiple categories.
var NotesSchema = new mongoose.Schema({
title: String,
note: String,
favorite: {type: Boolean, default: false},
category: [{ type: mongoose.Schema.Types.ObjectId, ref: "categories" }]
},{ timestamps: { createdAt: 'created_at' } });
var Notes = mongoose.model('Notes', NotesSchema);
var CategoriesSchema = new Schema({
name: {type: String, required: true},
favorite: {type: Boolean, default: false},
})
var Categories = mongoose.model('Categories', CategoriesSchema);
I can do this with only one category but I don't know how it's done for multiple categories.
First I thought I need to store the categories, get each id and then store the note. Here's what I tried:
.get('/', (req, res) => {
var data = {
title : "scientific revolution",
note : " some note",
favorite : true,
category : [ 'science', 'books']
}
var catIds = [], seriesIds = [];
data.category.map(cat =>{
const category = new Categories({
name: cat
})
category.save((err,data)=>{
if(err) throw err;
catIds.push(data._id)
})
})
data.category = catIds;
const note = new Notes(data)
note.save((err, data)=>{
if(err) throw err;
res.json(data)
})
})
The catIds array never gets the ids!
I'm totally new at using references. Didn't even know they existed.
Your ref field should point to the model name not the table name.
In this case Categories.
I see you have write {ref: 'categories'} with small c but mongoose collection name is case sensitive and you should do {ref: 'Categories'}.
You can read more about mongoose case sensitivity in this post

ERROR: ValidationError: CastError: Cast to ObjectID failed for value

SITUATION:
It seems I must have made a mistake in my Mongoose Model or in one of the parameters that are passed to the route.
I am fairly new to the angular2 architecture, so the mistake might be quite obvious.
ERROR:
ERROR: ValidationError: CastError: Cast to ObjectID failed for value "{ title: 'das',
username: 'John',
choice1: 'FSDAFASDF',
choice2: 'FDSAFD',
counter1: 11,
counter2: 0,
pollId: '5920598ade7567001170c810',
userId: '591c15b3ebbd170aa07cd476' }" at path "poll"
CODE:
route
router.patch('/', function (req, res, next) {
var decoded = jwt.decode(req.query.token);
User.findById(decoded.user._id, function (err, user) {
user.votes = req.body.votes;
user.save(function(err, result) {
if (err) {
console.log("ERROR: "+err);
return res.status(500).json({
title: 'An error occurred',
error: err
});
}
res.status(201).json({
poll: 'Vote Saved',
obj: result
});
});
});
});
models/user:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var mongooseUniqueValidator = require('mongoose-unique-validator');
var schema = new Schema({
firstName: {type: String, required: true},
lastName: {type: String, required: true},
password: {type: String, required: true},
email: {type: String, required: true, unique: true},
polls: [{type: Schema.Types.ObjectId, ref: 'Poll'}],
votes: [{
poll: {type: Schema.Types.ObjectId, ref: 'Poll'},
choice: {type: Number},
}],
});
schema.plugin(mongooseUniqueValidator);
module.exports = mongoose.model('User', schema);
models/poll
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var User = require('./user');
var schema = new Schema({
title: {type: String, required: true},
choice1: {type: String, required: true},
choice2: {type: String, required: true},
counter1: {type: Number, required: true},
counter2: {type: Number, required: true},
user: {type: Schema.Types.ObjectId, ref: 'User'}
});
schema.post('remove', function (poll) {
User.findById(poll.user, function (err, user) {
user.polls.pull(poll);
user.save();
});
});
module.exports = mongoose.model('Poll', schema);
EDIT:
router.patch('/', function (req, res, next) {
var decoded = jwt.decode(req.query.token);
console.log("VALID ID ? :"+mongoose.Types.ObjectId.isValid(decoded.user._id));
console.log("DECODED USER ID:"+ decoded.user._id);
User.findByIdAndUpdate(decoded.user._id, {votes: req.body.votes}, function (err, user) {
user.save(function(err, result) {
if (err) {
console.log("ERROR: "+err);
return res.status(500).json({
title: 'An error occurred',
error: err
});
}
res.status(201).json({
poll: 'Vote Saved',
obj: result
});
});
});
});
I'm thoughtfully guessing that this particular piece of code is what causes the issue:
...
User.findById(decoded.user._id, function (err, user) {
user.votes = req.body.votes;
user.save(function(err, result) {
...
mongoose is trying to resave the model and overwrite it's _id property with a plain string, whereas it should be an instance of the ObjectId.
Instead of using save to update your model, please try to use findByIdAndUpdate instead. If this is working, than my guess would be correct.
User.findByIdAndUpdate(decode.user._id, {votes: req.body.votes}, function (err, user) {
Or, cast the string _id into an ObjectId manually
...
User.findById(decoded.user._id, function (err, user) {
user.votes = req.body.votes;
user._id = mongoose.Types.ObjectId(user._id);
user.save(function(err, result) {
...
The first is preferred.

How to implement partial document embedding in Mongoose?

I have a simple relation between topics and categories when topic belongs to a category.
So schema looks like this:
const CategorySchema = new mongoose.Schema({
name: String,
slug: String,
description: String
});
And topic
const TopicSchema = new mongoose.Schema({
category: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Category'
},
title: String,
slug: String,
body: String,
created: {type: Date, default: Date.now}
});
I want to implement particular embedding of category into topic
{
category: {
_id: ObjectId('abc'),
slug: 'catslug'
},
title: "Title",
slug: "topictitle",
...
}
It will help me avoid unnecessary population and obtain performance bonuses.
I don't want to embed whole document because I want to changes categories sometimes (it is a rare operation) and maintain references.
Hope this helps, done it in my own project to save some RTTs in common use cases. Make sure you're taking care of both copies on update.
parent.model.js:
const mongoose = require('mongoose');
const childEmbeddedSchema = new mongoose.Schema({
_id: {type: mongoose.Schema.Types.ObjectId, ref: 'Child', auto: false, required: true, index: true},
someFieldIWantEmbedded: {type: String}
});
const parentSchema = new mongoose.Schema({
child: { type: childEmbeddedSchema },
moreChildren: { type: [{type: childEmbeddedSchema }] }
});
module.exports = mongoose.model('Parent', parentSchema);
child.model.js:
const mongoose = require('mongoose');
const childSchema = new mongoose.Schema({
someFieldIWantEmbedded: {type: String},
someFieldIDontWantEmbedded: {type: Number},
anotherFieldIDontWantEmbedded: {type: Date}
});
module.exports = mongoose.model('Child', childSchema);
parent.controller.js:
const mongoose = require('mongoose');
const Parent = require('path/to/parent.model');
exports.getAll = (req, res, next) => {
const query = Parent.find();
// only populate if requested! if true, will replace entire sub-document with fetched one.
if (req.headers.populate === 'true') {
query.populate({
path: 'child._id',
select: `someFieldIWantEmbedded ${req.headers.select}`
});
query.populate({
path: 'moreChildren._id',
select: `someFieldIWantEmbedded ${req.headers.select}`
});
}
query.exec((err, results) => {
if (err) {
next(err);
} else {
res.status(200).json(results);
}
});
};

Mongoose find/findOne always returns null when filter is added

I have problem which has seriously been bothering for the past few days. I have mongoose setup for a nodejs project I have defined all the schemas and models as shown below
var studentSchema = new Schema({
fullname: {type: String, required: true},
student_id: {type: String, required: true, unique: true},
votingNo: {type: Number, required: true, unique: true},
voted: {type: Boolean, required: true, default: false}
});
var Student = mongoose.model('Student', studentSchema, 'student');
I have exported the model and i'm using it in another module.
whenever I try query for results like so:
model.Student.find({}, function (err, students) {
console.log(err);
console.log(students);
});
I get results. But the moment I add a filter, like so:
model.Student.find({student_id: studentId}, function (err, students) {
console.log(err);
console.log(students);
});
The result is always an empty array.
I've tried using findOne() but it's always returning null.
Try to call the queries like this
var Student = mongoose.model('Student');
Student.find({}, function (err, students) {
console.log(err);
console.log(students);
});
If it doesn't work, add this before your call to be sure that the database is open.
var mongoose = require('mongoose');
console.log(mongoose.connection.readyState); // Should not return 0
Hope it helps!

update an internal schema with mongoose

i'm having a problem with updating a member in schema that contained in other schema.
var User = new mongoose.Schema({
first_name : { type: String, required: true , lowercase: true},
last_name : { type: String, required: true , lowercase: true},
log : { type: [Log], default: [Log] },
});
var Log = new mongoose.Schema({
left_groups : [{ type: mongoose.Schema.Types.ObjectId, ref: 'Group' }],
});
i'm trying to update the left_groups member (from the user) which is a reference to group schema and i can't do that.
after a research on the net, the best i came up with is:
User.update({_id: "549a972f243a461c093f8ebb"}, {log:{$push:{left_groups: gr}}}, function () {
console.log("updated")
});
and this seems not working for me.
//gr[0] means for group.
i succeed to figure it out..
i'm not sure it's a proper solution though:
Group.findOne({_id: "549b18c73117388028c3990f"}, function (err, gr) {
User.findOne({_id: '549b18c73117388028c39904'}, function(err, user){
user.log[0].left_groups.push(gr);
user.save();
});
})

Resources