• Meteor

Saving and Retrieving Original Documents

From the class:  RPC with Meteor Methods

We left off by implementing the Save Originals and Retrieve Originals functions in our item store, but we didn't finish the implementation in our local cache, and that's what we'll do in the rest of this video.

Even though we're using our own local cache, a very simple object implementation, we're going to use one data structure from the Mini Mongo package. And so I'm going to add that package. I'm already using it, but just to make sure you understand that I'm using it. And we'll pick out a data structure that will make our lives a little bit easier from that package.

Next step, let's go up and look at our local cache document, or our local cache object, and add the Save Originals and Retrieve Originals functions. So just like in our store, I'm going to create these two functions. So Save and Retrieve. And I'm going to add one more function here called Save Original, so this will just be for saving one original document.

OK. So when the Save Originals function is called, what we're going to do is to create a new data structure, and I'll just call it Saved Originals. And this is where we'll use a data structure that we get from the Mini Mongo package called Local Collection ID Map. And you can think of the ID map as just an object that lets us store a bunch of documents and call a couple of methods, like Get, Set, and For Each on the collection.

OK. So all we're going to do here is just initialize this property, Saved Originals to a new collection, a new local collection ID map, to be specific. And then what we'll do is, when we want to save an original document, we'll say if there's a Saved Originals property, or if there isn't one, we'll just return. And if we have a Saved Originals property but it already has this ID, then we'll return as well. Otherwise, just go ahead and set the original value.

So I'll say Set, and we'll set the document with ID to the original value that's passed in. And then the Save Original function will get called in our various mutator methods up here. So for Insert, for example, before we actually set the document, let's save the original value.

Now on the insert, the original value will be undefined because the document didn't exist before. In an update, the original value will be the original doc. And same for a Remove. So when we do our insert, here's where we'll take the original value of the document, which is undefined, and put it into the Saved Originals data structures, so long as it's been initialized.

Let's scroll down and finish the implementation, which is in Retrieve Originals. So this is pretty straightforward. What we're going to do is grab the value of the saved original, and then I'm going to set that Saved Originals back to null. So this Saved Originals function and Retrieve Originals works as a team. So when we're done doing any inserts or mutations, then we'll call this Retrieve Originals, and that will reset this back to null. And then we'll return the originals data structure there.

I've scrolled to the bottom, and let me put a debugger statement here so that we can stop in the debugger-- in the Chrome debugger, in the browser-- and see what all these values look like. And then we'll go over to the server after that and finish the process by actually inserting the document into MongoDB. All right. I'll uncomment the method calls inside of our store. So here we'll proxy the Save Originals to the original local cache, and same with the Retrieve Originals.

And then over here in the Chrome debugger, let's go over to the source file and actually insert a document. And this should drop us right into our debugger point down at the bottom here. So I'll call Items Insert, and we'll insert a document with the title of, we'll say, 2.

That drops us into our debugger. Looks like we have a bug here. And the danger of live coding is sometimes you make a mistake. And so looks like I made a spelling mistake in the Save Originals function. So let's come over here and press Play again, and we'll restart this process.

Now it looks like we're OK. So let's drop this into the debugger. We've made our Meteor call, which means the local stub has been called. And then the method completes, and here we are on the debugger. So this should stop us before the RPC method gets sent off, or right after it gets sent off but before we process it.

And what I want to do is to take a look at our local cache. We've inserted the document to the local cache, and here it is with the ID and the title.

Now let's take a look at the Meteor connection. And there should be a property in here called Server Docs. Actually, it's called _serverdocuments. And you can see its value is a key value object of collections to their local collection ID maps, or to our maps that we created in Saved Originals. And here's the document that is in the map. And you could see the original value here is undefined because it didn't exist before we inserted it.

Now when I press Play again, we should process the messages that we get back from the server. And notice that on the server, we don't actually do the insert, and so no added message is going to get sent back to the client. But about 10 seconds later, we send the updated message simply by committing the last right. And let's look at what happens on the client.

Looks like I didn't quite do it fast enough, so I got an error here. But the thing to point out, that we can still see, is that the update method of our local store gets called with a message called Replace. And what this means is that live data wants our local store to replace the document that has an ID of this.

And in this case it's not providing that document. Otherwise there'd be a property here called Replace, which you'll see when we do this for real in a second. But this property is not defined. And so what it is saying is that we should replace the document with this ID with nothing. Just get rid of it. And that's because the server never actually acknowledged this right. And so what the client should do is to get rid of it.

If we go over to the Network tab and I clear this up a little bit, you'll see just, like last time, that we make this method call to Items Insert, and we get back the result. But there's just no added message here because the server didn't actually add the document. And so by the time we get this updated message a little while later, we haven't gotten the document from the data. And that's why the live data connection is telling our local item store to just replace the document that we have in cache with nothing, so just to get rid of it.

Now let's look what happens if we actually do send this added message, so we do the Insert on the server. Let's actually insert the document into the server side Mongo. And before I forget, let's also assign the proper ID to the document that we created above, so that it has the same ID as the one created on the client.

This time we'll go over and we'll say Items Insert, and I'll give this document a title of a real insert. And that should drop us right into our debugger. And once again, in the local cache, you can see the document exists with this ID here and a. Title and if we look into the Meteor connection, the document's original value has been stored away.

So if we scroll down into Server Documents, here we go, and the original value of this document is undefined with this ID. And if I press Play this time, we should have gotten an added message from the server. Let me make it a little larger, and we'll look at the EDP messages. Here's the method call to Items Insert. then we get the result, and then before we get this updated message, we get the Added message with what the server thinks it's added, or what it's added, so the client can update its view.

But notice our local store doesn't get that added message. It doesn't get the added message because it knows that there's an outstanding server doc, and so what it does is it takes this value and replaces the value that we stored away with this. And then once it gets the updated message a few seconds later, that's when it'll take this value and copy it into the local cache. And so the message that we actually get in our update method is this Replace message. And this time around, we have the ID of the document to replace and then an actual replacement value.

So if we go back over to our watch expressions, if I look in the local cache, you'll see the same document because we haven't done anything different to it. So here it is. But it's been deleted from the server documents collection. So here's the items collection here, and inside of the map it's just empty now. And that's because we've already taken that value and copied it over to our local cache. So live data doesn't need this anymore. So it just gets rid of it.

To show you another example, I've logged into the Mongo database directly. And what I want to do is to insert a couple of extra documents. In this case, this will be just like publishing out a document where another client did the insert. And so in this case, we would expect that we get an Added message and not this Replace message, because we're not actually replacing something that we've already written in our local cache.

So let me say DB items insert. And when I press Enter, because we're using Oplog tailing, a message gets sent right away to the client, and our items local store gets its update method called with a message of Added and the collection and the ID and the documents. And I can do that again. And you can see the messages just keep getting passed down the wire, and then our local item store's updated method gets called with Added messages for each item to add.

So that's a little bit different than the Replace message, and the Replace message gets used only when we wrote the document from the client. And to clean up the collection a little, I can say DB Items Remove, and that'll send a Remove message for each one of these items.

So this was quite a detailed dive into how Meteor synchronizes rights between the client and the server. But I hope by looking at this in diagram form, and then by writing some custom code, the concept was made a little bit clearer for you.

In this video, we completed our custom store for items by implementing the Save Originals and Retrieve Originals functions, and then hooking that up into our local cache so that when we do a write-- either an Insert, an Update, or a Remove, we save away the original values so that we can roll them back later.