I am using Mongoose and MongoDB v. 6.4.1. I have defined a document collection with embedded subdocuments using the following Mongoose schemas:
import mongoose, { Collection } from 'mongoose';
const connectStr = 'mongodb://localhost/appdb';
mongoose.set('useFindAndModify', false);
//Open connection to database
mongoose.connect(connectStr, {useNewUrlParser: true, useUnifiedTopology: true})
.then(
() => {console.log(`Connected to ${connectStr}.`)},
err => {console.error(`Error connecting to ${connectStr}: ${err}`)}
);
//Define schema that maps to a document in the Users collection in the appdb
//database.
const Schema = mongoose.Schema;
const roundSchema = new Schema({
date: {type: Date, required: true},
course: {type: String, required: true},
type: {type: String, required: true, enum: ['practice','tournament']},
holes: {type: Number, required: true, min: 1, max: 18},
strokes: {type: Number, required: true, min: 1, max: 300},
minutes: {type: Number, required: true, min: 1, max: 240},
seconds: {type: Number, required: true, min: 0, max: 60},
SGS: {type: Number,
default: function(){return (this.strokes * 60) + (this.minutes * 60) + this.seconds}
},
notes: {type: String, required: true}
});
const userSchema = new Schema({
id: {type: String, required: true}, //unique identifier for user
password: String, //unencrypted password (for now!)
displayName: {type: String, required: true}, //Name to be displayed within app
authStrategy: {type: String, required: true}, //strategy used to authenticate, e.g., github, local
profileImageUrl: {type: String, required: true}, //link to profile image
rounds: [roundSchema],
securityQuestion: {type: String},
securityAnswer: {type: String, required: function() {return this.securityQuestion ? true: false}}
});
//Convert schema to model
const User = mongoose.model("User",userSchema);
In an Express.js GET route, I am using the following code to query for a specific document:
try {
let thisUser = await User.findOne({id: req.params.userId});
console.log("thisUser: " + JSON.stringify(thisUser));
if (!thisUser) {
return res.status(400).send("No user account with specified userId was found in database.");
} else {
return res.status(200).json(thisUser.rounds);
}
} catch (err) {
console.log(err);
return res.status(400).message("Unexpected error occurred when looking up user in database: " + err);
}
My console.log statement confirms that the above route in fact obtains the desired document, e.g.:
thisUser: {"_id":"5e6704234f3864318caedd12","id":"chundhau#gmail.com","password":"GoCougs20","displayName":"chundhau#gmail.com","authStrategy":"local","profileImageUrl":"https://www.gravatar.com/avatar/4b565c54d37b3f5ad4caa1c129e865b8","securityQuestion":"First pet?","securityAnswer":"Daisy","__v":0,"rounds":[]}
When I look at this same document in MongoDB Compass Community, I can confirm that its rounds subdocument array has several elements:
However, as shown in the console.log output above, rounds is coming back as an empty array. I have confirmed that (a) rounds is in fact an array (using Array.isArray()) and that (b) rounds has no elements (thisUser.rounds.length === 0).
Shouldn't I be able to access all of the subdocuments through thisUser.rounds? What have I done wrong?
I have discovered a solution. I changed:
let thisUser = await User.findOne({id: req.params.userId});
to
let thisUser = await User.findOne({id: req.params.userId}).lean();
Miraculously, thisuser.rounds was no longer empty. It instead contained all of the array elements I could see when I inspected the document in MongoDB Compass Community!
While this solution worked, I do not know why it worked. If anyone could help me understand what's going on here, I'd appreciate it!
Related
how to create an array of objects with default values?
const schema = new mongoose.Schema({
login: {type: String, unique: true, required: true},
password: {type: String, required: true},
chatlist: [
{
channelname: {type: String, default: "frontend"},
channelid: {type: String, default: "619a6bfe5b0931f1e5dbaf2c"}
},
{
channelname: {type: String, default: "backend"},
channelid: {type: String, default: "619a71002954ba23a951bb8f"}
},
{
channelname: {type: String, default: "devops"},
channelid: {type: String, default: "619d69c190a85a40893b6522"}
},
]
})
this code above does not work
when a new user register i want to add a default chats to his profile
next thing is the user must be able to add/remove chats from the database
how should i do this? do the chats need to be an objects or documents?
const schema = new mongoose.Schema({
chatlist: {
type: Array,
defualt: []
}
})
so to have a structure in the array I personally would make the request correct using error handling.
for more information visit this site
didnt find any good solution so i just did this
const schema = new mongoose.Schema({
login: {type: String, unique: true, required: true},
password: {type: String, required: true},
chatlist: [{
channelname: String,
channelid: String
}]
})
const user = await MONGO_user.create({
login: login,
password: hashPassword,
})
user.chatlist.push(
{
channelname: "frontend",
channelid: "619a6bfe5b0931f1e5dbaf2c"
}, {
channelname: "backend",
channelid: "619a71002954ba23a951bb8f"
}, {
channelname: "devops",
channelid: "619d69c190a85a40893b6522"
})
await user.save()
I'm trying to add createdAt and updatedAt timestamps in subfields of otp in generate: {} and verify:{}
I know that using { timestamps: true } will add the createdAt and updatedAt timestamps to the whole schema.`
const userSchema = new mongoose.Schema({
email: { type: String, unique: true },
name: { type: String },
mobileNumber: {
isVerified: {type: Boolean, default: false},
otp: {
generate: {
attempts: {type: Number, default: 0},
total: {type: Number, default: 0},
createdAt: {type: Date},
updatedAt: {type: Date}
},
verify: {
attempts: {type: Number, default: 0},
total: {type: Number, default: 0},
createdAt: {type: Date},
updatedAt: {type: Date}
}
}
}
}, { timestamps: true });
What is correct solution to add individual timestamps to subfields? Is it correct to do the same by adding {timestamps: true} to the subfields?
generate: {
attempts: {type: Number, default: 0},
total: {type: Number, default: 0},
{timestamps: true}
},
verify: {
attempts: {type: Number, default: 0},
total: {type: Number, default: 0},
{timestamps: true}
}
You will have do define a separate schema for your subfields, then use that as the type of your subfields.
const otpSchema = new mongoose.Schema({
attempts: { type: Number, default: 0 },
total: { type: Number, default: 0 }
}, {
_id: false, // omit _id fields for subfields
timestamps: true // timestamps options for subfields
});
const userSchema = new mongoose.Schema({
email: { type: String, unique: true },
name: { type: String },
mobileNumber: {
isVerified: { type: Boolean, default: false },
otp: {
generate: otpSchema, // use the defined schema
verify: otpSchema
}
}
}, { timestamps: true });
Okay, it seems like this answer became more popular, so a will extended it with full coverage.
What does {timesamps: true} do and how it does what it does?
The original {timesamps: true} code, from mongoose.js can be found here # line:1150
How exactly timestamps: true} knows when and how it should update updatedAt field, and don't update createdAt ?
By this code:
this.pre('save', function(next) {
/**
* SKIP CODE A BIT
*/
if (!skipUpdatedAt && updatedAt && (this.isNew || this.isModified())) {
let ts = defaultTimestamp;
if (this.isNew) {
if (createdAt != null) {
ts = this.$__getValue(createdAt);
} else if (auto_id) {
ts = this._id.getTimestamp();
}
}
this.set(updatedAt, ts);
}
next();
});
So each time when mongoose driver triggers .save on MongooseDocument, this code got executed (if timestamps set to true, of course)
There is a big difference between MongooseDocument (object) and js-Object/JSON/result of find({}).lean()
You could cast various methods on MongooseDocument, like .isNew (this is exactly how mongoose understood that updatedAt field should be updated, and createdAt should not). or convert it .toObject() or .toJSON() Full list of methods can be found here.
Just to be sure: when you are using .find without .lean() option, you are dealing with MongooseDocument(s), but if enable it, you will receive plain JavaScript objects.
How to create you own implementation of {timestamps: true} for your own schema?
It's easy to achieve the same results, via default values and with using setters in your schema:
createdAt: {type: Date, default: Date.now},
updatedAt: {type: Date, default: Date.now, set: v => v.Date.now()}
You could read more about setters here.
Also, it could be any function you want, for example you could modify value each time on any insert || modify operation (and update, too)
..Or you could avoid setters and update updatedAt field manually in code, every time, via: model.findAndUpdate({your_field: value}, {your_field: value, updatedAt: Date.now()) each time.
So, in the end, using setters (or manual query update), will gave you the same result as timestamps: true option, but you could apply it to every sub-document in your schema.
I don't think such feature fall in the scope of the database capabilities to provide, nor mongoose to enable.
You may want to create two other entities - Attribute and AttributeValue with OneToMany relationship, to track values changes timestamp.
Well, that's how we tackled the issue on my main current project.
I ran into the same problem and mine was a pretty straightforward solution.
const userSchema = mongoose.Schema({ email: String }, { timestamps: true });
my result with createdAt and updatedAt automatically added:
{
"_id" : ObjectId("60ba2cb3bca3572f6ca1b525"),
"title" : "First post with timestamp",
"content" : "Test post timestamp",
"createdAt" : ISODate("2021-06-04T13:37:55.025Z"),
"updatedAt" : ISODate("2021-06-04T13:37:55.025Z"),
"__v" : 0
}
I'm trying to conditionally update a field in a document but I need to know the value of an other field in a linked document. I'm struggling to explain my problem so i will give you my code and try to explain what I'm trying to do.
I got a route to update my 'workstation' collection
router.post("/workstation/update/:id", (req, res, next) => {
const id = req.params.id;
const previousWorkstation = req.body.previous;
const updatedWorkstation = req.body.updated;
const {errors, isValid} = validateWorkstation(updatedWorkstation);
if(!isValid){
return res.status(422).json(errors);
}
Workstation.findByIdAndUpdate(mongoose.Types.ObjectId(id), updatedWorkstation, (err, workstation) => {
if(err) return;
NonConformity.updateMany({workstation: previousWorkstation.name, line: previousWorkstation.line}, {$set:{workstation: updatedWorkstation.name}}, (err) => {
if(err) return;
Rework.updateMany({workstation: previousWorkstation.name}, {$set:{workstation: updatedWorkstation.name}}, (err) => {
if(err) return;
res.send(200);
})
})
})
});
My problem is I need to update rework depending on the line too, and not only the workstation name ! Here are my schemas
// WORKSTATION SCHEMA
const mongoose = require('mongoose');
const WorkstationSchema = mongoose.Schema({
name: {type:String, required: true},
line: {type:String, required: true}
})
const Workstation = mongoose.model('workstations', WorkstationSchema);
module.exports = Workstation;
// REWORK SCHEMA
const mongoose = require('mongoose');
const ReworkSchema = mongoose.Schema({
nonConformity_id: {type:String, required: true},
operator: {type:String, required: true},
fin: {type:String, required: true},
workstation: {type:String, required: false},
code: {type:String, required: true},
declination: {type:String, required: true},
description: {type:String, required: true},
advice: {type:String, required: false},
pps: {type: String, required: false},
quantity: {type:Number, required: true},
reworked: {type:Boolean, default: false, required: true},
verified: {type:Boolean, default: false, required: true},
})
const Rework = mongoose.model('nonConformities.reworks', ReworkSchema);
module.exports = Rework;
// NON CONFORMITY
const mongoose = require('mongoose');
const NonConformitySchema = mongoose.Schema({
number: {type: String, unique: true, required: true, dropDups: true},
line: {type: String, required: true},
product: {type: String, required: true},
workstation: {type: String, required: true},
msn: {type: String, required: true},
date: {type: String, required: true},
type: {type: String, required: true},
})
const NonConformity = mongoose.model('nonConformities', NonConformitySchema);
module.exports = NonConformity;
My question is : Is it possible to update my workstation name in the rework collection depending on the line name of the linked non conformity id in the rework schema ?
Sorry if my english is bad, i can try to reformulate if you don't understand my question.
Thanks in advance !
I have three collections in my Database: Apps, Reviews, and Users.
Apps will have multiple reviews, reviews will have one user, and users will have multiple reviews.
I do need them all tied together and am struggling. I have been able to attach reviews to users (and vice versa), but I am unable to attach the reviews to the apps. I was thinking this action needs to happen on the POST to the database for a review.
I do have the appId required (and it's stored within the review body), but I am unable to correctly identify and update the correct collection (applications) in the database. I seem to be spinning myself in circles over this.
Basically, I need to reference both the Application the review is for, and the User who reviewed when a review is posted. I do have all models "correctly" referencing the other collections (as you demo'd with your messages and users components).
Would anyone be able to point me in the right direction? Here is my code for the POST so far.
// SAVE A REVIEW TO DATABASE
router.post('/', function(req, res, next) {
var decoded = jwt.decode(req.query.token);
User.findById(decoded.user._id, function(err, doc) {
if (err) {
return res.status(404).json({
title: 'An error occurred',
error: err
});
}
var review = new Review({
content: req.body.content,
rating: req.body.rating,
app: req.body.appId,
user: doc
})
Application.findById(req.body.appId, function(err, doc) {
if (err) {
return res.status(404).json({
title: 'An error occurred',
error: err
});
}
review.save(function(err, result) {
if (err) {
return res.status(404).json({
title: 'An error occurred',
error: err
});
}
review.update(function(err, result) {
if (err) {
return res.status(404).json({
title: 'An error occurred',
error: err
});
}
doc.reviews.push(result);
doc.save();
res.status(201).json({
message: 'Saved review',
obj: result
});
});
});
});
MODELS:
Review Model:
var schema = new Schema({
content: {type: String, required: true},
rating: {type: Number},
dateCreated: {type: String},
app: {type: Schema.Types.ObjectId, ref: 'Application'},
user: {type: Schema.Types.ObjectId, ref: 'User'}
});
Application Model:
var schema = new Schema({
name: {type: String, required: true},
description: {type: String},
reviews: [{type: Schema.Types.ObjectId, ref: 'Review'}],
})
User Model:
var schema = new Schema({
firstName: {type: String, required: true},
lastName: {type: String, required: true},
password: {type: String, required: true},
passwordConfirm: {type: String, required: true},
email: {type: String, required: true, unique: true},
reviews: [{type: Schema.Types.ObjectId, ref: 'Review'}]
});
Perhaps I am going the way wrong way about this, but I seem to be pretty lost.
Thank you everyone in advance.
Hey guys here is my mongoose schema with a way to create a user token that will be included in a password reset link, I was just wondering if it looked right? Also, not sure how to exactly call a new token, after the User has been created, will a User.save or User.findAndUpdate and then include the token work in the route file? Also, what do I insert inside the token field as a value that will draw from the token encrypted here?
Here is the file:
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
ObjectId = mongoose.Schema.Types.ObjectId,
bcrypt = require('bcrypt-nodejs'),
SALT_WORK_FACTOR = 10;
var crypto = require('crypto');
var UserToken;
var UserSchema = new Schema({
email: { type: String, required: true, lowercase:true, index: { unique: true } },
password: { type: String, required: true },
firstName: {type: String, required: true},
lastName: {type: String, required: true},
phone: {type: Number, required: true},
birthday: {type: Date, required: true},
friendRequest: {type: Object},
notifications: {type: Object},
friend: {type: Object},
date_created: {type: Date},
token: {type: String}
}, {collection: "users"});
UserSchema.statics.new = function(_id, fn) {
var user = new UserToken();
crypto.randomBytes(48, function(ex, buff) {
var token = buff.toString('based64').replace(/\//g, '_').replace(/\+/g, '-');
user.token = _id + '|' + token.toString().slice(1,24);
user._id = _id;
user.save(fn);
});
};
module.exports = mongoose.model('User', UserSchema);