How to add timestamps to mongoose schema subfields? - mongodb

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
}

Related

MongoDB does not store date accurately while adding new items repeatedly

If I add new data to MongoDB repeatedly, for example adding 3 data in 2-3 seconds, it stores "createdAt" field same.
I actually does not know much about MongoDB. I don't know what else I should try.
FactSchema
const FactSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true,
},
text: {
type: String,
required: true,
},
vote: {
type: Number,
default: 0,
},
createdAt: {
type: Date,
default: Date.now(),
},
updatedAt: {
type: Date,
default: Date.now(),
},
});
These 3 documents show same createdAt yet they shouldn't be. There should be 2-3 seconds different between them.
_id: 5d6a8d6863a5d51af80eec87
createdAt: 2019-08-31T15:08:18.190+00:00
_id: 5d6a8d6f63a5d51af80eec88
createdAt: 2019-08-31T15:08:18.190+00:00
_id: 5d6a8d7263a5d51af80eec89
createdAt: 2019-08-31T15:08:18.190+00:00
Use Date.now instead of Date.now().
Update:
Date.now sends the function itself to mongoose which will be evaluated at the time of "document creation",
Whereas Date.now() is the evaluated value which is created when it is run for the first time, that is, at the time of "schema creation".

Is there a way to selectively apply timestamps in mongoose schema?

I'm currently designing a mongoose Schema. The schema is for blog comments, it looks like this:
new mongoose.Schema({
commentedOn: {
type: mongoose.Schema.Types.ObjectId,
required: true
},
author: {
type: String,
required: true
},
contents:{
type: String
},
points: {
type: Number,
default:0
},
timestamps: true
})
The points field is to record the votes of one comment. I don't want to change the timestamp every time when users vote the comment. Is there a way to achieve this? Or should I move the points field out of this schema?
I believe timestamps should be passed in the second argument of the schema.
Regarding your question, the only way I can think of doing this is to not use timestamps and explicitly declare your timestamp fields e.g. createdAt and updatedAt in your schema. Whenever you save or update, you would explicitly set the updatedAt field (or not) depending on the situation.
new mongoose.Schema({
commentedOn: { type: mongoose.Schema.Types.ObjectId, required: true },
author: { type: String, required: true },
contents: String,
points: { Number, default: 0 },
createdAt: { type: Date, default: Date.now },
updatedAt: Date
});

database design for a market

I want to design database for a market with simple and few objects for selling using NodeJS, MongoDB and Mongoose. Because I'm new to MongoDB and NoSQL designs, I need a guide for designing it.
My implementation is here:
var orderSchema = new Schema({
orderId: Schema.Types.ObjectId,
orderType: {
type: String, enum: ['OBJEC1',
'OBJECT2',
//other objects
], default: 'ALBUM'
},
price: { type: String, enum: ['PRICE1', 'PRICE2', 'PRICE3'] },
coverPhoto: { type: String, default: '' },
photos: [{
address: { type: String, default: 'media/uploads' },
}],
orderQuantity: { type: Number, default: 1 },
isChecked: { type: Boolean, default: true },
date: { type: Date, default: Date.now }
});
Besides, I'll save reference of each order to its related user. Am I right, or not? Thanks a lot.
The way you designed your schema based on the logic seems good. One thing is you have used default in all the fields.
First, you should understand that default is optional and default is used only when you want to populate some value during the data is created.
Example: you have default for date field, here it is good to have. You don't want to manually assign a date during processing the data. So only unless your field should have common default value when creation then you go ahead otherwise remove the default field and make sure the data is inserted properly.
you can use required attribute in case some field is mandatory to create a document in the collection. I guess orderType a mandatory field so don't miss it ever during insertion so make it as required: true.
var orderSchema = new Schema({
orderId: {
type: Schema.Types.ObjectId
},
orderType: {
type: String,
enum: ['OBJEC1','OBJECT2']
},
price: {
type: String,
enum: ['PRICE1', 'PRICE2', 'PRICE3']
},
coverPhoto: {
type: String
},
photos: [{
address: {
type: String
}
}],
orderQuantity: {
type: Number
},
isChecked: {
type: Boolean,
default: true
},
date: {
type: Date,
default: Date.now
}
});

mongoose Foreign Key

I have a model called Portfolio that points to a user object using the user_id field. How can I model a many to one relationship with mongoose?
Portfolio
- user_id => is the id of a user object
Basically every portfolio object belongs to a user object.
I have the following code: Is this correct?
var PortfolioSchema = mongoose.Schema({
url: String,
createTime: { type: Date, default: Date.now },
updateTime: { type: Date, default: Date.now },
user:[
{type: Schema.Types.ObjectId, ref: 'User'}
]
});
Try this instead
var PortfolioSchema = mongoose.Schema({
url: String,
createTime: { type: Date, default: Date.now },
updateTime: { type: Date, default: Date.now },
user:{type: Schema.Types.ObjectId, ref: 'User'}
});

Populate query with match returns null

I have three schemas, that need them to be separated and I can't use subdocuments. The important one is this
export var TestSchema = new mongoose.Schema({
hash: { type: String, index: { unique: true }, default: common.randomHash },
date: { type: Date, default: Date.now },
result: { type: Object },
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
data: { type: Object },
finished: Date,
lang: { type: String, default: 'pt' },
benchmark: { type: String, required: true },
order: { type: mongoose.Schema.Types.ObjectId, ref: 'Transaction' },
/* TODO: remove */
name: { type: String }
});
I have a query that does the populate (it's actually a pagination helper, but I'm cutting to the chase):
TestModel.find({hide: {$ne: true}, user: id}).populate({
path: 'user',
match: {$or: [
{email: new RegExp(search, i)},
{name: new RegExp(search, i)},
{empresa: new RegExp(search, i)},
]}
}).exec().then(/*...*/)
when populate.match doesn't find anything, it sets the user to null. I tried setting the find({'user':{$ne: null}}) but it ignores it. (I guess the populate happen after the find call, maybe that's the reason).
Is there any way I can filter it in the database layer instead having to rely on iterating of the results, check for null then filter out?
This answer on GitHub clarifies that it is not possible with populate, due to how MongoDB works. However, you should be able to do it with $lookup.