Draft documents in Mongo - mongodb

Suppose I have a Mongo collection and a web application to view the collection in the Internet. When I edit the collection (i.e. add, delete, and update the documents) my changes are instantly available in the Internet.
Now I would like to save all my changes as a draft and when I want to make it available I will publish the draft explicitly.
The trivial implementation is to add a new draft collection. I edit only this draft collection and when I press a publish button the draft collection is copied to the original collection.
So far so good, but this solution is not scalable since the time of the copy is a function of the collection size. Would you suggest another solution ?

Have a field in each document that's a draft that's set if the document is a draft. Something like:
title:"FooBar blah blah",
draft:true
When the button is clicked to publish the post you only need to update a single document - that post's document to either change draft to false or $unset it. Your queries which choose which documents to display must be checking for draft:{$ne:true} - note that this will match both documents which have draft set to false and documents which don't have the field draft at all. This allows you to leave the current documents alone and not need to update them all to have this field.

Related

OfficeJS - Retrieving document ID

We need to retrieve an ID that uniquely identifies a document, so that when a user opens the same document in different sessions (even a year apart) we can identify this in the logs.
In the API I found DocumentURL but this could change (if the document is moved?) and it might even be empty (if the document is never stored online?). We could hash a combination of properties like Author and Date Created but these too can change and thus can't be fully relied upon.
How do we access the ID of a document? Ideally we're looking for a solution that works for any type of document, but if currently there is only such a property for a Word document then that is sufficient as well.
EDIT: Adding scenarios that need to work because otherwise my request seems too simple (hence the down-votes?):
The user can open, edit, save, etc. other documents and the ID should ALWAYS be the same PER document. Similarly, if a user shares a document with someone else, the ID read by the other user (when running our add-in) should be the same as for the owner of that document.
The add-in needs to be portable and usable on multiple platforms. When a user opens the same document on Word Online and Win 32, on different computers, etc. the ID must always be the same for that document.
To create a unique ID, it takes only a little JavaScript to create a GUID. See this SO post for example: Create GUID/UUID in JavaScript
To store the ID, you could use a custom setting or custom property. See Persist State and Settings

Conditional update for MongoDB (Meteor)

TLDR: Is there a way I can conditionally update a Meteor Mongo record inside a collection, so that if I use the id as a selector, I want to update if that matches and only if the revision number is greater than what already exists, or perform an upsert if there is no id match?
I am having an issue with updates to server side Meteor Mongo collections, whereby it seems the added() function callback in the Observers is being triggered on an upsert.
Here is what I am trying to do in a nutshell.
My meteor js app boots and then connects to an endpoint, fetching data and then upserting it into the collection.
collection.update({'sys.id': item.sys.id}, item, {upsert: true});
The 'sys.id' selector checks to see if the item exists, and then updates if it does or adds if it does not.
I have an observer monitoring the above collection, which then acts when an item has been added/updated to the collection.
collection.find({}).observeChanges({
added: this.itemAdded.bind(this),
changed: this.itemChanged.bind(this),
removed: this.itemRemoved.bind(this)
});
The first thing that puzzles me is that when the app is closed and then booted again, the 'added()' callback is fired when the collection is observed. What I would hope to happen is that the changed() callback is fired.
Going back to my original update - is it possible in Mongo to conditionally update something, so you have the selector, then the item, but only perform the update when another condition is met?
// Incoming item
var item = {
sys: {
id: 1,
revision: 5
}
};
collection.update({'sys.id': item.sys.id, 'sys.revision': {$gt: item.sys.revision}, item, {upsert: true});
If you look at the above code, what this is going to do is try to match the sys.id which is fine, but then the revisions will of course be different which means the update function will see it as a different document and then perform a new insert, thus creating duplicate data.
How do I fix this?
To your main question:
What you want is called findAndModify. First, look for the the document meeting the specs, and then update accordingly. This is a really powerful idea because if you did it in 2 queries, the document you found could be deleted/updated before you got to update it. Luckily for you, someone made a package (I really wish this existed a year ago!) https://github.com/fongandrew/meteor-find-and-modify
If you were to do this without using findAndModify you'd have to use javascript to find the doc, see if it matches your criteria, and then update it. In your use case, this would probably work, but there will always be that "what if" in the back of your mind.
Regarding observeChanges, the added is called each time the local minimongo receives a document (it's just reading what the DDP is telling it). Since a refresh will delete your local collection, you have to add those docs one by one. What you could do is wait until all added callbacks have fired, and then run your server method. In doing so, you get a ton of adds, and then a couple more changes will trickle in afterwards.
As Matt K said, you want findAndModify. There are some gotchas to be aware of:
findAndModify is about 100x slower than a find followed by an update. Find+modify is, obviously, not atomic and so won't do what you need, but be aware of the speed hit. (This is based off experience with MongoDB v2.4, so run some benchmarks to confirm under your own version.)
If your query matches multiple items, findAndModify will only act on the first one. In this case, you're querying on a unique id, but be aware of the issue for future use.
findAndModify will return the document after doing its thing, but by default it returns the pre-modification version. If you want the modified one, you need to pass the 'new: true' in your query.

Determine which Mongo collection an document exists in?

Is there a way in Meteor/MongoDB to do a find to get the collection an document's _id exists in?
What I am trying to accomplish is to create a generic Comments framework for my app, where comments can be applied to several different document types that are saved in multiple Mongo collections. For instance, comments can be applied to Pages as well as Comments. What I need to do is save the comment, then modify the parent document. I can pass in the _id of the parent, but without strong typing I can't figure out if this is a Page or a Comment (or any other "commentable" type I might come up with.
One solution, I think, would be to store the "parent"'s ID in the comment, but I wanted to try to save an array of comments in the parent instead.

MongoDB schema design to support editing subdocuments within an array in multi-user environment?

Let's suppose I have a basic blog web app using the following document schema for a blog post.
{
_id: ObjectId(...),
title: "Blog Post #1",
text: "<p>This is my blog post!</p>",
comments: [
{
user: "username1",
time: Date(...),
text: "This is a great blog post!"
},
{
user: "username2",
time: Date(...),
text: "This is even better than sliced bread!"
}
]
}
That's all well and good, but now let's suppose that a user can edit or delete his comment. On top of that, it's a web app, so there could be multiple people editing or deleting their comments at the same time. Now suppose I am logged in as "username2" and try to edit my comment, which is the 2nd item in the comments array - index position 1. Just before I click "save", user1 logs in and deletes his comment which is the 1st item in the array. If my code tries to delete user 2's comment by index position, it will fail because there are no longer 2 items in the array.
Two ideas came to mind, but I'm not crazy about either one.
create some sort of id on each comment
create a "lastModified" timestamp on the parent document, and only save the edit if nothing has changed on the document.
What is the best way to handle this type of situation? If I really need an id on each comment, will I have to generate it myself? What data type should it be? Or would it be best to use both of my ideas together? Or is there another option I'm not even thinking about?
Having different writers is a key downside of embedding documents in my opinion. You might want to take a look at this discussion that presents different solutions. I'd try to avoid different writers to one document and use a separate Comments collection instead, where each comment is owned by its author. You can fetch all comments on a post by an indexed field postId reasonably fast. Then the comments simply have a regular _id field. It makes sense to use an ObjectId because that automatically stores the time the comment was created, and it's a monotonic index by default.
create a "lastModified" timestamp on the parent document, and only save the edit if nothing has changed on the document.
This is called 'optimistic locking' and it's generally not good if there is a high probability of concurrent operations. In the case of blog posts, it's likely that newer posts receive a lot more comments than older ones, so I'd say the collision proability is kinda high.
There's yet another nasty side-effect: let's say the blog post author wants to modify the text but someone adds or removes a comment in the mean time. Now even the blog author wouldn't be able to change the text unless you use the atomic $set operation on the text and bypass the version check.

Where to extend collection documents with computed fields in Meteor?

We have information we need on the client which is computed on a document. Like for example the number of entries in an array.
More practically we have a document Workshop which helds an array of participants (user's _id). Now we want the Workshop.numberOfParticipants().
There is no need to transmit the whole array to the client, so where to calculate this value? Is it possible to add this value to the document "Workshop" as a field like the other data?
I like to circumvent the generation of a Template.workshop.numberOfParticipants().
One option for the future is MongoDB's oddly-named aggregation framework. Queries written against the aggregate API can return documents with calculated fields.
Meteor core doesn't support aggregate queries yet, but it's on the wishlist.
You'll need to publish a set of documents called NumParticipants and then add an observer that updates a count property or something similar when documents are added (and similarly reduces that property when docs are removed).
An example of how to do this is described in the documentation for publish.