Skip to content

Instantly share code, notes, and snippets.

@brynbellomy
Created July 4, 2011 19:54
Show Gist options
  • Save brynbellomy/1063852 to your computer and use it in GitHub Desktop.
Save brynbellomy/1063852 to your computer and use it in GitHub Desktop.
This is a Markdown sandbox.

merlin is an object-oriented, MVC framework for node.js and CouchDB. It's built on top of Express and Cradle. Its name comes from Drupal, under which the author has slaved for far too long.

Getting started

Firstly, you should understand Model-View-Control architecture in order to use merlin. If you don't yet, you might want to read up on it somewhere a little more verbose than here.

Now, before building out your classes, you'll need to load up merlin and whichever of his plugins you think you might need.

var merlin = require('merlin').initMerlin(
  // global merlin settings come in the first argument
  { // this must be a cradle object and is a required arg
    db: cradleDb, 
    
    // don't change this unless you're a true wizard!
    layoutController: 'layoutController'
  },

  // example of loading a plugin with its default settings
  // (we don't actually need this, though, so you can remove it)
  'messages',

  // views plugin
  { name: 'views', settings: {
      templatePaths: [__dirname + '/templates'] }
  },

  // objects plugin
  { name: 'oop', settings: {
      // specify includePaths if you intend to build classes beyond
      // the basics that are included with merlin.  it's almost assured
      // that you will, so go ahead and create these directories and
      // tell merlin which paths to examine.  order is irrelevant.
      includePaths: [
        __dirname + '/classes/model',
        __dirname + '/classes/view',
        __dirname + '/classes/controller',
      ] }
  }
);

The 'views' and 'oop' plugins are sufficient for now. You can elminate 'messages' -- just make sure you don't accidentally erase the global merlin config object in the first argument.

Now simply add the merlinRouter middleware to your Express stack and requests will be routed to your layoutController class (this, by the way, is why you have to specify a layoutController parameter in your global merlin settings).

app.use(merlin.merlinRouter())

or...

express.createServer(
  ...,
  merlin.merlinRouter()
)

Writing your classes

Now that Express is routing requests to your layoutController, you'll probably want to write some classes so that you can capture those requests.

A quick note that might turn a few stomachs and shatter a few hearts: merlin is brave and strong, and he has decided that his kingdom will support multiple inheritance. There are (or at least WILL be) details about the chosen implementation in the full merlin documentation. You might want to keep a few bottles of Pepto Bismol on hand from this point forward.

Anyway. Let's say you're in an incredible band and you want to use merlin to make a website to show off the incredible gigs that your incredible band expends absolutely no effort in acquiring. What's first thing you might want to model? The gigs, obviously. I'll call the model "show" and build it out something like this:

A model

function show() { this.fullConstruct() }

  show.parents = ['merlinModel', 'renderable']

  show.construct = function() { return this }
  show.bootstrap = function() {
    show.bootstrap.super('merlinModel').call(this)
    show.bootstrap.super('renderable').call(this)
    return this
  }

  show.properties = {
    date: null,
    venue: null,
    venue_url: null,
    other_bands: [],
    location: null
  }

  show.methods = {
    getCouchViews: function() {
      return {
        all: {
          map: 'function(doc) { \
                  if (doc.type == "' + this.getClassname() + '") { \
                    emit(doc.id, doc) \
                  } \
                }'
        },
        activeByDate: {
          map: 'function(doc) { \
                  if (doc.type == "' + this.getClassname() + '") { \
                    if (doc.active == true || typeof doc.active == "undefined") { \
                      emit(Date.parse(doc.date), doc._id) \
                    } \
                  } \
                }'
        }
      }
    }
  }

module.exports = show

The view

The view will be quite simple, as it will rely on merlin's rich defaults and scaffolding mechanisms.

function showView() { this.fullConstruct() }

  showView.parents = ['merlinObjectView']

  showView.construct = function() { return this }
  showView.bootstrap = function() {
    showView.bootstrap.super('merlinObjectView').call(this)
    return this
  }

module.exports = showView

The controller

And finally, the controller, binding together the view and the model.

function showController() { this.fullConstruct() }

  showController.parents = ['merlinController', 'merlinObjectController', 'merlinViewableController']

  showController.construct = function() { return this }
  showController.bootstrap = function() {
    // these do some scaffolding work for us
    showController.bootstrap.super('merlinObjectController').call(this)
    showController.bootstrap.super('merlinViewableController').call(this)
    return this
  }

  showController.methods = {
    route: function(context, callback) {
      var myself = this
      var couchParams = {
        descending: false,
        include_docs: true
      }

      this.merlin().db().view('shows/activeByDate', couchParams, function (err, result) {
        if (err)
          callback(err, null)
        else
          myself.invokeRenderer(result, context.renderContext, callback)
      })
    }
  }

module.exports = showController

Calling overridden parent methods

To call a parent method from the child method that overrides it (which you'll notice above in show.bootstrap()), you'll use this pattern:

<theFunction>.super(<optionalParentClassname>).call(this)
  • theFunction: This is the path to the function, which can come in two forms:

    1. The bootstrap() and construct() methods live directly on the constructor rather than in constructor.prototype, so when calling super() from these methods, you'll do something like:

       myClass.bootstrap.super('parentClass').call(this)
      
    2. most other methods will require something like:

       myClass.prototype.someFunction.super('anotherParentClass').call(this)
      
  • optionalParentClassname: This is only optional when your class inherits the given method from a single parent. Any function inherited from multiple parents will need to specify which parent's implementation to use.

  • You can also simply call this.myFunction.super('myParentClass').call(this) in both contexts, though this is less precise and may have unexpected results.

Creating a front page (i.e., using the staticPage object)

Of course, you'll probably want a front page for your site as well, since most casual users will guess the paths to your hidden endpoints with suboptimal efficiency. merlin comes with a built-in staticPage model and the associated view and controller classes to interact with it. To utilize these, start by simply creating a staticPage document in your CouchDB database. A staticPage document ought to look something like this:

{ _id: 'staticPage-theBestPage',
  path: 'pathTo/my/page',
  title: 'Teh best paeg on my wabsite.',
  body: 'Thx u visiting!!!11~',
  stylesheets: [
    '/styles/somewhereOnMy/server.css',
    '/styles/another.css'
  ]
}

A few things to keep in mind:

  1. Leave off the preceding slash in the path.
  2. You can't use javascript variables or regions inside the body field (at least not yet). If you want to add dynamic content within the body of your staticPage, you can do so by creating a page-pathTo-my-page.haml template and inserting those variables/regions manually.
  3. The stylesheet paths are what will appear in the <link> tags in your page's header, so make sure that they're public URLs rather than filesystem paths.

You can create a different page template for as many different pages/paths as you want. The staticPageView class has a getTemplateNames() method that specifies the different filenames that staticPage templates can have. Those are:

['page', 'page-:id:', 'page-:path:']

As you might've guessed, the ":blahblahblah:" construction specifies a variable, so that merlin will know to look for "page-theBestPage.haml" and "page-pathTo-my-page.haml" without you having to explicitly tell him to do so.

All of that is good and well, but what path will we use for the front page? After all, the front page appears when no path is specified. Why, the magic path "<front>", of course. It's called 'merlin' for a reason, after all...

Available middleware

Session IDs

Merlin can add unique, randomly generated session IDs to the session object that Express's session middleware creates. express.createServer( express.cookieParser(), express.session({ secret: "hush! don't tell." }), merlin.sessionId() )

Router

You have to use the merlinRouter middleware. Otherwise, Express will have no idea that it needs to feed requests into your layoutController. You'll probably want this middleware last on the list (or nearly last, anyway). express.createServer( merlin.merlinRouter() )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment