modelizerfork
Version:
An ORM-Mapper with a shared model between client and server for MongoDB
1,964 lines (1,532 loc) • 282 kB
JavaScript
require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
////// Micro libs ////
module.exports = {
assert : function (condition, message) {
if (!condition) {
console.log('Assertion failed', message);
console.trace();
throw new Error(message || "Assertion failed");
}
},
check : function(condition, message) {
if (!condition) {
throw new Error(message || "Check failed");
}
},
// http://stackoverflow.com/questions/679915/how-do-i-test-for-an-empty-javascript-object-from-json
isEmptyObject : function(obj) {
var name;
for (name in obj) {
return false;
}
return true;
}
};
},{}],2:[function(require,module,exports){
/**
* Modelizer - by Jonathan Haeberle
* https://github.com/dreampulse/modelizer
*
* This it the code, which is being shared between server and client
*
*/
var assert = require('./microlibs').assert;
var check = require('./microlibs').check;
var isEmptyObject = require('./microlibs').isEmptyObject;
var Q = require('q');
//var _ = require('lodash');
// note: browserify will replace this ObjectId implementation by 'require("objectid-browser")' for the client
var ObjectId = require('./objectid');
//////////////////////////
// The Model implementation
var Model = function(modelName, schema) {
this.self = this;
this.modelName = modelName;
this.schema = schema;
// im scope der Model-Methode speichere ich alles was ich dem Modell übergeben kann
// also Attribute, operationen usw..
// die Funktionen hierfür sehen eigentlich immer gleich auch
// deswegen sollte das auch in ein meta-modell wandern
this.attrs = {};
// a reference to another model
this.attrRefs = {};
// a reference to another model
this.attrObjs = {};
// an inline object
this.methods = {};
this.methodImpls = {};
this.virtualAttrs = {};
this.virtualAttrImpls = {};
this.attrArrays = {};
this.attrRefArrays = {};
this.attrLinks = {}; // TODO
this.operations = {};
this.operationImpls = {};
this.factorys = {};
this.factoryImpls = {};
// setup filters for using the model
this.readFilters = [];
this.afterReadFilters = function() { }; // only one
// setup write filters for using the model
this.writeFilters = [];
/////////////////
// Constructing
// 'execute' schema definition
if (this.schema) { // a schema was provided
this.processSchema(this.schema);
}
// set connection for all models
if (Model.globalConnection) {
this.connection(Model.globalConnection);
}
// set express for all models
if (Model.globalExpress) {
this.express(Model.globalExpress);
this.serve();
}
// init sample Server
if (Model.simpleServer) {
console.log("init simple server for", this.modelName);
// say that our model should use express and the database connector
this.connection(Model.simpleServer.connector);
this.express(Model.simpleServer.app);
this.serve();
}
return this;
};
///////
// Der Store
/// Problem: ich will eigentlich Mengen abonieren
// das geht aber nicht so einfach
// couchdb anschauen...
// alle objekte werden hier gecached
// TODO: an remove denken -> geht nicht in js
Model.prototype.store = (function(){
var store = {}; // private store
var watchers = {};
// sets of objects
var all = [];
var find = {}; // set of array with this results
// änderungen können dann per websocket immer rein kommen
// ist eigentlich ein abo an mengen...
var updateSets = function() {
// clean array
// http://stackoverflow.com/questions/1232040/how-to-empty-an-array-in-javascript
all.length = 0;
for (var i in store) {
all.push(store[i]);
}
}
// todo use getters & setters
return {
set : function(obj) {
assert(typeof obj === 'object', 'obj has to be an object');
assert(obj.hasOwnProperty("_id"), 'obj has no id');
// assume ObjectID right now!
var id = obj._id.toString();
if (store.hasOwnProperty(id)) { // this object already exists
for (var i in obj) { // copy values
store[id][i] = obj[i];
}
updateSets(); // TODO: performace only do that if object changed (later)
if (watchers.hasOwnProperty(id)) { // call watcher
watchers[id](obj);
}
} else {
assert(false, "actually no problem, but get() should be called before set() - in this case ;-)");
store[id] = new Object();
}
},
get : function(id) { // implements a get or create
//assert(typeof id === 'string', 'id has to be a string');
id = id.toString();
if (store.hasOwnProperty(id)) { // there is that object
return store[id];
} else { // create a new object
store[id] = new Object();
return store[id];
}
},
del : function(id) {
//assert(typeof id === 'string', 'id has to be a string');
id = id.toString();
if (store.hasOwnProperty(id)) { // there is that object
delete store[id];
}
updateSets();
if (watchers.hasOwnProperty(id)) { // call watcher
watchers[id](undefined);
}
},
all : function() {
return all;
},
addWatcher : function(id, callback) {
//assert(typeof id === 'string', 'id has to be a string');
id = id.toString();
watchers[id] = callback; // todo können auch mehere sein
},
removeWatcher : function(id) {
//assert(typeof id === 'string', 'id has to be a string');
id = id.toString();
delete watchers[id]; // todo können auch mehere sein
}
}
})();
// "Subclasses"
///////////////////////////
// Methods
Model.prototype.attr = function(attrName) {
var filters = [];
for (var i in arguments) {
if (i != '0') // except for attrName
filters.push(arguments[i]);
}
this.attrs[attrName] = {name : attrName, filters : filters};
return this;
};
Model.prototype.attrRef = function(attrName, reference) {
this.attrRefs[attrName] = reference;
return this;
};
Model.prototype.attrObj = function(attrName, obj) {
this.attrObjs[attrName] = obj;
return this;
};
Model.prototype.method = function(methodName) {
this.methods[methodName] = methodName;
return this;
};
Model.prototype.methodImpl = function(methodName, fnImpl) {
this.methodImpls[methodName] = fnImpl;
return this;
};
Model.prototype.virtualAttr = function(attrName, attrType) {
this.virtualAttrs[attrName] = {name : attrName, type : attrType};
return this;
};
Model.prototype.virtualAttrImpl = function(virtualAttrName, fnImpl) {
this.virtualAttrImpls[virtualAttrName] = fnImpl;
return this;
};
Model.prototype.attrArray = function(attrName, obj) {
this.attrArrays[attrName] = obj;
return this;
};
Model.prototype.attrLink = function(linkName, model, link) {
this.attrLinks[linkName] = {
model : model,
link : link
};
return this;
};
Model.prototype.attrRefArray = function(attrName, obj) {
this.attrRefArrays[attrName] = obj;
return this;
};
Model.prototype.operation = function(operationName) {
this.operations[operationName] = operationName;
this[operationName] = function(params) {
assert(this.collection != undefined, "Use a connector!");
return this.callOpQ(operationName, params, null);
};
return this;
};
Model.prototype.operationImpl = function(operationName, fnImpl) {
this.operationImpls[operationName] = fnImpl;
return this;
};
Model.prototype.factory = function(factoryName) {
this.factorys[factoryName] = factoryName;
this[factoryName] = function(params) {
return this.callOpQ(factoryName, params, null);
};
return this;
};
Model.prototype.factoryImpl = function(factoryName, fnImpl) {
this.factoryImpls[factoryName] = fnImpl;
return this;
};
// setup connection stuff
Model.prototype.connection = function(connector) {
assert(connector !== undefined, "Don't set a undefined connection!");
this.connector = connector;
this.collection = connector(this);
};
Model.prototype.link = function(model, linkModel) {
var object;
var subObject;
this.set = function(obj, link) {
//assert(obj instanceof model, "The object is not a instance of the right model");
//assert(link instanceof linkModel, "You linked to a model with the wrong type");
assert(link._id, "the target object has no _id");
object = obj;
subObject = link;
this._link = link._id;
};
this.init = function(obj) {
object = obj;
};
this.ref = function() {
if (object && this._link) {
var res = object._childObjs[this._link];
assert(res, "link missing in lookup-table");
return res;
} else {
throw new Error("Link not initialized");
}
}
};
// simuliert das verhalten einer referenz auf ein anderes Object
// Zugriff dann mit object.ref().XXX
Model.prototype.reference = function(refModel, parentModel) {
// das weiterreichn wollte macht irgendwie probleme (sollte eigentlich auch ohne funktionieren) TODO
//refModel.connection(parentModel.connector); // mongodb connection an child modell durchreichen
this.create = function() {
var refObj = refModel.create();
this.ref = function() { return refObj; } // definition von der .ref()-Methode
return this.ref();
}
// load object
// todo: soll erst verfügbar sein, wenns auch was zum laden gibt
this.loadQ = function() {
var loadScope = this;
var deferred = Q.defer();
if (this._reference === undefined) {
deferred.reject(new Error("Reference not set! Don't now what to load!"));
return deferred.promise;
}
return refModel.getQ(this._reference)
.then(function(obj) {
var refObj = obj;
loadScope.ref = function() { return refObj; } // definition von der .ref()-Methode
return refObj;
});
};
this.load = this.loadQ;
// set an existing object to the reference
// TODO: type check -> geht garnicht so einfach (object müsste wissen zu welchem modell es gehört)
this.setObject = function(obj) {
var refObj = obj;
if (obj._id === undefined) {
throw new Error("Save object first, then set object reference!");
}
this.ref = function() { return refObj; }
this._reference = obj._id;
}
};
Model.prototype.arrayReferenceRoot = function(refModel, obj, arrayName) {
var theArryRef = obj[arrayName];
assert(Array.isArray(theArryRef));
var self = this;
theArryRef.iterate = function(callback) {
var loadPromises = [];
for (var i=0; i<theArryRef.length; i++) {
loadPromises.push(
theArryRef[i].loadQ()
.then(function (el) {
return callback(el);
})
)
}
return Q.all(loadPromises);
};
theArryRef.create = function() {
return self.createArrayReferenceElement(refModel, obj, arrayName)();
};
};
// Many-Referenzen (das laden aus der DB)
Model.prototype.arrayReference = function(refModel, parentModel) {
//refModel.connection(parentModel.connector); // mongodb connection an child modell durchreichen
this.loadQ = function() { //TODO: ist ja genau das selbe Load wie beim reference!!
var loadScope = this;
var deferred = Q.defer();
if (this._reference === undefined) {
deferred.reject(new Error("Reference not set! Don't now what to load!"));
return deferred.promise;
}
return refModel.getQ(this._reference)
.then(function(obj) {
var refObj = obj;
loadScope.ref = function() { return refObj; } // definition von der .ref()-Methode
return refObj;
});
};
this.load = this.loadQ;
};
// verhalten von Many-Referenzen (erstellen eines neuen objects)
Model.prototype.createArrayReferenceElement = function(refModel, obj, arrayName) {
var self = this;
// Das hier ist die Funktion die ein neue Array-Reference anlegt
return function() {
var refObj = refModel.create();
// Das Reference Array Element zusammen bauen
var el = new self.arrayReference(refModel, self);
el.ref = function() { return refObj; };
var array = obj[arrayName];
array.push(el);
return el.ref();
}
}
Model.prototype.createArrayElement = function(arrayModel, obj, arrayName, rootObject) {
// Das hier ist die Funktion um ein neues Array Element anzulegen
return function() {
var el = arrayModel._initObject(rootObject); // creates the new element
// add an unique id for each object in the array,
// to enable linking to that object
el._id = ObjectId();
// add the element to the lookup-table
rootObject._childObjs[el._id] = el;
var array = obj[arrayName];
array.push(el); // add the element to the array
return el;
}
}
/// Erstellt alles was für den Datenbankzugriff benötigt wird
// def was ich alles mit dem obj tun kann
Model.prototype._addStore = function(obj) {
var self = this;
var prepareSave = function(obj, model) {
var doc = {}; // the target document to sore
if (obj.hasOwnProperty("_id")) {
doc._id = obj._id; // copy _id
}
// copy attrObjs
for (var i in model.attrObjs) {
check(typeof obj[i] == 'object', "Object attribute '"+ i +"' not provided in your object (model '"+model.modelName+"')");
doc[i] = prepareSave(obj[i], model.attrObjs[i]);
}
// copy attrArrays
for (var i in model.attrArrays) {
check(Array.isArray(obj[i]), "Array '"+ i +"' is not correctly provided in your object (model '"+model.modelName+"')");
doc[i] = [];
for (var j in obj[i]) {
doc[i][j] = prepareSave(obj[i][j], model.attrArrays[i]);
}
}
// apply attribute filters (eg. for type check..)
for (var i in model.attrs) {
// if (!obj.hasOwnProperty(model.attrs[i].name)) { // check for error in usage!
// console.log("Warning: Attribute '"+ model.attrs[i].name +"' not provided in your object (model '"+model.modelName+"')");
// console.log(" Attribute will be set to null! Use '"+model.modelName+".create()' to avoid this problem!");
// obj[model.attrs[i].name] = null;
// }
check(obj.hasOwnProperty(model.attrs[i].name), "Attribute '"+ model.attrs[i].name +"' not provided in your object (model '"+model.modelName+"')");
doc[model.attrs[i].name] = obj[model.attrs[i].name]; // copy attribute
try {
for (var j=0; j<model.attrs[i].filters.length; j++) {
doc[model.attrs[i].name] = model.attrs[i].filters[j](doc[model.attrs[i].name], 'save'); // call filter
}
} catch (err) {
err.message = "Can't save '"+model.attrs[i].name+"' " + err.message;
throw err;
};
obj[model.attrs[i].name] = doc[model.attrs[i].name]; // copy back to obj
}
// Speichern (vorbereiten) von Referenz-Attributen
for (var i in model.attrRefs) {
check(obj[i] instanceof Object, i + " not correctly provided");
//doc[i] = obj[i]; // copy ref-attr
doc[i] = {};
if (obj[i]._reference) { // es ist gerade nicht geladen, hat aber eine _reference-id
doc[i]._reference = obj[i]._reference;
}
if (obj[i].ref !== undefined && obj[i].ref()._id !== undefined) { // wenn das referenzierte Object existiert und bereits gespeichert wurde
obj[i]._reference = obj[i].ref()._id; // die reference id an das object geben
doc[i]._reference = obj[i].ref()._id; // die reference id mit persisieren
}
//obj[i] = doc[i]; // copy back to obj
}
// Speichern (vorbereiten) von Link-Attributen
for (var i in model.attrLinks) {
doc[i] = {};
if (obj[i]._link) {
doc[i]._link = obj[i]._link;
}
}
// Speichern (vorbereiten) von Referenz-Arrays
for (var i in model.attrRefArrays) { // über alle Referenz Arrays die in meinem Modell vorkommen
check(obj[i] instanceof Object, i + " not correctly provided");
doc[i] = []; // copy only the references
var refArray = obj[i];
for (var j=0; j<refArray.length; j++) { // über die einzelnen Elemente dieses Arrays
var ref = refArray[j];
if (obj[i][j]._reference) { // es ist gerade nicht geladen, hat aber eine _reference-id
doc[i][j] = {};
doc[i][j]._reference = obj[i][j]._reference;
}
if (ref !== undefined && ref.ref !== undefined && ref.ref()._id !== undefined) { // wenn das referenzierte Object existiert und bereits gespeichert wurde
obj[i][j]._reference = ref.ref()._id; // die reference id im object speichern
doc[i][j] = {};
doc[i][j]._reference = ref.ref()._id; // die reference id mit persisieren
}
}
}
return doc;
}
// The Save-function for a object instance
obj.saveQ = function() {
var deferred = Q.defer();
try {
var doc = prepareSave(obj, self);
} catch (e) {
deferred.reject(e);
return deferred.promise;
}
// transform the object to a document
// and apply attribute filters
return self.saveQ(doc) // store the document
.then(function(resDoc) {
// Achtung: client gibt hier ein string zurück
if (resDoc != 1) { // doc is 1 if an insert was performed
obj._id = resDoc._id; // store the generated Mongo-ObjectId to the local context
}
return obj;
});
};
obj.save = obj.saveQ; // compatibility
obj.removeQ = function() {
var deferred = Q.defer();
if (obj._id === undefined) {
deferred.reject(new Error("Object seems not to been saved so far!"));
return deferred.promise;
}
return self.removeQ(obj._id)
.then(function() {
delete obj._id;
deferred.resolve();
return deferred.promise;
})
.fail(function(err) {
deferred.reject(err);
return deferred.promise;
});
};
obj.remove = obj.removeQ; // compatibility
obj.validate = function(attr) {
try {
for (var i=0; i<self.attrs[attr].filters.length; i++) {
self.attrs[attr].filters[i](obj[attr], 'validate'); // call filter
}
} catch (err) {
return err.message;
};
return true;
};
return obj;
};
// Erstellt ein neues Object aus dem Modell
Model.prototype.create = function(initValues) {
var obj = this._initObject();
obj = this._addStore(obj);
// lookup table of child objects
obj._childObjs = {};
obj.getChild = function(id) {
return obj._childObjs[id];
}
if (initValues) {
for (var i in initValues) {
obj[i] = initValues[i];
}
}
return obj;
};
// Create an empty collection of Modelizer Objects
// and add some nice functions
Model.prototype.createCollection = function() {
var self = this;
var coll = [];
coll.create = function() {
var el = self.create();
coll.push(el);
};
coll.saveQ = function() {
for (var i=0; i<coll.length; i++) {
coll[i].saveQ().done();
}
}
return coll;
}
// Diese Funktion fügt alles aus dem Modell in ein neues Object hinzu
Model.prototype._initObject = function(rootObject) {
var obj = new Object(); // das wird usere Modell-Instanz
rootObject = rootObject || obj;
// create the attributes
for(var i in this.attrs) {
obj[this.attrs[i].name] = null;
}
for (var i in this.virtualAttrs) {
obj[this.virtualAttrs[i].name] = null;
}
// create Attribute Objects (a sub structure)
for(var i in this.attrObjs) {
var attrObj = this.attrObjs[i]._initObject(rootObject);
obj[i] = attrObj;
}
// create a reference to another model
for(var i in this.attrRefs) {
var modelRef = this.attrRefs[i];
obj[i] = new this.reference(modelRef, this);
}
// create a links to objects
for(var i in this.attrLinks) {
var link = this.attrLinks[i];
obj[i] = new this.link(link.model, link.link);
}
// create a reference Array to another model
for(var i in this.attrRefArrays) {
var modelRef = this.attrRefArrays[i]; // Das Model auf das referenziert wird
var arrayName = i;
obj[i] = []; // init with empty array
this.arrayReferenceRoot(modelRef, obj, i); //init arrayRef helper functions
arrayName = arrayName[0].toUpperCase() + arrayName.substr(1);
obj["create" + arrayName] = this.createArrayReferenceElement(modelRef, obj, i);
}
// create the methods
for(var i in this.methods) {
// obj[i] = function() {
// return this.callMethod(i, arguments);
// }
obj[i] = this.methodImpls[i];
}
// // create the virtualAttrs
// for (var i in this.virtualAttrs) {
// obj[i] = this.virtualAttrImpls[i](obj);
// }
// TODO: es ist unklar wann am besten der Wert für ein virtueles Attribut brechnet wird
// create stuff for attrArrays
for (var i in this.attrArrays) {
obj[i] = [];
var arrayName = i;
arrayName = arrayName[0].toUpperCase() + arrayName.substr(1);
obj["create" + arrayName] = this.createArrayElement(this.attrArrays[i], obj, i, rootObject);
//obj[i].create = this.createArrayElement(this.attrArrays[i], obj, i); // add create method directly on the array
}
return obj;
};
Model.prototype.readFilter = function(fn) {
this.readFilters.push(fn);
};
Model.prototype.afterReadFilter = function(fn) {
this.afterReadFilters = fn;
}
// create the filter hash (mongo search string)
Model.prototype._getReadFilter = function(req) {
var res = {};
var bool_res = null;
var promises = [];
for (var i=0; i<this.readFilters.length; i++) { // alle filter
promises.push(Q(this.readFilters[i](req))
.then(function(filter) {
if (filter === false) bool_res = false;
if (filter === true) bool_res = true;
// copy content
for (var j in filter) {
res[j] = filter[j];
}
}))
}
return Q.all(promises)
.then(function() {
if (bool_res === null)
return res
else return bool_res;
})
}
Model.prototype.writeFilter = function(fn) {
this.writeFilters.push(fn);
}
// create the filter hash (mongo search string)
Model.prototype._getWriteFilter = function(obj, req) {
var res = {};
var bool_res = null;
var promises = [];
for (var i=0; i<this.writeFilters.length; i++) { // alle filter
promises.push(Q(this.writeFilters[i](obj, req))
.then(function (filter) {
if (filter === false) bool_res = false;
if (filter === true) bool_res = true;
// copy content
for (var j in filter) {
res[j] = filter[j];
}
}))
}
return Q.all(promises)
.then(function() {
if (bool_res === null)
return res
else return bool_res;
})
}
// Using the model (static functions)
Model.prototype.loadFromDoc = function(doc, initObj) {
var rootObj = initObj || this.create();
// TODO: das hier überschreibt ja alle Methoden
// da muss ich mir was besseres überlegen
var copy = function(obj, doc, model) {
if (!doc) { // doc missing
doc = {}; // use empty one
if (!obj) {
obj = {};
}
}
for (var i in doc) { // kopiere alles was von der Datenquelle kommt
obj[i] = doc[i]; // todo remove
}
for (var i in model.attrs) {
obj[i] = doc[i];
}
// copy Attribute Objects (a sub structure)
for(var i in model.attrObjs) {
copy(obj[i], doc[i], model.attrObjs[i]); // recursive
}
// create stuff for attrArrays
for (var i in model.attrArrays) {
if (doc[i] === undefined || doc[i] === null) {
obj[i] = [];
}
// jedes element kopieren
for (var j=0; j<doc[i].length; j++) {
copy(obj[i][j], doc[i][j], model.attrArrays[i]);
}
var arrayName = i;
arrayName = arrayName[0].toUpperCase() + arrayName.substr(1);
obj["create" + arrayName] = model.createArrayElement(model.attrArrays[i], obj, i, rootObj);
// obj[i].create = this.createArrayElement(this.attrArrays[i], obj, i); // add create method directly on the array
}
// TODO: hier kopierere ich teilweise _initObject verhalten (-> kapseln )
// TODO: big todo!!
// hier kümmere ich mich um die Referenzen
for(var j in model.attrRefs) {
var modelRef = model.attrRefs[j];
var ref_id = obj[j]._reference;
obj[j] = new model.reference(modelRef, model);
obj[j]._reference = ref_id;
}
for(var j in model.attrLinks) {
var link = model.attrLinks[j];
var link_id = obj[j]._link;
obj[j] = new model.link(link.model, link.link);
if (link_id) {
obj[j]._link = link_id;
}
}
// hier kümmere ich mich um Referenz-Arrays
for(var j in model.attrRefArrays) {
var modelRef = model.attrRefArrays[j];
var refArray = obj[j];
assert(typeof refArray.length === 'number');
for (var k=0; k<refArray.length; k++) {
var ref = refArray[k];
var ref_id = ref._reference; // _reference retten
obj[j][k] = new model.arrayReference(modelRef, model);
obj[j][k]._reference = ref_id; // zurück speichern
}
model.arrayReferenceRoot(modelRef, obj, j);
}
// TODO: kopieren von anderen Methoden die durch das kopieren oben überschrieben werden
// store a reference of this object in the lookup-table
if (doc._id && doc._id != rootObj._id) { // but don't store the rootObj
rootObj._childObjs[doc._id] = obj;
}
};
copy(rootObj, doc, this);
return rootObj;
};
// todo: conflict with angular-client
Model.prototype.$get = function(id) {
var self = this;
this.getQ(id)
.then(function(obj) {
self.store.set(obj);
})
.fail(function(err) {
if (err.message == "Object not found!") {
self.store.del(id);
}
});
return this.store.get(id); // todo: was tun wenns id nicht gibt -> fail
};
Model.prototype.getQ = function(id, initObj) {
var self = this;
var deferred = Q.defer();
self.collection.findOne({_id:id}, function(err, doc) {
if (err) {
deferred.reject(err);
return;
}
if (doc === null) {
deferred.reject(new Error("Object not found!"));
return;
}
var obj = self.loadFromDoc(doc, initObj);
Q(self.afterReadFilters(obj))
.then(function () {
deferred.resolve(obj);
})
});
return deferred.promise;
};
Model.prototype.get = Model.prototype.getQ;
Model.prototype.findOneQ = function(search, initObj) {
var self = this;
var deferred = Q.defer();
self.collection.findOne(search, function(err, doc) {
if (err) {
deferred.reject(err);
return;
}
if (doc === null) {
deferred.reject(new Error("Object not found!"));
return;
}
var obj = self.loadFromDoc(doc, initObj);
Q(self.afterReadFilters(obj))
.then(function () {
deferred.resolve(obj);
});
});
return deferred.promise;
};
Model.prototype.findOneQ = Model.prototype.findOne;
Model.prototype.findQ = function(search, initObj) {
var self = this;
var deferred = Q.defer();
self.collection.find(search, function(err, docs) {
if (err) return deferred.reject(err);
var objs = initObj || self.createCollection();
var promises = [];
// für jedes document in der DB ein object anlegen
for (var i=0; i<docs.length; i++) {
(function() { // extra closure für obj
var doc = docs[i];
var obj = self.loadFromDoc(doc);
promises.push(
Q(self.afterReadFilters(obj))
.then(function () {
objs.push(obj);
})
);
})();
}
return Q.all(promises)
.then(function() {
deferred.resolve(objs);
})
});
return deferred.promise;
};
Model.prototype.find = Model.prototype.findQ;
Model.prototype.$all = function() {
return this.store.all();
}
Model.prototype.allQ = function(initObj) {
return this.findQ({}, initObj);
};
Model.prototype.all = Model.prototype.allQ;
Model.prototype.saveQ = function(obj) {
var deferred = Q.defer();
assert(this.collection != undefined, "connection no set for " + this.modelName);
this.collection.save(obj, function(err, doc) {
if (err) {
deferred.reject(err);
return;
}
deferred.resolve(doc);
});
return deferred.promise;
};
Model.prototype.save = Model.prototype.saveQ;
Model.prototype.removeQ = function(id) {
var deferred = Q.defer();
this.collection.remove({_id:id}, true, function(err, result) {
if (err) {
deferred.reject(new Error('Failed to remove document!'));
return;
}
if (result.status == "OK") { // success from a client call
deferred.resolve();
return;
}
// TODO: das hat wohl was mit der express send funktion zu tun, wenn parameter fehlen
// Hinweis: MongoDB unter windows liefert ein anderes 'result'.. unklar warum
if (result === 1 || (result.hasOwnProperty('n') && result.n == 1)) { // removed sucessfull
deferred.resolve();
return;
}
if (result === 0 || ( result.hasOwnProperty('n') && result.n.hasOwnProperty('n') && result.n.n == 0 ) ) {
deferred.reject(new Error('No document with this id found!'));
return;
}
deferred.reject(new Error("Don't know what was the result of removing the object"));
return;
});
return deferred.promise;
};
Model.prototype.remove = Model.prototype.removeQ;
Model.prototype.callOpQ = function(operationName, params, HTMLrequest) {
return this.collection.callOperation(operationName, params, HTMLrequest);
};
Model.prototype.callOp = Model.prototype.callOpQ;
// serialize doc
Model.prototype._transform = function(model, doc, method) {
// handle attributes
for (var i in model.attrs) {
for (var j=0; j<model.attrs[i].filters.length; j++) {
doc[model.attrs[i].name] = model.attrs[i].filters[j](doc[model.attrs[i].name], method); // call pack/unpack filter
}
}
// pack attrObjs
for (var i in model.attrObjs) {
this._transform(model.attrObjs[i], doc[i], method);
}
// copy attrArrays
for (var i in model.attrArrays) {
for (var j in doc[i]) {
this._transform(model.attrArrays[i], doc[i][j], method);
}
}
// remove obj lookup-table
delete doc['_childObjs'];
}
Model.prototype.processSchema = function(schema) {
assert(typeof schema == 'object', "Error in Schema ("+this.modelName+") definition (has to be a Hash)");
for (var entry in schema) {
var value = schema[entry];
//console.log("value", value);
// it is a attrArray
if (Array.isArray(value)) {
assert(value.length == 1, "Only one element in " + entry + " allowed");
var attrArrayModel = new Model(entry, value[0]);
this.attrArray(entry, attrArrayModel);
} else if (value instanceof Model) {
this.attrObj(entry, value);
// it is a attribute
} else if (value._what == 'attr') {
this.attr(entry, value.filter);
// it is a virutal attribute
} else if (value._what == 'virtualAttr') {
this.virtualAttr(entry);
// it is a reference to another model
} else if (value._what == 'attrRef') {
this.attrRef(entry, value.ref);
// it is a link to an object
} else if (value._what == 'attrLink') {
this.attrLink(entry, value.model, value.link);
// it is a many Reference to another model
} else if (value._what == 'attrRefArray') {
this.attrRefArray(entry, value.ref);
// it is an array with objects
} else if (value._what == 'attrObjArray') {
this.attrArray(entry, value.ref);
// it is a operation definition
// todo parameter validation
} else if (value._what == 'operation') {
this.operation(entry);
} else if (value._what == 'method') {
this.method(entry);
this.methodImpl(entry, value.impl);
} else if (value._what == 'factory') {
this.factory(entry);
// i assume in this cases that it is an 'attrObj' (nested object)
} else {
var attrObjModel = new Model(entry, value);
this.attrObj(entry, attrObjModel);
}
}
};
///////////////////////////
// Helpers for Schema definition
// Usage eg: Attr(Types.String, Attr.default("foo"), ..)
Model.Attr = function() {
var filters = arguments;
return {
'_what' : 'attr',
filter: function(value, action) {
for (var i in filters) {
value = filters[i](value, action); // call all filters
}
return value;
}
};
};
Model.VirtualAttr = function() {
return {
'_what' : 'virtualAttr'
};
};
Model.Ref = function(reference) {
return {
'_what' : 'attrRef',
ref : reference
};
};
Model.Link = function(model, link) {
return {
'_what' : 'attrLink',
model : model,
link : link
};
};
Model.RefArray = function(reference) {
return {
'_what' : 'attrRefArray',
ref : reference
}
};
Model.ObjArray = function(reference) {
return {
'_what' : 'attrObjArray',
ref : reference
}
};
Model.Operation = function() {
return {
'_what' : 'operation'
}
};
Model.Factory = function() {
return {
'_what' : 'factory'
}
};
Model.Method = function(impl) {
return {
'_what' : 'method',
impl : impl
}
};
///////////////////////////
///////////////////////////
// Filters and Type checks
// define a default value for a attribute
Model.Attr.default = function(def) {
return function(value, action) {
if (value == undefined || value == null) {
value = def;
}
return value;
}
};
Model.Attr.required = function(def) {
return function (value) {
if (value === undefined || value === null || value === "") {
throw new Error("Value has to be not null");
}
return value;
}
};
Model.Attr.Types = {
// define a string type
string : function(value, action) {
if (typeof value != 'string' && value !== undefined && value !== null) {
throw new Error("'"+value+"' is not a string value");
}
return value;
},
// define a number type
number : function(value, action) {
if (typeof value != 'number' && value !== undefined && value !== null) {
throw new Error("'"+value+"' is not a number");
}
return value;
},
boolean : function(value, action) {
if (typeof value != 'boolean' && value !== undefined && value !== null) {
throw new Error("'"+value+"' is not a boolean");
}
return value;
},
date : function(value, action) {
if (action == 'pack') {
if (value !== undefined && value !== null && value instanceof Date) {
return value.toISOString();
} else {
return value;
}
}
if (action == 'unpack') {
if (value !== undefined && value !== null) {
return new Date(value);
} else {
return value;
}
}
if (!(value instanceof Date) && value !== undefined && value !== null) {
throw new Error("'"+value+"' is not a date");
}
return value;
},
array : function(value, action) {
if (!Array.isArray(value) && value !== undefined && value !== null) {
throw new Error("'"+value+"' is not an array");
}
return value;
},
ObjectId : function(value, action) {
return value;
},
// define a enumeration type
enum : function() {
var enums = {};
for (var i in arguments) {
enums[arguments[i]] = true;
}
return function(value, action) {
if (!enums[value] && value !== undefined && value !== null) {
throw new Error("'"+value+"' is not in the enum");
}
return value;
}
}
};
///////////////////////////
// all implementieren
// eigentlich ist model-def ein aspekt
// und von den aspekten gibts viele
module.exports = Model;
/*
if (typeof window === 'undefined') {
module.exports = Model;
} else {
window.require = function(content) {
if (content.indexOf('modelizer') != -1) return Model;
}
}
*/
},{"./microlibs":1,"./objectid":"H6+VjG","q":"qLuPo1"}],"tVRSAQ":[function(require,module,exports){
/**
* The Client implementation of Modelizer
*
* run:
* browserify ./lib/modelizer-client.js -r ./lib/modelizer-client:modelizer -r q -o ./browser-dist/modelizer.js
*/
var Q = require('q');
var http = require('http');
var assert = require('./microlibs').assert;
var check = require('./microlibs').check;
var isEmptyObject = require('./microlibs').isEmptyObject;
var Model = require('./model');
// Using the REST-Interface
Model.ClientConnector = function (host, port, API_VERSION) {
var unpackInterceptor = function (model, callback) {
return function (err, docs) {
if (err == undefined) {
if (Array.isArray(docs)) { // result is a collection
for (var i = 0; i < docs.length; i++) {
model._transform(model, docs[i], 'unpack');
}
} else {
model._transform(model, docs, 'unpack');
}
}
callback(err, docs);
}
};
var cookie;
// ajax Call to the server backend
var ajaxCall = function (method, path, data, callback) {
var options = {
host: host,
port: port,
path: API_VERSION + path,
method: method,
withCredentials: true
}
if (data != undefined) {
options.headers = {
'Content-Type': 'application/json;charset=utf-8',
'Accept': 'application/json, text/plain, */*'
};
} else {
options.headers = {
'Accept': 'application/json, text/plain, */*'
};
}
// this is only necessary if you use the client via node
// browsers handle cookies on their own
if (cookie) {
options.headers['Cookie'] = cookie;
}
var req = http.request(options, function (res) {
if (res.headers['set-cookie']) {
// funktioniert nur mit einem cookie
cookie = res.headers['set-cookie'][0].split(";")[0];
}
var data = '';
res.on('data', function (chunk) {
data += chunk;
});
res.on('end', function () {
data = JSON.parse(data);
if (data.hasOwnProperty("error")) {
callback(new Error(data.error), null);
} else {
if (res.statusCode == 200) {
callback(undefined, data);
} else {
callback(new Error(data.error), null);
}
}
});
});
req.on('error', function (e) {
callback(new Error("Error in HTTP request:\n " + e.message), null);
});
if (data != undefined) {
req.write(JSON.stringify(data));
}
req.end();
};
/*
$http({method: method, url: url, data: data, withCredentials: true, cache: false})
.success(function (data, status, headers, config) {
if (status == 200) {
if (!status.hasOwnProperty("error")) {
callback(undefined, data);
} else {
callback(status.error, null);
}
} else {
callback(new Error("HTTP Response != 200"), null);
}
})
.error(function (data, status, headers, config) {
if (data.hasOwnProperty("error")) {
callback(new Error(data.error), null);
} else {
callback(new Error("Error in $http-request"), null);
}
});
*/
return function (theModel) {
return {
find: function (search, callback) {
if (isEmptyObject(search)) {
ajaxCall('GET', '/' + theModel.modelName + '/all', undefined, unpackInterceptor(theModel, callback));
} else {
ajaxCall('POST', '/' + theModel.modelName + '/find', search, unpackInterceptor(theModel, callback));
}
},
findOne: function (search, callback) {
assert(search.hasOwnProperty("_id"), "Only searching for id implemented so far");
ajaxCall('GET', '/' + theModel.modelName + '/' + search._id, undefined, unpackInterceptor(theModel, callback));
},
save: function (doc, callback) {
theModel._transform(theModel, doc, 'pack');
ajaxCall('PUT', '/' + theModel.modelName + '/', doc, callback);
},
remove: function (id, ignored, callback) {
ajaxCall('DELETE', '/' + theModel.modelName + '/' + id._id, undefined, callback);
},
callOperation: function (opName, params, HTMLrequest) {
var deferred = Q.defer();
ajaxCall('PUT', '/' + theModel.modelName + '/' + opName, params, function (err, result) {
if (theModel.operations.hasOwnProperty(opName)) { // call is an operation
if (err) deferred.reject(err);
else deferred.resolve(result);
} else if (theModel.factorys.hasOwnProperty(opName)) { // call is an factory
unpackInterceptor(theModel, function(err, result) {
if (err) {
deferred.reject(err);
return deferred.promise;
}
// restore object from document
if (Array.isArray(result)) { // result is a collection
// für jedes document in der DB ein object anlegen
for (var i = 0; i < result.length; i++) {
result[i] = theModel.loadFromDoc(result[i]);
}
} else { // result is one object
result = theModel.loadFromDoc(result); // restore one object
}
deferred.resolve(result);
})(err, result);
} else {
assert(false, "operation or factory is not defined");
}
});
return deferred.promise;
}
}
}
};
// CommonJS
module.exports = Model;
},{"./microlibs":1,"./model":2,"http":9,"q":"qLuPo1"}],"modelizer":[function(require,module,exports){
module.exports=require('tVRSAQ');
},{}],5:[function(require,module,exports){
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
* @license MIT
*/
var base64 = require('base64-js')
var ieee754 = require('ieee754')
exports.Buffer = Buffer
exports.SlowBuffer = Buffer
exports.INSPECT_MAX_BYTES = 50
Buffer.poolSize = 8192
/**
* If `Buffer._useTypedArrays`:
* === true Use Uint8Array implementation (fastest)
* === false Use Object implementation (compatible down to IE6)
*/
Buffer._useTypedArrays = (function () {
// Detect if browser supports Typed Arrays. Supported browsers are IE 10+, Firefox 4+,
// Chrome 7+, Safari 5.1+, Opera 11.6+, iOS 4.2+. If the browser does not support adding
// properties to `Uint8Array` instances, then that's the same as no `Uint8Array` support
// because we need to be able to add all the node Buffer API methods. This is an issue
// in Firefox 4-29. Now fixed: https://bugzilla.mozilla.org/show_bug.cgi?id=695438
try {
var buf = new ArrayBuffer(0)
var arr = new Uint8Array(buf)
arr.foo = function () { return 42 }
return 42 === arr.foo() &&
typeof arr.subarray === 'function' // Chrome 9-10 lack `subarray`
} catch (e) {
return false
}
})()
/**
* Class: Buffer
* =============
*
* The Buffer constructor returns instances of `Uint8Array` that are augmented
* with function properties for all the node `Buffer` API functions. We use
* `Uint8Array` so that square bracket notation works as expected -- it returns
* a single octet.
*
* By augmenting the instances, we can avoid modifying the `Uint8Array`
* prototype.
*/
function Buffer (subject, encoding, noZero) {
if (!(this instanceof Buffer))
return new Buffer(subject, encoding, noZero)
var type = typeof subject
// Workaround: node's base64 implementation allows for non-padded strings
// while base64-js does not.
if (encoding === 'base64' && type === 'string') {
subject = stringtrim(subject)
while (subject.length % 4 !== 0) {
subject = subject + '='
}
}
// Find the length
var length
if (type === 'number')
length = coerce(subject)
else if (type === 'string')
length = Buffer.byteLength(subject, encoding)
else if (type === 'object')
length = coerce(subject.length) // assume that object is array-like
else
throw new Error('First argument needs to be a number, array or string.')
var buf
if (Buffer._useTypedArrays) {
// Preferred: Return an augmented `Uint8Array` instance for best performance
buf = Buffer._augment(new Uint8Array(length))
} else {
// Fallback: Return THIS instance of Buffer (created by `new`)
buf = this
buf.length = length
buf._isBuffer = true
}
var i
if (Buffer._useTypedArrays && typeof subject.byteLength === 'number') {
// Speed optimization -- use set if we're copying from a typed array
buf._set(subject)
} else if (isArrayish(subject)) {
// Treat array-ish objects as a byte array
for (i = 0; i < length; i++) {
if (Buffer.isBuffer(subject))
buf[i] = subject.readUInt8(i)
else
buf[i] = subject[i]
}
} else if (type === 'string') {
buf.write(subject, 0, encoding)
} else if (type === 'number' && !Buffer._useTypedArrays && !noZero) {
for (i = 0; i < length; i++) {
buf[i] = 0
}
}
return buf
}
// STATIC METHODS
// ==============
Buffer.isEncoding = function (encoding) {
switch (String(encoding).toLowerCase()) {
case 'hex':
case 'utf8':
case 'utf-8':
case 'ascii':
case 'binary':
case 'base64':
case 'raw':
case 'ucs2':
case 'ucs-2':
case 'utf16le':
case 'utf-16le':
return true
default:
return false
}
}
Buffer.isBuffer = function (b) {
return !!(b !== null && b !== undefined && b._isBuffer)
}
Buffer.byteLength = function (str, encoding) {
var ret
str = str + ''
switch (encoding || 'utf8') {
case 'hex':
ret = str.length / 2
break
case 'utf8':
case 'utf-8':
ret = utf8ToBytes(str).length
break
case 'ascii':
case 'binary':
case 'raw':
ret = str.length
break
case 'base64':
ret = base64ToBytes(str).length
break
case 'ucs2':
case 'ucs-2':
case 'utf16le':
case 'utf-16le':
ret = str.length * 2
break
default:
throw new Error('Unknown encoding')
}
return ret
}
Buffer.concat = function (list, totalLength) {
assert(isArray(list), 'Usage: Buffer.concat(list, [totalLength])\n' +
'list should be an Array.')
if (list.length === 0) {
return new Buffer(0)
} else if (list.length === 1) {
return list[0]
}
var i
if (typeof totalLength !== 'number') {
totalLength = 0
for (i = 0; i < list.length; i++) {
totalLength += list[i].length
}
}
var buf = new Buffer(totalLength)
var pos = 0
for (i = 0; i < list.length; i++) {
var item = list[i]
item.copy(buf, pos)
pos += item.length
}
return buf
}
// BUFFER INSTANCE METHODS
// =======================
function _hexWrite (buf, string, offset, length) {
offset = Number(offset) || 0
var remaining = buf.length - offset
if (!length) {
length = remaining
} else {
length = Number(length)
if (length > remaining) {
length = remaining
}
}
// must be an even number of digits
var strLen = string.length
assert(strLen % 2 === 0, 'Invalid hex string')
if (length > strLen / 2) {
length = strLen / 2
}
for (var i = 0; i < length; i++) {
var byte = parseInt(string.substr(i * 2, 2), 16)
assert(!isNaN(byte), 'Invalid hex string')
buf[offset + i] = byte
}
Buffer._charsWritten = i * 2
return i
}
function _utf8Write (buf, string, offset, length) {
var charsWritten = Buffer._charsWritten =
blitBuffer(utf8ToBytes(string), buf, offset, length)
return charsWritten
}
function _asciiWrite (buf, string, offset, length) {
var charsWritten = Buffer._charsWritten =
blitBuffer(asciiToBytes(string), buf, offset, length)
return charsWritten
}
function _binaryWrite (buf, string, offset, length) {
return _asciiWrite(buf, string, offset, length)
}
function _base64Write (buf, string, offset, length) {
var charsWritten = Buffer._charsWritten =
blitBuffer(base64ToBytes(string), buf, offset, length)
return charsWritten
}
function _utf16leWrite (buf, string, offset, length) {
var charsWritten = Buffer._charsWritten =
blitBuffer(utf16leToBytes(string), buf, offset, length)
return charsWritten
}
Buffer.prototype.write = function (string, offset, length, encoding) {
// Support both (string, offset, length, encoding)
// and the legacy (string, encoding, offset, length)
if (isFinite(offset)) {
if (!isFinite(length)) {
encoding = length
length = undefined
}
} else { // legacy
var swap = encoding
encoding = offset
offset = length
length = swap
}
offset = Number(offset) || 0
var remaining = this.length - offset
if (!length) {
length = remaining
} else {
length = Number(length)
if (length > remaining) {
length = remaining
}
}
encoding = String(encoding || 'utf8').toLowerCase()
var ret
switch (encoding) {
case 'hex':
ret = _hexWrite(this, string, offset, length)
break
case 'utf8':
case 'utf-8':
ret = _utf8Write(this, string, offset, length)
break
case 'ascii':
ret = _asciiWrite(this, string, offset, length)
break
case 'binary':
ret = _binaryWrite(this, string, offset, length)
break
case 'base64':
ret = _base64Write(this, string, offset, length)
break
case 'ucs2':
case 'ucs-2':
case 'utf16le':
case 'utf-16le':
ret = _utf16leWrite(