Last active
August 15, 2023 20:31
-
-
Save tcha-tcho/1675fb39c4f4b28ec50d844364796705 to your computer and use it in GitHub Desktop.
Small ORM part of the framework duJS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Module to work with Models and Classes | |
*/ | |
(function(o){ | |
o.Model = {_:{}}; | |
// o.Model.connection; | |
// o.Model.connect; | |
var counter = 0; | |
o.Model._.isDoc = function(obj){ | |
return obj instanceof du.Model.Doc; | |
}; | |
o.Model._.isModel = function(obj){ | |
return typeof obj == "function" && !!obj.modelName; | |
} | |
var isFunc = function(cb) { | |
return (cb && typeof cb == "function"); | |
}; | |
var isNative = function(obj) { | |
return obj.toString().indexOf("[native code]") != -1; | |
} | |
var empty = []; | |
var callCB = function(cb, doc, args) { | |
if (typeof args == "undefined") args = []; | |
if (!(args instanceof Array)) args = [args]; | |
args = [doc.errors||doc.invalid||null, doc] | |
.concat(empty.slice.call(args, 0)) | |
if (isFunc(cb)) cb.apply(doc, args); | |
} | |
// normal Methods | |
var eventName = function(model, type) { | |
return "__"+model.modelName+"."+model._.id+"_"+type; | |
} | |
// USE AUTOLOAD ON SCHEME!!! | |
o.Model._.embeddedID = function(doc, collectionName) { | |
if (typeof doc == "object") { | |
return doc.modelName+o.opt.embeddedIDseparator+((doc._||{}).id); | |
} else if (typeof doc == "string" && collectionName) { | |
if (!o.Model._.isEmbeddedID(doc)) { | |
return collectionName+o.opt.embeddedIDseparator+doc; | |
} else { | |
return doc; | |
} | |
} | |
}; | |
// var embRegex = new RegExp("(.*?)"+o.opt.embeddedIDseparator+"(.*?)"); | |
o.Model._.isEmbeddedID = function(id) { | |
if (typeof id == "string") { | |
// return embRegex.exec(id); | |
var splitted = id.split(o.opt.embeddedIDseparator); | |
return (splitted.length == 2)?splitted:false; | |
}; | |
}; | |
o.Model._.id = function(id){ | |
if (typeof id != "string") id = ""; | |
var splitted = id.split("::"); | |
return splitted[1]||splitted[0]||""; | |
}; | |
var applyEvents = function(methods) { | |
methods.on = function(type, func, one) { | |
o.on(eventName(this,type), func, this, one); | |
} | |
methods.one = function(type, func) { | |
this.on(type, func, 1); | |
} | |
methods.off = function(type, func) { | |
o.off(eventName(this,type), func); | |
} | |
methods.trigger = function(type) { | |
arguments[0] = eventName(this,type); | |
o.trigger.apply(o.trigger, arguments); | |
} | |
methods.change = function(func) { | |
this[func?"on":"trigger"]("change", func); | |
} | |
methods.hasEvent = function(type) { | |
return !!o.events[eventName(this,type)] | |
} | |
}; | |
/** | |
* DOC - Class and methods | |
*/ | |
var docMethods = {}; | |
// //DOC and Model events | |
// ["tp", "type", "on", "one", "off", "trigger", "change", "state"] | |
// .map(function(mtd){ | |
// docMethods[mtd] = o.Methods.Set[mtd]; | |
// }); | |
o.opt.docVersionLimit = 3; | |
applyEvents(docMethods); | |
docMethods.toObject = function(){ | |
var obj = {}; | |
for (prop in this) { | |
if (this.hasOwnProperty(prop)) obj[prop] = this[prop]; | |
} | |
return obj; | |
}; | |
docMethods.toString = function(){ | |
return JSON.stringify(this.toObject()); | |
}; | |
docMethods.log = function(subject) { | |
console.log((subject?subject+": ":"") + JSON.stringify(this, null, '\t')); | |
return this; | |
}; | |
docMethods.validate = function() { | |
o._.hidden(this, "invalid", this.Model.validate(this)); | |
return this.invalid; | |
}; | |
// docMethods.invalidate = function(path, msg, value){} | |
docMethods.save = function(cb){ | |
return this.Model.save(this, cb); | |
}; | |
docMethods.reset = function(){ | |
return this.Model.reset(this); | |
}; | |
docMethods.rollback = function(cb){ | |
return this.Model.rollback(this, cb); | |
}; | |
docMethods.update = function(obj, cb) { | |
return this.Model.update(this, obj, cb); | |
}; | |
docMethods.remove = function(cb) { | |
return this.Model.remove(this, cb); | |
}; | |
docMethods.attr = function(path, value) { | |
// Talvez aqui poderia ser como no Node attr() ou prop() | |
if (typeof path === "object") { | |
for (var prop in path) { | |
if (!docMethods[prop]) this[prop] = path[prop]; | |
} | |
return this; | |
} else { | |
return o._.path(this, path, value); | |
} | |
}; | |
docMethods.clone = function(){ | |
return new this.Model(this); | |
}; | |
docMethods.is = function(compare, recurr) { // is accept partials | |
var matches = 0, count = 0; | |
if (!recurr && o._.isEmpty(compare)) return false; | |
for (var prop in compare) { | |
count ++ | |
if (typeof compare[prop] == "object") { | |
if (docMethods.is.call(this[prop], compare[prop], recurr)) matches ++; | |
} else { | |
if (o._.deepEqual(this[prop], compare[prop])) matches ++; | |
} | |
} | |
return matches == count; | |
}; | |
docMethods.isModified = function (path) { | |
if (!this.version[0]) return true; | |
return !o._.deepEqual(this.attr(path), this.version[0].attr(path)); | |
}; | |
docMethods.toObject = function(){ | |
return o._.clone(this); | |
}; | |
docMethods.toJSON = function(){ | |
return JSON.parse(JSON.stringify(o._.clone(this))); | |
}; | |
docMethods.state = function(states, data) { | |
return o.state(states, data, this) | |
}; | |
docMethods.sync = function(next) { | |
var self = this; | |
return o.Model[this.modelName].get(this._.id).then(function(docs){ | |
o._.replaceObj(self, docs[0]); | |
next(self) | |
}); | |
}; | |
docMethods.clean = function(propToClean) { | |
var self = this; | |
var isEmb = o.Model._.isEmbeddedID(self[propToClean]); | |
if (isEmb) { | |
return isEmb[1]||isEmb[0] | |
} else { | |
return self[propToClean] | |
}; | |
}; | |
docMethods.get = function(propToLoad, cb) { | |
var self = this; | |
var isEmb = o.Model._.isEmbeddedID(self[propToLoad]); | |
if (isEmb) { | |
var prom = o.Model[isEmb[0]].get(isEmb[1], cb); | |
prom.firstOnly = true; | |
return prom; | |
} else { | |
if (typeof cb == "function") cb.call(self, self[propToLoad]); | |
return {then: function(fun){ | |
if (typeof fun == "function") fun.call(self, self[propToLoad]); | |
}} | |
} | |
}; | |
docMethods.load = function(propToLoad, next) { | |
if (typeof propToLoad == "function") { | |
next = propToLoad; | |
propToLoad = false; | |
}; | |
if (!propToLoad) propToLoad = "*"; | |
if (propToLoad && !(propToLoad instanceof Array)) { | |
propToLoad = [propToLoad]; | |
}; | |
var self = this; | |
var queue = []; | |
var queueCount = 0; | |
var toLoad = []; | |
// this.Model.base(this._.id) | |
o._.eachAll(self, function(attr, key, upperKey) { | |
var _this = this | |
var _key = key; | |
if (du._.isNumber(key)) key = upperKey; | |
if (propToLoad.indexOf(key) != -1 || propToLoad[0] == "*") { | |
var isEmb = o.Model._.isEmbeddedID(attr); | |
if (isEmb) toLoad.push({attr: attr, obj: _this, key: _key}); | |
} | |
}); | |
toLoad.forEach(function(load) { | |
var attr = load.attr; | |
var obj = load.obj; | |
var key = load.key; | |
// var scheme = self.Model.schema.scheme | |
var type; | |
if (o.Model._.isDoc(attr)) { | |
obj[key] = o.Model._.embeddedID(attr); | |
attr = obj[key]; | |
// attr = attr._.id; | |
// scheme[key].type = attr.modelName | |
type = attr.modelName; | |
}; | |
var isEmb = o.Model._.isEmbeddedID(attr); | |
if (isEmb) { | |
// scheme[prop].type = isEmb[0]; | |
type = isEmb[0]; | |
attr = isEmb[1]; | |
}; | |
if (attr && type) { | |
queue.push({type: type, id: attr, obj: obj, key: key}); | |
queueCount++; | |
}; | |
}) | |
/// call after ready!!!! | |
var resolve = function(){} | |
var reject = function(){} | |
function finished() { | |
if (typeof next == "function") { | |
next.call(self, self) | |
}; | |
resolve.call(self, self) | |
}; | |
queue.forEach(function(get){ | |
var Model = o.Model[get.type]; | |
if (Model) { | |
Model.find(get.id).then(function(docs){ | |
var doc = docs[0]; | |
get.obj[get.key] = doc || get.id; | |
queueCount--; | |
if (queueCount <= 0) finished() | |
}) | |
}; | |
}); | |
return { | |
then: function(func){ | |
resolve = func; | |
} | |
,catch: function(func){ | |
reject = func; | |
} | |
} | |
}; | |
// mongoosejs | |
// http://mongoosejs.com/docs/api.html#document-js - compat pack here | |
o.Methods.Doc = docMethods; | |
o.Methods.DocModel = {}; | |
/** | |
* Main Document Class | |
* All other Classes will be instance of this Model | |
* And they will have any method from this one | |
* @param {String} collection Model name | |
*/ | |
o.Model.Doc = function(modelName, obj, cb) { | |
if (typeof obj == "object" && obj.__id) { | |
var id = obj.__id; | |
delete obj.__id; | |
} else { | |
counter ++; | |
var id = o._.to_id(o.opt.ID+counter); | |
} | |
var self = this; | |
var initial = { | |
Model: o.Model[modelName] | |
,modelName: modelName | |
,version: [] | |
,_: {body: {state: "*"}, type:"Doc", id: id} | |
,before: {} | |
,after: {} | |
}; | |
for (var prop in initial) { | |
o._.hidden(self, prop, initial[prop]); | |
} | |
Object.keys(o.Methods.Doc).forEach(function(method) { | |
o._.hidden(self, method, o.Methods.Doc[method]); | |
}); | |
if (o.Methods.DocModel[modelName]) { | |
Object.keys(o.Methods.DocModel[modelName]).forEach(function(method) { | |
o._.hidden(self, method, o.Methods.DocModel[modelName][method]); | |
}); | |
}; | |
for (var attr in obj) { // Doc-specific properties | |
self[attr] = obj[attr]; | |
}; | |
if (cb) self.save(cb); | |
}; | |
o.Model.ViewMethods = {}; | |
var viewMethods = {}; | |
applyEvents(viewMethods); | |
var clearFilters = function(filters){ | |
if (!(filters instanceof Array)) filters = [filters]; | |
var self = this; | |
filters.forEach(function(filter){ | |
self.filters = o.Set(self.filters).removeIf(function(item, i){ | |
if (!item) return true; | |
return item[0] == filter; | |
}) | |
}); | |
}; | |
// Default ViewMethods | |
o.Model.ViewMethods.query = function(query, next) { | |
if (typeof query != "object") query = {}; | |
if (!this.called) this.docs = this.Model.filter(query); | |
next(); | |
}; | |
o.Model.ViewMethods.limit = function(num, next) { | |
this.docs = this.docs.limit(num); | |
next(); | |
}; | |
o.Model.ViewMethods.select = function(fields, next) { | |
if (typeof fields == "string") fields = [fields]; | |
if (fields instanceof Array) { | |
this.docs = this.docs.map(function(item){ | |
var clone = o._.clone(item, true); | |
for (var prop in clone) { | |
if (fields.indexOf(prop) == -1) { | |
delete clone[prop]; | |
} | |
} | |
return clone; | |
}) | |
}; | |
next(); | |
}; | |
o.Model.ViewMethods.skip = function(num, next) { | |
if (!this.called) this.docs = this.docs.skip(num); | |
next(); | |
}; | |
o.Model.ViewMethods.slice = function(start, num, next) { | |
if (!this.called) this.docs = this.docs.slice(start, num); | |
next(); | |
}; | |
o.Model.ViewMethods.reverse = function(next) { | |
this.docs.reverse() | |
next(); | |
}; | |
o.Model.ViewMethods.first = function(next) { | |
this.docs = this.docs.first(); | |
next(); | |
}; | |
o.Model.ViewMethods.last = function(next) { | |
this.docs = this.docs.last(); | |
next(); | |
}; | |
o.Model.ViewMethods.sort_by = function(field, reverse, primer, next) { | |
if (!next && typeof reverse == "function") next = reverse; | |
if (!next && typeof primer == "function") { | |
next = primer; | |
primer = false; | |
} | |
this.docs = this.docs.sort_by(field, reverse, primer); | |
next(); | |
}; | |
o.Model.ViewMethods.orderBy = o.Model.ViewMethods.sort_by; | |
o.Model.ViewMethods.startAt = function(item, next){ | |
this.docs = this.docs.startAt(item); // include item | |
next() | |
}; | |
o.Model.ViewMethods.startAfter = function(item, next){ | |
this.docs = this.docs.startAt(item); // include item | |
this.docs.shift(); | |
next(); | |
}; | |
o.Model.ViewMethods.endAt = function(item, next){ | |
this.docs = this.docs.endAt(item); // include item | |
next() | |
} | |
o.Model.ViewMethods.endBefore = function(){ | |
this.docs = this.docs.endAt(item); // include item | |
this.docs.pop(); | |
next(); | |
} | |
o.Model.ViewMethods.removeMore = function(next) { | |
// if (this._.inMore) { | |
this.docs = this.Model.docs; | |
delete this.called; | |
delete this._.inMore; | |
this.filters = this._.backupFilters || this.filters; | |
delete this._.backupFilters | |
clearFilters.call(this, ["more","removeMore"]); | |
// return o.Model.ViewMethods.process(this, next) | |
this.docs = this.docs.slice(0,10); | |
this.change(); | |
next(); | |
// } else { | |
// next(); | |
// } | |
}; | |
o.Model.ViewMethods.more = function(num, reverse, next) { | |
if (!next && typeof num == "function") { | |
next = num; | |
num = false; | |
reverse = false; | |
}; | |
if (!next && typeof reverse == "function") { | |
next = reverse; | |
reverse = false; | |
} | |
var self = this; | |
// var lastdoc = self.docs[reverse?"first":"last"](); //.last()?? | |
var lastdoc = self.docs["last"](); //.last()?? | |
if (lastdoc) { | |
if (!self._.inMore) { | |
self._.inMore = true; | |
self._.backupFilters = o._.clone(self.filters); | |
}; | |
clearFilters.call(self, | |
["live","more", "startAt", "limit", "removeMore"]); | |
if (self.Model.docs.length > self.docs.length) { | |
var filters = o._.clone(self.filters); | |
self.Model.view(false, filters) | |
.startAfter(lastdoc).limit(num||20).then(function(docs){ | |
if (docs.length) { | |
// if (reverse) docs.reverse(); | |
// self.docs = reverse ? | |
// docs.concat(self.docs) : self.docs.concat(docs); | |
self.docs = self.docs.concat(docs); | |
// self.docs.forEach(function(item) { | |
// console.log(item.uid) | |
// }) | |
self.change() | |
next() | |
} else { | |
next() | |
} | |
}) | |
} | |
} else { | |
next(); | |
}; | |
}; | |
o.Model.ViewMethods.finally = function(next) { | |
if (this.firstOnly && this.docs instanceof Array) this.docs = this.docs[0]; | |
next(); | |
} | |
o.Model.ViewMethods.process = function(promise, resolve, reject) { | |
promise.docs = promise.docs || promise.Model.docs; | |
var queue = []; | |
if (promise.Model.ViewMethods.beforeStart) { | |
queue.push(promise.Model.ViewMethods.beforeStart) | |
} | |
promise.lastFilters = o._.clone(promise.filters); | |
for (var i = 0; i < promise.filters.length; i++) { | |
var fun = function(args, next) { | |
var argsNext = [].slice.call(args.args||[]).concat(next); | |
if (promise.Model.ViewMethods[args.filter]) { | |
promise.Model.ViewMethods[args.filter].apply(this, argsNext); | |
} | |
} | |
fun.args = { | |
filter: promise.filters[i][0] | |
,args: promise.filters[i][1] | |
}; | |
queue.push(fun) | |
}; | |
if (promise.Model.ViewMethods.finally) { | |
queue.push(promise.Model.ViewMethods.finally) | |
} | |
queue.push(function() { | |
promise.called = true; | |
// promise.change() | |
if (promise.errors.length) { | |
if (typeof reject == "function") { | |
reject.call(this, this.errors); | |
}; | |
if(promise._errorCatch) { | |
promise._errorCatch.call(promise, promise.errors) | |
} | |
} else { | |
if (typeof resolve == "function") { | |
resolve.call(this, this.firstOnly?this.docs:o.Set(this.docs)); | |
}; | |
}; | |
}) | |
o._.queue(queue, promise) | |
return promise; | |
}; | |
/** | |
* Main View Class | |
*/ | |
o.Model.View = function(Model, docs, filters) { | |
// var promise = new Promise(function(resolve, reject) { | |
// setTimeout(function(){ | |
// o.Model.processView(promise, resolve, reject); | |
// }, 0); | |
// }); | |
var promise = { | |
then: function(resolve, reject) { | |
return o.Model.ViewMethods.process(promise, resolve, reject) | |
} | |
,catch: function(error) { | |
// do something about that error | |
if (typeof error == "function") { | |
promise._errorCatch = error; | |
}; | |
return promise | |
} | |
}; | |
counter ++; | |
var initial = { | |
Model: Model | |
,modelName: Model.modelName | |
,version: [] | |
,_: {body: {state: "*"}, type:"View", id: counter} | |
,errors: [] | |
,before: {} | |
,after: {} | |
,filters: filters || [] | |
,docs: docs || false | |
}; | |
for (var prop in initial) { | |
o._.hidden(promise, prop, initial[prop]); | |
} | |
for (var method in viewMethods) { | |
promise[method] = viewMethods[method]; | |
}; | |
var methods = {}; | |
o._.extend(methods, Model.ViewMethods); | |
Object.keys(methods).forEach(function(method){ | |
promise[method] = function() { | |
promise.filters.push([method, arguments]) | |
return promise; | |
}; | |
}); | |
return promise | |
}; | |
/** | |
* DOC - Class and methods | |
*/ | |
var schemaMethods = {}; | |
schemaMethods.path = function(){}; | |
// prop: { | |
// type: String | |
// ,validate: f() - a function | |
// ,content: {} - Validate contents | |
// ,unique: true - If other exist | |
// ,default: "value" - Will fill with this - if nodug is present | |
// ,required: true - Will test du._.isEmpty() | |
// ,index: true - Create a index for this field ??? | |
// ,match: "value" || /value/ - Only will accept this value or test | |
// ,before: func(val) - will return the new value | |
// ,lowercase: true - Will lowerCase the content | |
// ,uppperCase: true - Will upperCase the content | |
// ,trim: true - Will trim the value before save | |
// ,enum: true - If the value being set is not in this array | |
// ,max: 30 - length | |
// ,min: 6 - length | |
// } - mixed | |
schemaMethods.validate = function(doc, scheme) { // return [err] or undefined | |
var scheme = (scheme || this.scheme); | |
if (!scheme) return undefined; | |
var all_errors = []; | |
var keys = Object.keys(scheme); | |
if (scheme["*"]) { | |
for (var attr in doc) { | |
scheme[attr] = scheme[attr] || scheme["*"]; | |
}; | |
delete scheme["*"]; | |
}; | |
for (var prop in scheme) { | |
var test = scheme[prop]; | |
//redefine values | |
for (var attr in test) { | |
var valid = test[attr]; | |
switch(attr) { | |
case "type": | |
if (du.opt.embedAsReference && typeof valid == "string" | |
&& typeof doc[prop] == "string") { | |
doc[prop] = o.Model._.embeddedID(doc[prop], valid) | |
} | |
break; | |
case "default": | |
if (typeof doc[prop] == "undefined") doc[prop] = valid; | |
break; | |
case "lowercase": | |
doc[prop] = doc[prop].toLowerCase(); | |
break; | |
case "uppercase": | |
doc[prop] = doc[prop].toUpperCase(); | |
break; | |
case "trim": | |
doc[prop] = doc[prop].trim(); | |
break; | |
case "before": | |
doc[prop] = valid.call(doc, doc[prop]); | |
break; | |
case "remove": | |
delete doc[prop]; | |
break; | |
default: | |
// if (/d/.test()) { | |
// } | |
break; | |
} | |
}; | |
//test values | |
var val = doc[prop]; | |
var undef = typeof doc[prop] == "undefined"; | |
var errors = []; | |
for (var check in test) { | |
var valid = test[check]; | |
var ok = true; | |
switch(check) { | |
case "type": | |
if (typeof valid == "string" && o.Model[valid]) { | |
if (undef) { // difficult to check | |
ok = true; | |
} else if (typeof val == "string") { | |
var isEmb = o.Model._.isEmbeddedID(val); | |
ok = isEmb instanceof Array | |
&& isEmb[0].toLowerCase() == valid.toLowerCase(); | |
} else if (typeof val == "object") { | |
ok = !!o.Model[valid].modelName && !!val.modelName; | |
} | |
} else { | |
ok = !undef && val.constructor === valid; | |
}; | |
break; | |
case "validate": | |
ok = valid.call(doc, val); | |
break; | |
case "unique": | |
ok = !this.Model.filter(doc.toObject()).length | |
break; | |
case "required": | |
ok = !undef; | |
break; | |
case "index": | |
// Model indexing | |
break; | |
case "match": | |
ok = valid instanceof RegExp? | |
new RegExp(valid).test(val): | |
o._.deepEqual(valid, val); | |
break; | |
case "max": | |
case "min": | |
var len = (typeof val == "number")?val:val.length; | |
ok = check == "min"? | |
len >= valid: | |
len <= valid; | |
break; | |
case "enum": | |
ok = valid.indexOf(val) != -1; | |
break; | |
case "content": | |
if (!undef) { | |
if (val instanceof Array) { | |
var content_errs = 0; | |
for (var i = 0; i < val.length; i++) { | |
if (isNative(valid)) { | |
if (val[i].constructor !== valid) content_errs ++; | |
} else if ( this.validate.call(this, val[i], valid) ) { | |
content_errs ++; | |
}; | |
}; | |
ok = !content_errs; | |
} else { | |
ok = this.validate.call(this, val, valid) | |
}; | |
} | |
break; | |
// default: | |
// break; | |
}; | |
if (undef && o.opt.allowUndefinedOnModel) ok = true; | |
if (!ok) errors.push(check) | |
}; | |
if (errors.length) all_errors.push({ | |
msg: o._.tmpl(test.msg || o.opt.defaultErrorMsg, { | |
PATH: prop | |
,VALUE: val | |
,TYPE: errors | |
}) | |
,errors: errors | |
,validation: test | |
,value: val | |
,prop: prop | |
}); | |
}; | |
return all_errors.length?all_errors:undefined; | |
}; | |
schemaMethods.parseScheme = function(scheme) { // normalize | |
for (prop in scheme) { | |
var test = scheme[prop]; | |
switch(typeof test) { | |
case "function": | |
if (isNative(test)) { | |
// String, Number, Date, Boolean, Object, Array | |
scheme[prop] = {type: test}; // constructor | |
} else { | |
// func() | |
scheme[prop] = {validate: test}; | |
}; | |
break; | |
case "object": | |
if (test instanceof Array) { | |
// [String] | |
scheme[prop] = { | |
type: Array | |
,content: schemaMethods.parseScheme(test[0]) | |
}; | |
} else if (test instanceof RegExp) { | |
// RegExp | |
scheme[prop] = {match: test}; | |
} else if (test === null) { | |
// Null | |
scheme[prop] = {match: test} | |
} else { | |
// Object | |
if (test.content) { | |
scheme[prop].content = schemaMethods.parseScheme(test.content); | |
} | |
} | |
break; | |
default: | |
// string, number, boolean, undefined, xml | |
scheme[prop] = {match: test} | |
break; | |
}; | |
}; | |
return scheme; | |
}; | |
// schemaMethods.__ = {event:{}} | |
o.Methods.Schema = schemaMethods; | |
/** | |
* Main Schema Class | |
*/ | |
o.Model.Schema = function(scheme, model) { | |
this.Model = model; | |
this.__ = {type: "Schema"} | |
for (method in o.Methods.Schema) { | |
this[method] = o.Methods.Schema[method]; | |
}; | |
this.scheme = this.parseScheme(scheme); | |
return this; | |
}; | |
/** | |
* MODEL - Class and methods | |
*/ | |
var modelMethods = {}; | |
applyEvents(modelMethods); | |
// // import du.Set methods | |
// Object.keys(o.Methods.Set).map(function(method){ | |
// modelMethods[method] = function() { | |
// return this.docs[method].apply(this.docs, arguments); | |
// } | |
// }); | |
// TODO: MongoDB style queries | |
// User.filter({age: {$gte: 21, $lte: 65}}, callback); | |
// we can instead write: | |
// User.where('age').gte(21).lte(65).exec(callback); | |
/** | |
* Overwriten by DB | |
*/ | |
var save = function(doc, next) { | |
var self = this; | |
var taintedDocs = []; | |
var queueCount = 0; | |
var scheme = doc.Model.schema.scheme | |
var transform = function(attr, key) { | |
if (o.Model._.isDoc(attr)) { | |
if (o.opt.autoSaveModified) { | |
if (attr.isModified()) { | |
queueCount++ | |
taintedDocs.push({doc: attr, obj: this, key: key}) | |
} | |
} | |
if (o.opt.embedAsReference) { | |
// if (!scheme[prop]) scheme[prop] = {}; | |
// scheme[prop].type = doc[prop].modelName | |
this[key] = o.Model._.embeddedID(attr); | |
} | |
} | |
}; | |
if (o.opt.autoSaveModified || o.opt.embedAsReference) { | |
du._.eachAll(doc, transform); | |
}; | |
var finished = function(){ | |
doc.validate(); | |
if (!doc.invalid) { | |
doc.version.unshift(doc.clone()); | |
doc.version.splice(o.opt.docVersionLimit || 1); | |
} else { | |
for (var i = 0; i < doc.invalid.length; i++) { | |
var prop = doc.invalid[i].prop | |
if (doc.version[0]) { // reset wrong values | |
doc[prop] = doc.version[0][prop]; | |
} else { | |
// var scheme_prop = doc.Model.schema.scheme[prop]; | |
// if (scheme_prop && scheme_prop.default) { | |
// // ?? necessary ?? to fill with default value | |
// } else { | |
delete doc[prop]; | |
// } | |
} | |
} | |
}; | |
if (typeof next == "function") next() | |
} | |
if (taintedDocs.length) { | |
taintedDocs.forEach(function(toSave){ | |
toSave.doc.save(function(err, doc){ | |
// o._.replaceObj(toSave[0], doc); | |
// self[toSave[0]] = doc || toSave[0]; | |
queueCount--; | |
if (queueCount <= 0) finished() | |
}) | |
}); | |
} else { | |
finished(); | |
} | |
}; | |
var change = function(doc) { | |
doc.change(); // notify _subscribers | |
doc.Model.change() // notify _subscribers | |
// if (o.render) o.render(); | |
}; | |
/////////////////////////////// | |
// overwrited in other DBs | |
modelMethods.save = function(doc, cb, args) { | |
if (!doc.invalid && !doc.added) { | |
this.docs.push(doc); | |
this.base[doc._.id] = doc; | |
o._.hidden(doc, "added", true); | |
this._.methods["afterSave"].apply(this, doc); | |
}; | |
callCB(cb, doc, args); | |
return doc; | |
}; | |
modelMethods.insert = function(doc, at){ | |
if (!doc.tp) doc = new this(doc); | |
if (at==undefined) at = this.docs.length; | |
this.docs.splice(at, 0, doc); | |
o._.hidden(doc, "added", true).save(); | |
return this; | |
}; | |
modelMethods.size = function() { | |
return this.docs.length; | |
}; | |
modelMethods.get = function(query, cb) { | |
if (typeof query == "number") { | |
return this.docs[query]; | |
} else if (typeof query == "string") { | |
return this.base[query]; | |
} else { | |
return o.Model.View(this, this.docs).query(query); | |
}; | |
}; | |
modelMethods.find = modelMethods.get; // alias | |
modelMethods.filter = function(query, not) { // models act as Node - filter | |
// return this.get(query, cb); | |
if (typeof query == "number") { | |
return this.docs[query]; | |
} else if (typeof query == "string") { | |
return this.base[query]; | |
} else { | |
var matches = this.docs; // It will be a o.Set() if present! | |
if (o.Model._.isDoc(query)) { // I know, but necessary for polyvalence | |
matches = [query]; | |
} else { | |
matches = matches.filter(function(doc){ | |
return checkDoc(doc, query, not) | |
}); | |
} | |
return matches; | |
} | |
}; | |
////////////////////// | |
modelMethods.view = function(docs, filters) { | |
return o.Model.View(this, docs, filters); | |
}; | |
modelMethods.clear = function(cb, args) { | |
this.docs.length = 0; | |
this.base = {}; | |
callCB(cb, this, args) | |
return this; | |
}; | |
modelMethods.delete = function(doc, cb, args) { // only for memory ORM | |
this.docs.splice(this.docs.indexOf(doc), 1) | |
callCB(cb, doc, args) | |
return this; | |
} | |
modelMethods.remove = function(query, cb, args) { // add or update | |
if (typeof query == "function") { | |
cb = query; | |
query = undefined; | |
}; | |
var self = this; | |
var resolve = function(){} | |
var callResolve = function(doc){ | |
callCB(cb, doc||self, args); | |
resolve.call(self, doc||self) | |
} | |
var lastly = function(docs){ | |
if (docs && docs.length) { | |
docs.forEach(function(doc, i) { | |
self.delete(doc) | |
if (i == docs.length-1) { | |
callResolve(docs.length==1?doc:self) | |
} | |
}) | |
} else { | |
callResolve(); | |
} | |
}; | |
setTimeout(function(){ | |
if (du.Model._.isDoc(query)) { | |
lastly([query]); | |
} else if (typeof query == "object" || o.Model._.isEmbeddedID(query)) { | |
self.get(query).then(lastly); | |
} else if (typeof query == "undefined") { | |
self.docs.clear(); | |
callResolve() | |
} | |
}, 0); | |
return { | |
then: function(func){ | |
resolve = func; | |
} | |
,catch: function(func){} | |
}; | |
}; | |
modelMethods.update = function(query, obj, cb, args) { // add or update | |
// safe (boolean) safe mode (defaults to value set in schema (true)) | |
// upsert (boolean) whether to create the doc if it doesn't match (false) | |
// multi (boolean) whether multiple documents should be updated (false) | |
// strict (boolean) overrides the strict option for this update | |
// overwrite (boolean) disables update-only mode, allowing you | |
// to overwrite the doc (false) | |
var docs = this.filter(query); | |
var errs = []; | |
if (docs.length) { | |
docs.forEach(function(doc, i) { | |
o._.extend(doc, obj); | |
doc.save(function(err){ | |
if (err) errs.push(err) | |
if (i == docs.length-1) { | |
if (errs.length) { | |
doc.errors = errs; | |
} else { | |
delete doc.errors | |
} | |
callCB(cb, this, args) | |
} | |
}) | |
}) | |
}; | |
return obj; | |
}; | |
modelMethods.upsert = function(query, obj, cb) { // add or update | |
var docs = this.filter(query); | |
if (docs.length) { | |
return this.update(query, obj, cb); | |
} else { | |
obj = new this(obj); | |
return obj.save(cb) | |
}; | |
}; | |
modelMethods.validate = function(doc){ | |
return this.schema.validate(doc); // return [errs] or undefined | |
}; | |
modelMethods.reset = function(doc) { | |
return o._.replaceObj(doc, (doc.version[0] || {_:doc._})); | |
}; | |
modelMethods.rollback = function(doc, cb, args) { | |
if (doc.version.length > 1) doc.version.shift(); | |
if (doc.version[0]) o._.replaceObj(doc, doc.version[0]); | |
return doc; | |
}; | |
modelMethods.add = function(obj, cb) { | |
return (new this(obj).save(cb)); | |
}; | |
modelMethods.create = modelMethods.add; | |
// TODO: index Model to quick find by col | |
// modelMethods.index = function(){} | |
// modelMethods.findBy - consumes index | |
// modelMethods.virtual - Achieved by ModelName.prototype.custom() | |
modelMethods.state = function(states, data) { // Interceptable?? | |
return o.state(states, data, this) | |
}; | |
Object.getOwnPropertyNames(Array.prototype).forEach(function(method){ | |
if(!(/(length|constructor|filter|find)/.test(method))) { | |
modelMethods[method] = function() { | |
return Array.prototype[method].apply(this.docs, arguments); | |
} | |
} | |
}) | |
modelMethods.toString = function() { | |
return this.modelName + ".docs has "+this.size()+" items"; | |
} | |
modelMethods.log = docMethods.log; | |
var checkDoc = function(obj, query, not) { | |
var matches = 0; | |
var counter = 0; | |
for ( prop in query) { | |
counter ++; | |
var type = typeof query[prop]; | |
switch (true) { | |
case (query[prop] instanceof RegExp): | |
//problem with global inverter - lastIndex - Create a new RegExp | |
if (new RegExp(query[prop]).test(obj[prop])) matches ++; | |
break; | |
case (type == "object"): | |
if (checkDoc(obj[prop], query[prop], not)) matches ++; | |
break; | |
case (type == "function"): | |
if (query[prop](obj[prop])) matches ++; | |
break; | |
default: | |
var scheme = ((obj.Model||{}).schema||{}).scheme||{}; | |
var schemeType = (scheme[prop]||{}).type | |
if (du.opt.embedAsReference && typeof schemeType == "string") { | |
query[prop] = du.Model._.embeddedID(query[prop], schemeType) | |
} | |
if (o._.deepEqual(obj[prop], query[prop])) matches ++; | |
}; | |
}; | |
var found = matches == counter; | |
return not?!found:found; | |
}; | |
modelMethods.findOne = function(query, not) { | |
return this.filter(query, not)[0]; | |
}; | |
modelMethods.not = function(query) { | |
return this.filter(query, true); | |
}; | |
modelMethods.all = function() { | |
return this.get({}); | |
}; | |
modelMethods.addView = function(method, methodFun) { | |
var model = this; | |
model.ViewMethods[method] = methodFun; | |
}; | |
modelMethods.addMethod = function(method, methodFun, notOnDoc) { | |
var model = this; | |
if (direct.indexOf(method) != -1 | |
|| typeof methodFun != "function") { | |
o._.hidden(model, method, model._.methods[method]) | |
// } else if (method == "view") { | |
// o._.hidden(model, method, model._.methods[method]) | |
} else if (method == "beforeSave" || method == "afterSave"){ | |
this._.methods[method] = methodFun; // they will triggered by | |
} else { | |
if (typeof methodFun == "function") { | |
model._.methods[method] = methodFun; | |
}; | |
o._.hidden(model, method, function() { | |
var arg = empty.slice.call(arguments, 0); | |
var args = direct.slice.call(arg, 0); | |
args.unshift(method); | |
this.trigger.apply(this, args) | |
var res, no; | |
if (method == "save" && this._.methods["beforeSave"]) { | |
var self = this; | |
no = true; | |
var argsNext = [arg[0], function(err){ // next | |
if (!err) model._.methods.save.apply(self, arg); | |
}]; | |
res = this._.methods["beforeSave"].apply(this, argsNext); | |
} else if (method == "save") { | |
save.call(this, arg[0]); | |
}; | |
if (!no) res = this._.methods[method].apply(this, arg); | |
if (method == "save" || method == "delete") change(arg[0]); | |
return res; | |
}); | |
} | |
if (!notOnDoc && typeof methodFun == "function") { | |
if (!o.Methods.DocModel[model.modelName]) { | |
o.Methods.DocModel[model.modelName] = {} | |
} | |
o.Methods.DocModel[model.modelName][method] = methodFun; | |
} | |
}; | |
modelMethods.before = {}; | |
modelMethods.after = {}; | |
// o.Set(["tp", "type"]).each(function(mtd){ | |
// modelMethods[mtd] = o.Methods.Set[mtd]; | |
// }); | |
o.Model.Connectors = {}; | |
o.Methods.Model = modelMethods; | |
o.Model.addMethod = function(method, methodFun){ | |
o.Methods.Model[method] = methodFun; | |
} | |
var direct = ["on","one","off","trigger","change","hasEvent"]; | |
/** | |
* Define a new Model, using doc as constructor | |
* @param {String} modelName Model Name | |
* @return {undefined} | |
*/ | |
o.Model.define = function(modelName, scheme, methods, otherMethods) { | |
modelName = modelName || "Default"; | |
var model = function(obj, cb) { | |
o.Model.Doc.call(this, modelName, obj, cb); | |
}; | |
model.docs = o.Set?o.Set():[]; | |
model.docs.__ = {mBox: true, Model: model}; | |
model.base = {}; | |
model.schema = new o.Model.Schema(scheme, model); | |
model.modelName = modelName; // .name is reserved :( | |
model.ViewMethods = o._.extend({}, o.Model.ViewMethods); | |
counter++; | |
model._ = {body: {state: "*"}, type: "Model", id: counter }; | |
model.prototype = Object.create(o.Model.Doc.prototype); | |
o._.hidden(model, "constructor", o.Model[modelName]); | |
// model.prototype.constructor = o.Model[modelName]; | |
du.Model[modelName] = model; | |
var methodsModel = o._.clone(o.Methods.Model); | |
if (typeof methods == "string") { | |
model.Connector = methods; | |
methods = o._.clone(o.Model.Connectors[methods]); | |
}; | |
if (typeof methods == "object") { | |
if (methods && typeof otherMethods == "object") { | |
o._.extend(methods, otherMethods) | |
} | |
if (!o.Methods.DocModel[modelName]) o.Methods.DocModel[modelName] = {}; | |
Object.keys(methods).forEach(function(exe) { | |
// o.Methods.Doc[exe] = methods[exe]; | |
if (typeof methods[exe] == "function") { | |
o.Methods.DocModel[modelName][exe] = function() { | |
arguments = empty.slice.call(arguments, 0); | |
arguments.unshift(this); | |
return this.Model[exe].apply(this.Model, arguments); | |
} | |
} | |
}) | |
if (methods.DocMethods) { | |
o._.extend(o.Methods.DocModel[modelName], methods.DocMethods); | |
delete methods.DocMethods; | |
}; | |
methods = o._.extend(methodsModel, methods); | |
} else { | |
methods = methodsModel; | |
} | |
if (methods.ViewMethods) { | |
o._.extend(model.ViewMethods, methods.ViewMethods); | |
delete methods.ViewMethods; | |
}; | |
if (methods.Model) { | |
o._.extend(methods, methods.Model); | |
delete methods.Model; | |
} | |
// if (!methods.beforeSave) methods.beforeSave = function(next){next()}; | |
if (!methods.afterSave) methods.afterSave = function(){}; | |
model._.methods = methods; | |
model.addMethod = o.Methods.Model.addMethod; | |
Object.keys(methods).forEach(function(method) { | |
if (method != "addMethod") { | |
o.Methods.Model.addMethod.call(model, method, methods[method], true) | |
}; | |
}); | |
return model; | |
}; | |
// shortcut for JMLs and React Style | |
var __States = o.Model.define("__States"); | |
o.Model.useState = function(val) { | |
var _state = new o.Model.__States({val: val}).save(); | |
return [ _state, function(newVal, callback) { | |
_state.attr("val", newVal).save(callback); | |
}]; | |
}; | |
})(du); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment