Saving Private Functions

Extending the Mongo Shell

In the last blog post, we had a look at how you could make the Mongo Shell work more efficiently for you by using a combination of JavaScript functions and the Shell’s edit helper. But we were left with the problem that for all the efficiency gains, none of the functions defined would be preserved into the next session with the shell. So now, we’ll look at some ways to create persistent functions and even share them with other database and shell users.

The first stop on our persistence quest is the .monogorc.js file. This is a file that is automatically loaded and run in the shell before the first prompt appears and can contain JavaScript code. We can use this file to set variables we need set, such as the name of the editor for the edit helper to use:

EDITOR=“vi”

Or we can put an often used function definition in there. In the previous post we created the oneFrom function to help randomly select values when creating test data so we would add that to .mongorc.js like so:

function oneFrom(list) {
  return list[Math.floor(Math.random()*list.length)];
}

So now we have a way of persisting very commonly used functions in the shell, though when we do update the file remember we have to either restart the shell (recommended) or run load(‘.mongorc.js’) (assuming you are running Mongo Shell from your home directory and that you don’t mind the updated version being mingled with the current state of the shell).

How about other collections of functions you may want to load? You could always load them manually using Mongo Shell’s load(‘filename’) which we just used. But this means your workflow is going to be split between the shell and an editor, regularly reloading if you are debugging and if you use the Shell’s edit helper changes you make there will not be reflected in the file you loaded the function from.

You may think this is an easy problem to fix and that there’s a JavaScript save or file writing command somewhere that will take care of the issue. The thing is, there aren’t any file writing commands in JavaScript. They weren’t really in JavaScript as it was designed to be run in a browser safely – reading and writing local files from a browser is somewhat unsafe. JavaScript is much more comfortable with getting data from URLs. Other JavaScript based platforms that work outside the browser, like Node.js, have implemented their own file I/O libraries. In Mongo Shell’s case the load command we used is a MongoDB specific extension but there’s no save command to go with it.

Fear not though, we are working with a database too here and one thing that databases are good at is storing things. Lets look at how we can save a function in a collection. Let’s start by looking at what a JavaScript function is by first defining a simple one:

> function example() { print("Example"); }

In JavaScript, functions are first class objects, like strings and numbers. The declaration of a function like this actually creates a variable called `example` and sets its value to a function object. You can ask the variable what type it is:

> typeof example
function

If you ask for the value of example a function will return its entire definition; although it looks like a string on the console. It is a function though and if you add some parentheses to the end, you’ll ask the JavaScript engine to execute that function. So entering example() into the shell will print “Example”.

You can also find out what name the function was defined with it by calling example.name, which here would return “example”. Note though that not all functions can have a name – anonymous functions are assigned to a variable or used as parameters and don’t need a name. The name of a function sticks with the function too. If you assigned resample=example, then resample() would still print “Example” and resample.name would still return “example”. With this knowledge, it is fairly simple to write our function into a collection that we will call functionstash:

> db.functionstash.insert({ _id:example.name, fn:example });

Functions, as a first class object, can be stored in MongoDB – they have their own BSON type which means they can be safely stored and restored. If you exit the shell and relaunch it, you can now get the function back by doing:

> example=db.functionstash.findOne({_id:"example"});

And then you can call it up with example(). Note that you could restore it to any variable name, but the function would retain its function name. But all of that is a lot of typing and we could always make it simpler.

With that in mind, allow us to introduce a bit of JavaScript code which can live in your .mongorc.js file as “stash”. Here’s the code:

var stash={
  "save":
  function(func) {
    db.functionstash.update({ _id:func.name },
      { fn:func },
      { upsert:true } );
  },
  "load":
  function(name) {
    return db.functionstash.findOne({ _id:name }).fn;
  },
  "list":
  function() {
    db.functionstash.find().forEach(
      function(func) { print(func._id); })
  }
}

This little block of code adds the commands stash.save(functionname), stash.load("functionname") and stash.list() to your Mongo Shell. To try these out, lets first empty the functionstash collection…

> db.functionstash.remove()

Now, let’s create and save a function:

> function example() { print("Example") }
 > stash.save(example)

And the function is saved. To see what functions are already saved, we can use the list function:

> stash.list()
example

And to get back the function, we need to just retrieve it and assign it to an appropriate variable:

> example=stash.load("example")
function example() { print("Example") }
> example()
Example
>

So now you can save and share functions with other users. At some point, you will probably want a file copy of all the functions you’ve been saving in the database and again, you’ll hit the problem that the Mongo Shell doesn’t have file writing capabilities. The easiest way to get that list is to run the shell and, on its command-line, ask it to evaluate query on the stash and redirect the output to a file, like this:

mongo --quiet -eval "db.functionstash.find().forEach(function(func) { print(func.fn) })" >myfunctions.js

This is only the start of what you can do with the Mongo Shell. You could, for example, extend the stash functions to keep old versions and documentation or you could create a fluent interface for your day-to-day tasks. How far can you take it? Well, it is possible to extend the functionality of the shell quite dramatically, as the developer behind mongo-hacker has done, adding a huge amount of JavaScript to .mongorc.js to add an enhanced color-highlighting user interface, shell helpers, extra db.collection commands and much more.

Or you can simply add features you want to your own shell settings as you master the Mongo Shell and JavaScript. Like adding

DBQuery.prototype._prettyShell = true

to your .mongorc.js to always have your queries return pretty-printed versions instead of having to always add .pretty() to get that effect.

The power to save time and typing is now yours.

Written by Dj Walker-Morgan

Content Curator at MongoHQ, Dj has been both a developer and writer since Apples came in ][ flavors and Commodores had Pets.

  • Geert Van Damme

    For personal settings and functions .mongorc.js might be a good place, but most of the functions I create are related to the db more than to me (as the developer). I find it much more appropriate to save these functions in the database itself. I experimented quite a bit with this approach, and found some very nice examples. Especially for complex stuff like aggregations and map-reduce. I’m planning to write a few blog posts about this.