Fork me on GitHub mmmodel

mmmodel

Homer Simpson's favoriate Javascript ORM

core

lib/core.js

NB private functions are indicated by an underscore prefix.

  • param: String name of the class

  • param: Properties list of property definitions

  • param: String name of store, e.g. "redis"

module.exports = function(type, properties, store) {

Constructor for model. Normally is unsynchronized (i.e. unsaved)

  • param: Object hash of properties

  • param: boolean [private] True if the new object is synchonized (i.e. appear to be saved)

var Model = function(props, _sync) {
    props = props || {}   
    this._.properties = {}      // this._.properties contains the synchronized version of the properties in the Store
    
    for(var k in properties) {
      var p = properties[k]
      if(k in props) this[k] = props[k]
      else if('default' in p) this[k] = p['default']  
    }

    this._.callbacks = {} // this._.callbacks contains event bindings relevant to this instance
    if(props.errors) this.errors = props.errors
    this.trigger("initialize")
    if(_sync) this._synchronize()
  }

Create a sychronized object (typically it would be one that been retrieved from a database)

Model.load = function(props) {
    return new Model(props, true)
  }

Load a list of models expressed as JSON

Model.load_multi = function(a) {
    var ret = []
    for(var i=0;i<a.length;i++) ret[i] = Model.load(a[i])
    return ret
  }

Create a new object that's then saved to the database

  • param: Object

Model.create = function(props, cb) {
    var m = new Model(props)
    m.save(cb || noop)
  }

Find an object or create it if it doesn't exist

Model.find_or_create = function(props, cb) {
    Model.find(props.id, function(m) {
      m ? cb(m) : Model.create(props, cb || noop) 
    })
  }

Has this instance been modified (i.e not in sync wit the Store). Returns null if there's no difference

  • param: string optional if null, we'll check the whole function, else it will check only this property

Model.prototype.modified = function(prop) {
    var o = {}, modified
    
    if(prop) {
      modified = this._.properties[prop] != this[prop]
      return modified ? [this._.properties[prop], this[prop]] : false
    }

    for(var i in properties) {
      if(this._.properties[i] !== this[i]) {
        modified = true
        o[i] = [this._.properties[i], this[i]]
      }
    }
    return modified ? o : false
  }

Has this model been saved? I.e. is it not modified

Model.prototype.saved = function() {
    return !this.modified()
  }

Update the model with the params and save it

Model.prototype.update = function(params, cb) {
    params || (params == {})
    cb || (cb == noop)
    
    Model.find(params.id, function(m) {
      if(!m) {
        cb()
      } else {
        m.merge(params)
        m.save(function(ok) {
          cb.call(m, ok)
        })
      }
    })
  }

Save this instance to the store

  • param: Function optional callback. Returns the saved object

Model.prototype.save = function(cb) {
    var self = this
    cb || (cb = noop)
    utils.achain.call(self, self._saveStack, [], function(err, results) {
      if(!err) self._synchronize()
      cb.call(self, self)
    })
  }

Add an error

Model.prototype.error = function(error) {
    this.errors || (this.errors = [])
    this.errors.push(error)
  }

Destroy the object in the store

  • param: Function optional callback - returns if it was successful or not

if successful triggers "destroyed"

Model.prototype.destroy = function(cb) {
    var self = this
    cb || (cb = noop)
    Model.destroy(this.id, function(ok) {
      if(ok) self.trigger("destroyed")
      cb(ok)
    })
  }

Returns a object ready for JSON.stringify NB Does not return a string NB Date types are converted to numbers

Model.prototype.toJSON = function() {
    var o = {}
    for(var name in properties) {
      if(name in this) {
        o[name] = this[name]
        if(properties[name].type == "date") o[name] = o[name]/1
      }
    }     
    o.type = type
    if(this.in_error()) o.errors = this.errors
    return o   
  }

Does this model have errors ?

Model.prototype.in_error = function() {
    return this.errors && this.errors.length != 0
  }

Merge a list of properties on this the model

  • param: Object properties

  • param: Object optional alternative for "this"

Model.prototype.merge = function(props, x) {
    for(var name in properties)
      if(name in props) (x || this)[name] = props[name]
    return this
  }

Validate the model for errors performs some simple checks for 'required' and 'format' properties calls a "saving" callback, which on completion checks that the number of errors is zero and runs the callback

Model.prototype.validate = function(cb) {
    this.errors = []
    this._property_validate()
    this.trigger("saving").complete(function() {
      cb.call(this, !this.in_error()) 
    })
  }

Simple Validation for 'required' and 'format' properties

Model.prototype._property_validate = function() {
    for(var i in properties) {
      var p = properties[i]
      if(p.required && this[i] == null)
        this.error(i + " is required")
      if(p.format && this[i] && !this[i].match(p.format))
        this.error(i + " is bad format")
    }
  }

Bind to all instances of Model or just this instance ##

  • param: string name of event, e.g. "saving"

  • param: Function callback to run

  • param: boolean is the callback async?

Model.bind = Model.prototype.bind = function(ev, callback, async) {
    if(async) callback.async = true
    var list  = this._.callbacks[ev] || (this._.callbacks[ev] = [])
    list.push(callback)
  }

Unbind to all instances of Model or just this instance

Model.unbind = Model.prototype.unbind = function(ev, callback) {
    if (!ev)
      this._.callbacks = {}
    else if (!callback)
      this._.callbacks[ev] = []
    else {
      var list = this._.callbacks[ev] || []

      for (var i = 0; i < list.length; i++) {
        if (callback === list[i]) {
          list.splice(i, 1)
          break
        }
      }
    }
    return this
  }

Trigger an event. This will trigger all handers with the particular event It will also return a promise that allows a complete callback to be set. ##

  • param: string name of event

  • para: m [further arugments]

Model.prototype.trigger = function(ev) // further args 
  {
    var global = Model._.callbacks[ev] || []
        local = this._.callbacks[ev] || [],
        list = global.concat(local)
        args = Array.prototype.slice.call(arguments, 1),
        self = this

    var pr = new (utils.promise)(self)

    utils.chain.call(self, list, args, function(err, results) {
      nextTick(function() {
        pr.invoke(err, results)
      }, 0)
    })
    return pr
  }

  return Model
}

edge_query

lib/edge_query.js

function EdgeQuery(x, self) { this.key = x this.self = self }

EdgeQuery.prototype.range = function(low, high) { high != null || (high = low) this._range = [low, high] return this }

EdgeQuery.prototype.count = function(cb) { this._count = true this.all(cb) }

EdgeQuery.prototype.all = function(cb) {

var args = [this.key] var method if(this.range) { method = this.count ? "ZCOUNT" : "ZRANGEBYSCORE" } else { if(this._count) method = "ZCARD" else { method = "ZRANGE" args.push("0") args.push("-1") } }

if(this.range) { var L = this.range[0].toString().replace(/()/g, "").replace("inf", "Infinity") var R = this._range[1].toString().replace(/()/g, "").replace("inf", "Infinity")

L = parseFloat(L)
R = parseFloat(R)

if(R < L) { // swap ! 
  method = method.replace("ZRANGE","ZREVRANGE")
  // this._range = [this._range[1], this._range[0]]
}

args = args.concat(this._range)

}

var context = this.self

var klass = this.loadas

args.push(function(err, data) { if(err) return cb.call(context, null) if(klass) klass.load_ids(data, cb) else cb.call(context, data) })

var client = self.constructor.client

client[method].apply(client, args) }

EdgeQuery.prototype.loadas = function(type) { this.load_as = type return this }

// EdgeQuery.prototype.desc = function() { // this._desc = true // return this // }

module.exports = EdgeQuery

mmmodel

lib/mmmodel.js

exports.core = require("./core")

exports.redis = function(type, properties) { return exports.core(type, properties, "redis") }

exports.rest = function(type, properties) { return exports.core(type, properties, "rest") }

exports.memory = function(type, properties) { return exports.core(type, properties, "memory") }

queue

lib/queue.js

function Q() { this.jobs = [] return }

Q.prototype._run = function() { if(this.current) return this.current = this.jobs.shift()

if(!this.current) return this.current.job.call(this) //() //.call this.current.context }

Q.prototype.complete = function(result) {

console.log("completeing queue") if(!this.current) { console.log("error result - when not running?!") return } if(this.current.callback) this.current.callback(result) //.call(this.current.context, result)

this.current = null this._run() }

Q.prototype.add = function(job, callback) { console.log("adding to queue") this.jobs.push({job: job, callback: callback}) this._run() }

module.exports = Q

utils

lib/utils.js

var nextTick = (typeof process != "undefined") ? process.nextTick : setTimeout

exports.merge = function(self, o) { for(var i in o) self[i] = o[i] return self }

function Promise(context) { this.context = context } Promise.prototype.invoke = function() { if(!this.complete) return this.complete.apply(this.context, arguments) } Promise.prototype.complete = function(cb) { this._complete = cb }

exports.promise = Promise

exports.achain = function achain(fns, args, done) { for(var i in fns) fns[i].async = true exports.chain.call(this, fns, args, done) }

exports.chain = function chain(fns, args, done) { var self = this, results = [], i = 0

args || (args = []) function complete(err, results) { done.call(self, err, results ) }

function result(answer){ if(answer === false) return complete(true, results) results[i] = answer

i++ 
if(i == fns.length) return complete(null, results)
run(fns[i])

}

var async_args = args.concat([result])

function run(fn) { if(fn.async) fn.apply(self, async_args) else { var n = fn.apply(self, args) result(n) } }

if(fns.length == 0) return complete(null, results) run(fns[0]) }

function Q() { this.jobs = [] return }

Q.prototype._run = function() { if(this.current) return this.current = this.jobs.shift()

if(!this.current) return this.current.job.call(this) //() //.call this.current.context }

Q.prototype.complete = function(result) {

if(!this.current) { console.log("error result - when not running?!") return } if(this.current.callback) this.current.callback(result) //.call(this.current.context, result)

this.current = null this._run() }

Q.prototype.add = function(job, callback) { this.jobs.push({job: job, callback: callback}) this._run() }

exports.queue = Q