Using mongoose timestamps option does not create properties - mongodb

I may be missing something obvious, but have read to docs.I had an existing collection. Using Robo3T, I dropped it. In my script, running out of Node, I have defined the schema adding timestamps option as shown below. I run my app. The collection is created. However, there are no timestamps when I view via Robo. Everything else is as I expect. The indices are created. The fields are populated.
I expected two additional properties: createdAt and updatedAt.
I am using mongoose 5.2.7.
const categorySchema = mongoose.Schema(
{
value: String,
recordName: String,
sourceId: Number,
targetId: Number,
requestParameters: Object,
magentoResponse: Object
},
{
autoIndex: true
},
{
timestamps: true
}
);
categorySchema.index({sourceId: 1}, {unique: true});
categorySchema.index({targetId: 1, recordName: 1}, {unique: true});

Oh! I was being an idiot. autoIndex and timestamps should be in the same block. I was being an idiot!
It should have been:
const categorySchema = mongoose.Schema(
{
value: String,
recordName: String,
sourceId: Number,
targetId: Number,
requestParameters: Object,
magentoResponse: Object
},
{
autoIndex: true,
timestamps: true
}
);
categorySchema.index({sourceId: 1}, {unique: true});
categorySchema.index({targetId: 1, recordName: 1}, {unique: true});

How did you re-create those records? If they did not go though mongoose (but via mongoDB client/cli) they would not have those fields. These are mongoose specific.
And are created when you create a new Model and save it:
var thingSchema = new Schema({..}, { timestamps: { createdAt: 'created_at' } });
var Thing = mongoose.model('Thing', thingSchema);
var thing = new Thing();
thing.save(); // `created_at` & `updatedAt` will be included

Related

How to use setDefaultsOnInsert properly

Am trying to upsert an object with a query below:
await Property.findOneAndUpdate({ "property_id": property_id }, object,{ upsert: true, setDefaultsOnInsert: true })
My schema looks like below:
const mongoose = require('mongoose');
const propertySchema = new mongoose.Schema({
property_id: String,
details: {
location: {
type: {
type: String,
default: "Point"
},
coordinates: [Number],
address: String,
country: {
type: String,
default: 'USA'
},
state: {
type: String,
default: 'CA'
},
city: {
type: String,
default: 'N/A'
},
},
status: {
type: String,
enum: ['pending', 'active', 'deleted', 'suspended'],
default: 'pending'
},
}
},
{
strict: false
});
propertySchema.index({ "details.location.coordinates": "2dsphere" });
mongoose.model('Property', propertySchema);
module.exports = mongoose.model('Property');
Yet, when new objects are inserted, attributes and their default values are not inserted, what am doing wrong ?
There a big difference between MongooseDocument and Object:
on MongooseDocument you could apply any method, like $set or .save() it. So you could modify the DB value directly. (Or convert it to JSON/Object/String and lose this property)
when you are dealing with JSON or vanilla js Object you are modifying the object itself, not the DB value. So if you want to modify the DB document you should find it by object's key and update it.
When you are dealing with Model.method_name, especially with find(One)AndUpdate you should provide object type. NOT MongooseDocument or anything else. In that case you should convert DB doc toObject by this method.
Or, if you receiving DB value via any .find method, you receive MongooseDocument by default, and you should use lean() option right after your find query. If you need js-object for any findAndUpdate method later.
For example:
let mongoose_doc = await model.findByID(id) //returns mongoose doc
but
let js_object = await model.findByID(id).lean() //returns you js object

How to add timestamps to mongoose schema subfields?

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
}

Mongoose findOneAndUpdate on model

What's the purpose of a Mongoose schema if I'm doing an upsert with findOneAndUpdate call?
Everything I can find seems to indicate if I do a findOneAndUpdate, I need to reference the base schema instead of an instance.
Here's my setup:
const PersonSchema = new mongoose.Schema({
ssn: {
type: Number,
unique: true,
},
first: String,
last: String
})
const Person = mongoose.model("Person", PersonSchema)
const person = new Person({ssn: 123456789, first: "Foo", last: "Bar"})
If I just do a save (and that ssn exists already, I'll get a 'unique' violation).
person.save();
Instead, I'm finding out that I need to do something like
const options = { upsert: true, new: true }
const query = { ssn: 123456789 }
Person.findOneAndUpdate(
query,
{
ssn: 123456789,
first: "Foo",
last: "Bar"
},
options)
OR
const options = { upsert: true, new: true }
const query = { ssn: 123456789 }
const newPerson = Object.assign({}, person._doc)
// delete this so I don't get a conflict with Mongoose on the _id during insert
delete newPerson._id
Person.findOneAndUpdate(query, newPerson, options)
It seems like findOneAndUpdate doesn't care about the specific model (or instance) and is simply just a mechanism to get to the underlying MongoDB method.
Is that the case? Or am I missing something that should be obvious?

Mongoose why would you use populate over another find?

I'm guessing because you save resources by making 1 request instead of 2 to the database. Is this significant? Should I care to use populate if I'm populating only 1 field (the advantage is clearer when you populate more than 1)?
You don't save resources by using populate. Under the hood mongoose calls the database as many times as required. Consider the example:
module.exports = function(){
var UserSchema = new Schema({
email : {type : String, required: true},
password: {type: String, required: true}
});
return mongoose.model("User", UserSchema);
};
module.exports = function(){
var AccountSchema = new Schema({
number : {type : String, required: true},
user: {type: Schema.Types.ObjectId, ref: 'User'}
});
return mongoose.model("Account", AccountSchema);
};
mongoose.set('debug', true); //to see queries mongoose is using behind the scenes
Account.find({}, function(err, res){
console.log(res)
}).populate("user")
Apart from the results, you'll see something like this on console:
Mongoose: accounts.find({}, { fields: undefined })
Mongoose: users.find({ _id: { '$in': [ ObjectId("5807d6d6aa66d7633a5d7025"), ObjectId("5807d6d6aa66d7633a5d7026"), ObjectId("5807d709aa66d7633a5d7027") ] } }, { fields: undefined })
That's mongoose finding account documents and then user for each one of them.
It's saving you a lot of code and I don't see why you should not use it irrespective of the number of fields you're populating.

Mongoose: does a custom _id need to be declared as an index and be unique

Here is the common way to define a collection structure with Mongoose :
var UserSchema = new Schema({
_id: Schema.Types.ObjectId,
username: String,
...
});
And Now I want _id field declared as Number type :
var UserSchema = new Schema({
_id: Number,
username: String,
...
});
The problem is, do I need to declare more infomation about _id ? Such as :
var UserSchema = new Schema({
_id: {type: Number, required: true, index: {unique: true}},
username: String,
...
});
I am not sure whether MongoDB would do it automatically.
if you know the answer, could you leave a comment below ? Thank you!
Well, after some practice, I realized that, MongoDB would set _id as PRIMARY KEY (NOT NULL + UNIQUE INDEX) automatically. So, just type:
_id: Number,
...