Relations: has many and belongs to
This commit is contained in:
parent
76b8bf54d1
commit
975b148d54
120
index.js
120
index.js
|
@ -16,6 +16,7 @@ function Schema(name, settings) {
|
||||||
|
|
||||||
// create blank models pool
|
// create blank models pool
|
||||||
this.models = {};
|
this.models = {};
|
||||||
|
this.definitions = {};
|
||||||
|
|
||||||
// and initialize schema using adapter
|
// and initialize schema using adapter
|
||||||
// this is only one initialization entry point of adapter
|
// this is only one initialization entry point of adapter
|
||||||
|
@ -66,7 +67,7 @@ Schema.prototype.define = function defineClass(className, properties, settings)
|
||||||
|
|
||||||
// every class can receive hash of data as optional param
|
// every class can receive hash of data as optional param
|
||||||
var newClass = function (data) {
|
var newClass = function (data) {
|
||||||
AbstractClass.apply(this, args.concat([data]));
|
AbstractClass.call(this, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
hiddenProperty(newClass, 'schema', schema);
|
hiddenProperty(newClass, 'schema', schema);
|
||||||
|
@ -79,6 +80,10 @@ Schema.prototype.define = function defineClass(className, properties, settings)
|
||||||
|
|
||||||
// store class in model pool
|
// store class in model pool
|
||||||
this.models[className] = newClass;
|
this.models[className] = newClass;
|
||||||
|
this.definitions[className] = {
|
||||||
|
properties: properties,
|
||||||
|
settings: settings
|
||||||
|
};
|
||||||
|
|
||||||
// pass controll to adapter
|
// pass controll to adapter
|
||||||
this.adapter.define({
|
this.adapter.define({
|
||||||
|
@ -104,22 +109,41 @@ Schema.prototype.define = function defineClass(className, properties, settings)
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Schema.prototype.defineForeignKey = function defineForeignKey(className, key) {
|
||||||
|
// return if already defined
|
||||||
|
if (this.definitions[className].properties[key]) return;
|
||||||
|
|
||||||
|
if (this.adapter.defineForeignKey) {
|
||||||
|
this.adapter.defineForeignKey(className, key, function (err, keyType) {
|
||||||
|
if (err) throw err;
|
||||||
|
this.definitions[className].properties[key] = keyType;
|
||||||
|
}.bind(this));
|
||||||
|
} else {
|
||||||
|
this.definitions[className].properties[key] = Number;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract class constructor
|
* Abstract class constructor
|
||||||
*/
|
*/
|
||||||
function AbstractClass(name, properties, settings, data) {
|
function AbstractClass(data) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var ds = this.constructor.schema.definitions[this.constructor.modelName];
|
||||||
|
var properties = ds.properties;
|
||||||
|
var settings = ds.setings;
|
||||||
data = data || {};
|
data = data || {};
|
||||||
|
|
||||||
if (data.id) {
|
if (data.id) {
|
||||||
Object.defineProperty(this, 'id', {
|
defineReadonlyProp(this, 'id', data.id);
|
||||||
writable: false,
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true,
|
|
||||||
value: data.id
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(this, 'cachedRelations', {
|
||||||
|
writable: true,
|
||||||
|
enumerable: false,
|
||||||
|
configurable: true,
|
||||||
|
value: {}
|
||||||
|
});
|
||||||
|
|
||||||
Object.keys(properties).forEach(function (attr) {
|
Object.keys(properties).forEach(function (attr) {
|
||||||
var _attr = '_' + attr,
|
var _attr = '_' + attr,
|
||||||
attr_was = attr + '_was';
|
attr_was = attr + '_was';
|
||||||
|
@ -182,7 +206,7 @@ AbstractClass.create = function (data) {
|
||||||
this.schema.adapter.create(modelName, data, function (err, id) {
|
this.schema.adapter.create(modelName, data, function (err, id) {
|
||||||
obj = obj || new this(data);
|
obj = obj || new this(data);
|
||||||
if (id) {
|
if (id) {
|
||||||
obj.id = id;
|
defineReadonlyProp(obj, 'id', id);
|
||||||
this.cache[id] = obj;
|
this.cache[id] = obj;
|
||||||
}
|
}
|
||||||
if (callback) {
|
if (callback) {
|
||||||
|
@ -340,6 +364,68 @@ AbstractClass.prototype.reload = function (cb) {
|
||||||
this.constructor.find(this.id, cb);
|
this.constructor.find(this.id, cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// relations
|
||||||
|
AbstractClass.hasMany = function (anotherClass, params) {
|
||||||
|
var methodName = params.as; // or pluralize(anotherClass.modelName)
|
||||||
|
var fk = params.foreignKey;
|
||||||
|
// console.log(this.modelName, 'has many', anotherClass.modelName, 'as', params.as, 'queried by', params.foreignKey);
|
||||||
|
// each instance of this class should have method named
|
||||||
|
// pluralize(anotherClass.modelName)
|
||||||
|
// which is actually just anotherClass.all({thisModelNameId: this.id}, cb);
|
||||||
|
this.prototype[methodName] = function (cond, cb) {
|
||||||
|
var actualCond;
|
||||||
|
if (arguments.length === 1) {
|
||||||
|
actualCond = {};
|
||||||
|
cb = cond;
|
||||||
|
} else if (arguments.length === 2) {
|
||||||
|
actualCond = cond;
|
||||||
|
} else {
|
||||||
|
throw new Error(anotherClass.modelName + ' only can be called with one or two arguments');
|
||||||
|
}
|
||||||
|
actualCond[fk] = this.id;
|
||||||
|
return anotherClass.all(actualCond, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
// obviously, anotherClass should have attribute called `fk`
|
||||||
|
anotherClass.schema.defineForeignKey(anotherClass.modelName, fk);
|
||||||
|
|
||||||
|
// and it should have create/build methods with binded thisModelNameId param
|
||||||
|
this.prototype['build' + anotherClass.modelName] = function (data) {
|
||||||
|
data = data || {};
|
||||||
|
data[fk] = this.id; // trick! this.fk defined at runtime (when got it)
|
||||||
|
// but we haven't instance here to schedule this action
|
||||||
|
return new anotherClass(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.prototype['create' + anotherClass.modelName] = function (data, cb) {
|
||||||
|
if (typeof data === 'function') {
|
||||||
|
cb = data;
|
||||||
|
data = {};
|
||||||
|
}
|
||||||
|
this['build' + anotherClass.modelName](data).save(cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
AbstractClass.belongsTo = function (anotherClass, params) {
|
||||||
|
var methodName = params.as;
|
||||||
|
var fk = params.foreignKey;
|
||||||
|
anotherClass.schema.defineForeignKey(anotherClass.modelName, fk);
|
||||||
|
this.prototype[methodName] = function (p, cb) {
|
||||||
|
if (p instanceof AbstractClass) { // acts as setter
|
||||||
|
this[fk] = p.id;
|
||||||
|
this.cachedRelations[methodName] = p;
|
||||||
|
} else if (typeof p === 'function') { // acts as async getter
|
||||||
|
this.find(this[fk], function (err, obj) {
|
||||||
|
if (err) return p(err);
|
||||||
|
this.cachedRelations[methodName] = obj;
|
||||||
|
}.bind(this));
|
||||||
|
} else if (!p) { // acts as sync getter
|
||||||
|
return this.cachedRelations[methodName] || this[fk];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// helper methods
|
// helper methods
|
||||||
//
|
//
|
||||||
function isdef(s) {
|
function isdef(s) {
|
||||||
|
@ -347,6 +433,13 @@ function isdef(s) {
|
||||||
return s !== undef;
|
return s !== undef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function merge(base, update) {
|
||||||
|
Object.keys(update).forEach(function (key) {
|
||||||
|
base[key] = update[key];
|
||||||
|
});
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
function hiddenProperty(where, property, value) {
|
function hiddenProperty(where, property, value) {
|
||||||
Object.defineProperty(where, property, {
|
Object.defineProperty(where, property, {
|
||||||
writable: false,
|
writable: false,
|
||||||
|
@ -356,3 +449,12 @@ function hiddenProperty(where, property, value) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function defineReadonlyProp(obj, key, value) {
|
||||||
|
Object.defineProperty(obj, key, {
|
||||||
|
writable: false,
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
value: value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue