Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RelationDefinition applyScope/applyMapping #172

Merged
merged 3 commits into from
Jul 11, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 95 additions & 36 deletions lib/relation-definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var assert = require('assert');
var util = require('util');
var i8n = require('inflection');
var defineScope = require('./scope.js').defineScope;
var mergeQuery = require('./scope.js').mergeQuery;
var ModelBaseClass = require('./model.js');

exports.Relation = Relation;
Expand Down Expand Up @@ -68,6 +69,8 @@ function RelationDefinition(definition) {
this.modelThrough = definition.modelThrough;
this.keyThrough = definition.keyThrough;
this.multiple = (this.type !== 'belongsTo' && this.type !== 'hasOne');
this.properties = definition.properties || {};
this.scope = definition.scope;
}

RelationDefinition.prototype.toJSON = function () {
Expand All @@ -87,6 +90,41 @@ RelationDefinition.prototype.toJSON = function () {
return json;
};

/**
* Apply the configured scope to the filter/query object.
* @param {Object} modelInstance
* @param {Object} filter (where, order, limit, fields, ...)
*/
RelationDefinition.prototype.applyScope = function(modelInstance, filter) {
if (typeof this.scope === 'function') {
var scope = this.scope.call(this, modelInstance, filter);
if (typeof scope === 'object') {
mergeQuery(filter, scope);
}
} else if (typeof this.scope === 'object') {
mergeQuery(filter, this.scope);
}
};

/**
* Apply the configured properties to the target object.
* @param {Object} modelInstance
* @param {Object} target
*/
RelationDefinition.prototype.applyProperties = function(modelInstance, target) {
if (typeof this.properties === 'function') {
var data = this.properties.call(this, modelInstance);
for(var k in data) {
target[k] = data[k];
}
} else if (typeof this.properties === 'object') {
for(var k in this.properties) {
var key = this.properties[k];
target[key] = modelInstance[k];
}
}
};

/**
* A relation attaching to a given model instance
* @param {RelationDefinition|Object} definition
Expand Down Expand Up @@ -315,17 +353,19 @@ RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) {
var fk = params.foreignKey || i8n.camelize(thisClassName + '_id', true);

var idName = modelFrom.dataSource.idName(modelFrom.modelName) || 'id';

var definition = new RelationDefinition({
name: relationName,
type: RelationTypes.hasMany,
modelFrom: modelFrom,
keyFrom: idName,
keyTo: fk,
modelTo: modelTo,
multiple: true
multiple: true,
properties: params.properties,
scope: params.scope
});

if (params.through) {
definition.modelThrough = params.through;
var keyThrough = definition.throughKey || i8n.camelize(modelTo.modelName + '_id', true);
Expand All @@ -352,12 +392,15 @@ RelationDefinition.hasMany = function hasMany(modelFrom, modelTo, params) {
scopeMethods.create = scopeMethod(definition, 'create');
scopeMethods.build = scopeMethod(definition, 'build');
}

// Mix the property and scoped methods into the prototype class
defineScope(modelFrom.prototype, params.through || modelTo, relationName, function () {
var filter = {};
filter.where = {};
filter.where[fk] = this[idName];

definition.applyScope(this, filter);

if (params.through) {
filter.collect = i8n.camelize(modelTo.modelName, true);
filter.include = filter.collect;
Expand All @@ -384,42 +427,33 @@ HasMany.prototype.findById = function (id, cb) {
var fk = this.definition.keyTo;
var pk = this.definition.keyFrom;
var modelInstance = this.modelInstance;
modelTo.findById(id, function (err, inst) {
var idName = this.definition.modelTo.definition.idName();
var filter = {};
filter.where = {};
filter.where[idName] = id;
filter.where[fk] = modelInstance[pk];

this.definition.applyScope(modelInstance, filter);

modelTo.findOne(filter, function (err, inst) {
if (err) {
return cb(err);
}
if (!inst) {
return cb(new Error('Not found'));
}
// Check if the foreign key matches the primary key
if (inst[fk] && inst[fk].toString() === modelInstance[pk].toString()) {
cb(null, inst);
} else {
cb(new Error('Permission denied: foreign key does not match the primary key'));
}
cb(null, inst);
});
};

HasMany.prototype.destroyById = function (id, cb) {
var self = this;
var modelTo = this.definition.modelTo;
var fk = this.definition.keyTo;
var pk = this.definition.keyFrom;
var modelInstance = this.modelInstance;
modelTo.findById(id, function (err, inst) {
this.findById(id, function(err, inst) {
if (err) {
return cb(err);
}
if (!inst) {
return cb(new Error('Not found'));
}
// Check if the foreign key matches the primary key
if (inst[fk] && inst[fk].toString() === modelInstance[pk].toString()) {
self.removeFromCache(inst[fk]);
inst.destroy(cb);
} else {
cb(new Error('Permission denied: foreign key does not match the primary key'));
}
self.removeFromCache(inst[fk]);
inst.destroy(cb);
});
};

Expand Down Expand Up @@ -451,6 +485,9 @@ HasManyThrough.prototype.create = function create(data, done) {
var d = {};
d[fk1] = modelInstance[definition.keyFrom];
d[fk2] = to[pk2];

definition.applyProperties(modelInstance, d);

// Then create the through model
modelThrough.create(d, function (e, through) {
if (e) {
Expand Down Expand Up @@ -486,15 +523,20 @@ HasManyThrough.prototype.add = function (acInst, done) {
var pk2 = definition.modelTo.definition.idName();

var fk2 = findBelongsTo(modelThrough, definition.modelTo, pk2);

query[fk1] = this.modelInstance[pk1];
query[fk2] = acInst[pk2] || acInst;

var filter = { where: query };

definition.applyScope(this.modelInstance, filter);

data[fk1] = this.modelInstance[pk1];
data[fk2] = acInst[pk2] || acInst;
definition.applyProperties(this.modelInstance, data);

// Create an instance of the through model
modelThrough.findOrCreate({where: query}, data, function(err, ac) {
modelThrough.findOrCreate(filter, data, function(err, ac) {
if(!err) {
if (acInst instanceof definition.modelTo) {
self.addToCache(acInst);
Expand Down Expand Up @@ -526,8 +568,12 @@ HasManyThrough.prototype.remove = function (acInst, done) {

query[fk1] = this.modelInstance[pk1];
query[fk2] = acInst[pk2] || acInst;

var filter = { where: query };

definition.applyScope(this.modelInstance, filter);

modelThrough.deleteAll(query, function (err) {
modelThrough.deleteAll(filter.where, function (err) {
if (!err) {
self.removeFromCache(query[fk2]);
}
Expand Down Expand Up @@ -575,7 +621,7 @@ RelationDefinition.belongsTo = function (modelFrom, modelTo, params) {
var idName = modelFrom.dataSource.idName(modelTo.modelName) || 'id';
var relationName = params.as || i8n.camelize(modelTo.modelName, true);
var fk = params.foreignKey || relationName + 'Id';

var relationDef = modelFrom.relations[relationName] = new RelationDefinition({
name: relationName,
type: RelationTypes.belongsTo,
Expand Down Expand Up @@ -616,7 +662,6 @@ RelationDefinition.belongsTo = function (modelFrom, modelTo, params) {
fn.returns = {arg: relationName, type: 'object', root: true};

modelFrom.prototype['__get__' + relationName] = fn;

};

BelongsTo.prototype.create = function(targetModelData, cb) {
Expand Down Expand Up @@ -664,7 +709,7 @@ BelongsTo.prototype.related = function (refresh, params) {
var pk = this.definition.keyTo;
var fk = this.definition.keyFrom;
var modelInstance = this.modelInstance;

if (arguments.length === 1) {
params = refresh;
refresh = false;
Expand Down Expand Up @@ -788,14 +833,15 @@ RelationDefinition.hasOne = function (modelFrom, modelTo, params) {
var relationName = params.as || i8n.camelize(modelTo.modelName, true);

var fk = params.foreignKey || i8n.camelize(modelFrom.modelName + '_id', true);

var relationDef = modelFrom.relations[relationName] = new RelationDefinition({
name: relationName,
type: RelationTypes.hasOne,
modelFrom: modelFrom,
keyFrom: pk,
keyTo: fk,
modelTo: modelTo
modelTo: modelTo,
properties: params.properties
});

modelFrom.dataSource.defineForeignKey(modelTo.modelName, fk, modelFrom.modelName);
Expand Down Expand Up @@ -835,7 +881,11 @@ HasOne.prototype.create = function (targetModelData, cb) {
targetModelData = targetModelData || {};
targetModelData[fk] = modelInstance[pk];
var query = {where: {}};
query.where[fk] = targetModelData[fk]
query.where[fk] = targetModelData[fk];

this.definition.applyScope(modelInstance, query);
this.definition.applyProperties(modelInstance, targetModelData);

modelTo.findOne(query, function(err, result) {
if(err) {
cb(err);
Expand Down Expand Up @@ -869,13 +919,16 @@ HasMany.prototype.create = function (targetModelData, cb) {
var fk = this.definition.keyTo;
var pk = this.definition.keyFrom;
var modelInstance = this.modelInstance;

if (typeof targetModelData === 'function' && !cb) {
cb = targetModelData;
targetModelData = {};
}
targetModelData = targetModelData || {};
targetModelData[fk] = modelInstance[pk];

this.definition.applyProperties(modelInstance, targetModelData);

modelTo.create(targetModelData, function(err, targetModel) {
if(!err) {
// Refresh the cache
Expand All @@ -895,8 +948,12 @@ HasMany.prototype.build = HasOne.prototype.build = function(targetModelData) {
var modelTo = this.definition.modelTo;
var pk = this.definition.keyFrom;
var fk = this.definition.keyTo;

targetModelData = targetModelData || {};
targetModelData[fk] = this.modelInstance[pk];

this.definition.applyProperties(this.modelInstance, targetModelData);

return new modelTo(targetModelData);
};

Expand All @@ -916,6 +973,7 @@ HasOne.prototype.related = function (refresh, params) {
var modelTo = this.definition.modelTo;
var fk = this.definition.keyTo;
var pk = this.definition.keyFrom;
var definition = this.definition;
var modelInstance = this.modelInstance;

if (arguments.length === 1) {
Expand All @@ -937,6 +995,7 @@ HasOne.prototype.related = function (refresh, params) {
if (cachedValue === undefined) {
var query = {where: {}};
query.where[fk] = modelInstance[pk];
definition.applyScope(modelInstance, query);
modelTo.findOne(query, function (err, inst) {
if (err) {
return cb(err);
Expand Down
1 change: 1 addition & 0 deletions lib/scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var defineCachedRelations = utils.defineCachedRelations;
* Module exports
*/
exports.defineScope = defineScope;
exports.mergeQuery = mergeQuery;

function ScopeDefinition(definition) {
this.sourceModel = definition.sourceModel;
Expand Down
Loading