I have a mongoose setup which involves an embedded-schema, lets say: A Blogpost with embedded comments. Comments can be edited by the original publisher as well as by an editor/admin. After adding / editing a comment the entire blogpost is saved.
I have some custom mongoose's 'pre' middleware set up on the embedded comment-schema which automatically sets the lasteditdate for that particular comment.
The thing is that 'pre' is called on EVERY comment on the blogpost, since I call save() on the blogpost. (For other reasons I need to do it like this) . Therefore, I need a way to check which comments have changed (or are new) since they were last saved (as part of the Blogpost overall save())
The questio: how to check in 'pre' whether a comment has changed or not? Obviously calling this.isNew isn't sufficient, since comments could be edited (i.e: aren't new) as well.
Is there any isDirty or similar that I'm overlooking?
For version 3.x
if(doc.isModified()){
// do stuff
}
In Mongoose you can use the Document method isModified(#STRING).
The most recent documentation for the method can be found here.
So to check a specific property with doc.isModified you can do this:
doc.comments[4].message = "Hi, I've made an edit to my post";
// inside pre hook
if ( this.isModified('comments') ) {
// do something
}
If you want to check a specific comment you can do that with the following notation this.isModified('comments.0.message')
Since the argument takes a string if you needed to know specifically which comment was modified you could loop through each comment and run this.isModified('comments['+i+']message')
You may use the modified getter:
if (doc.modified) {
// :)
}
This may be relevant to Mongoose users circa mid-2020 who are seeing this error:
"errorType": "TypeError",
"errorMessage": "Cannot set property 'isDirty' of null",
Upgrade to the latest version of Mongoose to fix it.
https://github.com/Automattic/mongoose/issues/8719
Related
I could not find a good template for the update method for the xdevapi library and thought maybe some one could help me out with getting this to work.
acts_table
.update()
.set('account_status', 'new account status')
.where('customer_number like :customer_number && original_account_number like :original_account_number')
.bind('01234588456')
.bind('12156464645')
.execute()
I think its just a formatting issue, but with no real examples in the XDEVAPI Documentation for update for Javascript I'm kind of at a lost. Please help. The error I get is Cannot Read property 'toArray' of undefined.
It was formatting, in the bind method I forgot to put the fields we were binding so the answer is this:
acts_table
.update()
.set('account_status', 'new account status')
.where('customer_number like :customer_number && original_account_number like :original_account_number')
.bind('customer_number','01234588456')
.bind('original_account_number', '12156464645')
.execute()
https://thinkster.io/tutorials/node-json-api/adding-comments-to-articles > Section Retrieving comment(s) on an article.
Environment
Thinkster Conduit Project: Angular<->API(node.js)<->MongoDB
npm(1) (JavaScript): 6.9.0
mongoose : ^4.4.10 (Compass Community Version)
Goal
Store a comment on a (own written) article and retrieve this comment.
Note
To get the local API (for this purpose I used an exact copy of the production ready API supplied by Thinkster on GitHub) working, in particular: posting a comment on an own written article, I had to change in;
Model: User.js UserSchema (2x) >>> .push(id); => .concat([id]);
Route: routes/api/articles.js router.post('/:article/comments', ...) >>> .push(comment); => .concat([comment]);
These changes had something to do with the drop of 'usePushEach' (versions ago). Nothing is mentioned about it in the current supplied documents on thinkster.
The change on the posting route is apparently important: concatenation of comment-objects onto the article when posting a comment, but it works (see pictures).
Passes
Storage of a comment on a own written article (checked and tested
with Postman & Mongo)
Remove of the comment (checked and tested with Postman & Mongo)
Problem!
Can NOT retrieve, by all means, comment(s) on a own written article (as a list [GET-route]). Somehow the 'comment'-objects are correctly mapped back to the article.
Question: How to solve this? Is this under construction? How to do a callback of the function in which the 'comment' (as argument) has to be mapped back to the article? This argument 'comment' seems to be a empty object, see picture 5: line 356: comment.toJSONFor(user) ???,
In addition, I noticed, on the conduit client talking to the remote 'production-ready' api, own written articles ar not listed as own feed, however they do appear in de global list.
1) Storage of a comment on a own writer article works fine
2) Mongo Db:
3) Retrieve (.get) comments by Postman:
4) Response on the terminal:
5) Section of the Json:
Solution
The problem occurred by two pitfalls:
The promise 'comment' does not get aborted
It is not possible to 'push' directly into the 'req.article.comments' array property
Fixes
In the original code (GitHub) the .catch(next) is fit on the User [Document], this
stopper needs to sit on the promise stream 'comment' instead.
'Old fashion' JavaScript is needed to keep track of existing comment ids, load them separately into a new Array and push the new comment id onto it (so far pushing is allowed). Also the very first comment id on a article needs to be stored in an array type variable.
In both cases a new created array is allowed to map (=) onto the 'req.article.comment'-array property; pushing on it causes an $pushAll error and .concat(...) does not work either.
...better Solution
Circumscribe the sync post-route into an asynchronous post-route
But keep fix 2]1
Let's say I have the following schema:
PersonSchema = {
name: String,
timesUpdated: {
type: Number,
default: 0
}
}
Every time that the given person is updated, I would want the timesUpdated field to increment by one. Now, I could use Mongoose's update middleware hook, which would be called by something like
PersonModel.update({_id: <id>}, {name: 'new name'})
and my timesUpdated field would be appropriately incremented. However, if I only wrote a hook for the update middleware, the following code would not update my timesUpdated field:
PersonModel.updateOne({_id: <id>}, {name: 'new name'})
In order for my count to be updated, I would have to write middleware for the udpateOne query. This pattern repeats for several other similar middleware hooks, such as updateMany, replaceOne, save (if you want to update a document this way), findOneAndUpdate and I'm sure many others.
I use the example of an updated count for simplicity, but I could also have used an example where some other unrelated action happens upon changing my name. Am I missing something in how hooks should be used, or is this a limitation of mongoose hooks?
Pre save hook will only be executed with following functions according to mongoose's middleware document.
init
validate
save
remove
However update functions are working directly with MongoDB, therefor there is no general use hook applies on all update functions. See related discussion on Github.
I'd suggest using a function to perform your task before/after all required calls (to update or updateOne) rather a hook, because of the limitations mentioned in the other answer and the question.
Or perhaps limit the kinds of methods that can be called to the ones that have the hook set.
Or use a hook which will always get called in the middle-ware sequence, like a validate hook.
I recently realized that DocumentDB supports stand alone update operations via ReplaceDocumentAsync.
I've replaced the Upsert operation below with the Replace operation.
var result = _client
.UpsertDocumentAsync(_collectionUri, docObject)
.Result;
So this is now:
var result = _client
.ReplaceDocumentAsnyc(_collectionUri, docObject)
.Result;
However, now I get the exception:
Microsoft.Azure.Documents.BadRequestException : ResourceType Document is unexpected.
ActivityId: b1b2fd71-3029-4d0d-bd5d-87d8d0a2fc95
No idea why, upsert and replace are of the same vein and the object is the same that worked for upsert, so I would expect it to work without problems.
All help appreciated.
Thanks
Update: Have tried to implement this using the SelfLink approach, and it works for Replace, but selflink does not work with Upsert. The behavior is quite confusing. I don't like that I have to build a self link in code using string concatenation.
I'm afraid that building the selflink with string concatenation is your only option here because ReplaceDocument(...) requires a link to the document. You show a link to the collection in your example. It won't suck the id out and find the document as you might wish.
The NPM module, documentdb-utils, has library functions for building these links but it's just using string concatenation. I have seen an equivalent library for .NET but I can't remember where. Maybe it was in an Azure example or even in the SDK now.
You can build a document link for a replace using the UriFactory helper class:
var result = _client
.ReplaceDocumentAsync(UriFactory.CreateDocumentUri(databaseId, collectionId, docObject.Id), docObject)
.Result;
Unfortunately it's not very intuitive, as Larry has already pointed out, but a replace expects a document to already be there, while an upsert is what it says on the tin. Two different use-cases, I would say.
In order to update a document, you need to provide the Collection Uri. If you provide the Document Uri it returns the following:
ResourceType Document is unexpected.
Maybe the _collectionUri is a Document Uri, the assignment should look like this:
_collectionUri = UriFactory.CreateDocumentCollectionUri(DatabaseName, CollectionName);
I need to remove a property from a mongoose document instance. I've found a lot of questions that show how to remove it from the database, but that's not what I'm looking for.
I need to pull the document down including a field to check security access, I then want to strip that field so that it doesn't get disclosed if downstream code decides to call toObject() and send the object back to the client.
Any thoughts?
I needed to remove password property from the document instance but I didn't find anything in the API documentation. Here is what I did:
doc.set('password', null); // doc.password is null
Then I found you can also do this:
delete doc._doc.password // doc.password is undefined
Since version 2.4 you can do:
doc.field = undefined;
await doc.save();
This will essentially $unset the field
Using the set function with a value of null will simply assign the value, not remove it. Best to first convert the document using toObject() (so that it is becomes a plain Object), make the changes and revive it back to a model document:
let tempDoc = doc.toObject();
delete tempDoc.password;
doc = new this(tempDoc);