mmmodelHomer Simpson's favoriate Javascript ORM | |
| 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)
|
var Model = function(props, _sync) {
props = props || {}
this._properties = {}
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 = {}
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
|
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
|
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
|
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
|
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.
##
|
Model.prototype.trigger = function(ev)
{
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
}
|
| 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
|
|
| 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")
}
|
|
| 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
|
|
| 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
|
|