Permalink
Browse files

Named associations

  • Loading branch information...
1 parent 73d0480 commit ba841bbedbfbe8ba8a32d944108caacfaa7654b1 mde committed Nov 14, 2012
Showing with 165 additions and 55 deletions.
  1. +114 −42 lib/index.js
  2. +49 −6 test/adapters/shared.js
  3. +1 −3 test/fixtures/account.js
  4. +0 −2 test/fixtures/profile.js
  5. +1 −2 test/fixtures/user.js
View
@@ -65,8 +65,7 @@ utils.mixin(model, new (function () {
// Base constructor function for all model items
var ModelItemConstructor = function (params) {
var self = this
- , associations = model.descriptionRegistry[def.name].associations
- , assnKey;
+ , associations = model.descriptionRegistry[def.name].associations;
var _saveAssociations = function (callback) {
var self = this
@@ -192,16 +191,34 @@ utils.mixin(model, new (function () {
this.toJson = this.toString;
- this.getAssociation = function () {
+ this._getAssociation = function () {
var args = Array.prototype.slice.call(arguments)
- , modelName = args.shift()
+ , assnName = args.shift()
, assnType = args.shift()
, callback = args.pop()
, query
, opts
- , otherKeyName = utils.string.decapitalize(modelName)
- , selfKeyName = utils.string.decapitalize(this.type)
- , queryName;
+ , otherKeyName
+ , selfKeyName
+ , queryName
+ , reg = model.descriptionRegistry
+ , assn = reg[this.type].associations[assnType]
+ , modelName;
+
+ // Bail out if the association doesn't exist
+ if (!assn) {
+ throw new Error('Model ' + this.type + ' does not have ' + assnType +
+ ' association.');
+ }
+
+ modelName = assn[assnName].model;
+
+ // Normalize inflection
+ modelName = utils.inflection.singularize(modelName);
+ assnName = utils.inflection.singularize(assnName);
+
+ otherKeyName = utils.string.decapitalize(assnName)
+ selfKeyName = utils.string.decapitalize(this.type)
// Has query object
if (assnType == 'hasMany') {
@@ -226,19 +243,37 @@ utils.mixin(model, new (function () {
query[selfKeyName + 'Id'] = this.id;
}
- queryName = assnType == 'hasMany' ? 'all' : 'load'
+ queryName = assnType == 'hasMany' ? 'all' : 'first';
model[modelName][queryName](query, opts, callback);
};
- this.createAssociation = function () {
+ this._createAssociation = function () {
var args = Array.prototype.slice.call(arguments)
- , modelName = args.shift()
+ , assnName = args.shift()
, assnType = args.shift()
, data = args.shift()
- , otherKeyName = utils.string.decapitalize(modelName)
- , selfKeyName = utils.string.decapitalize(this.type)
+ , otherKeyName
+ , selfKeyName
+ , reg = model.descriptionRegistry
+ , assn = reg[this.type].associations[assnType]
+ , modelName
, unsaved;
+ // Bail out if the association doesn't exist
+ if (!assn) {
+ throw new Error('Model ' + this.type + ' does not have ' + assnType +
+ ' association.');
+ }
+
+ modelName = assn[assnName].model;
+
+ // Normalize inflection
+ modelName = utils.inflection.singularize(modelName);
+ assnName = utils.inflection.singularize(assnName);
+
+ otherKeyName = utils.string.decapitalize(assnName)
+ selfKeyName = utils.string.decapitalize(this.type)
+
if (assnType == 'belongsTo') {
if (!(data._saved && data.id)) {
throw new Error('Item cannot have a belongTo association ' +
@@ -262,12 +297,8 @@ utils.mixin(model, new (function () {
};
// Relation intstance-methods
- assnKey = associations.hasMany;
['hasMany', 'hasOne', 'belongsTo'].forEach(function (k) {
- var assnKeys
- , assnKey
- , modelName
- , keyForCreate = k == 'hasMany' ? 'add' : 'set'
+ var assns
, createMethod = function (type, keyName, assnType) {
return function () {
var args = Array.prototype.slice.call(arguments);
@@ -276,16 +307,23 @@ utils.mixin(model, new (function () {
self[type + 'Association'].apply(self, args);
};
};
- if ((assnKeys = associations[k])) {
- for (assnKey in assnKeys) {
- modelName = k == 'hasMany' ?
- utils.inflection.singularize(assnKey) : assnKey;
- // this.getBooks({}, {}, function () {}); =>
- // this.getAssociation('Book', 'hasMany', {}, {}, function () {});
- self['get' + assnKey] = createMethod('get', modelName, k);
- // this.addBook(book); =>
- // this.createAssociation('Book', 'hasMany', book);
- self[keyForCreate + modelName] = createMethod('create', modelName, k);
+ if ((assns = associations[k])) {
+ for (var assnName in assns) {
+ (function (assnName) {
+ var methodName = k == 'hasMany' ?
+ utils.inflection.pluralize(assnName) : assnName
+ , keyForCreate = k == 'hasMany' ? 'add' : 'set';
+
+ // get can be singular or plural, depending on hasMany/hasOne
+ // this.getBooks({}, {}, function () {}); =>
+ // this.getAssociation('Book', 'hasMany', {}, {}, function () {});
+ self['get' + methodName] = createMethod('_get', assnName, k);
+
+ // add/set is always singular, just use assnName for method
+ // this.addBook(book); =>
+ // this.createAssociation('Book', 'hasMany', book);
+ self[keyForCreate + assnName] = createMethod('_create', assnName, k);
+ })(assnName);
}
}
});
@@ -810,22 +848,56 @@ model.ModelDefinitionBase = function (name) {
}
['hasMany', 'hasOne', 'belongsTo'].forEach(function (assnKey) {
- self[assnKey] = function (name) {
- var assn = reg[self.name].associations[assnKey] || {}
- , def
- , idDatatype;
- assn[name] = true;
+ self[assnKey] = function (name, options) {
+ var opts = options || {}
+ , assn = reg[self.name].associations[assnKey] || {}
+ , assnName = name
+ , modelName = opts.model || name;
+
+ // Normalize inflection; we don't care which they use
+ assnName = utils.inflection.singularize(assnName);
+ modelName = utils.inflection.singularize(modelName);
+
+ assn[assnName] = {
+ model: modelName
+ };
+
reg[self.name].associations[assnKey] = assn;
- if (assnKey == 'belongsTo') {
- // FIXME: Hack, let other models get defined first
- // Should probably listen for an event that signals
- // base models are set up
- setTimeout(function () {
- def = model[name];
- idDatatype = def.autoIncrementId ? 'int' : 'string';
- self.property(utils.string.decapitalize(name) + 'Id', idDatatype);
- }, 0);
- }
+
+ // Set up foreign keys
+ // FIXME: Hack, let other models get defined first
+ // Should probably listen for an event that signals
+ // base models are set up
+ var createForeignKey = function (assnName) {
+ return function () {
+ var ownerModelName
+ , ownedModelName
+ , idKey
+ , datatype
+ , def;
+
+ if (assnKey == 'belongsTo') {
+ ownerModelName = modelName;
+ ownedModelName = self.name;
+ idKey = utils.string.decapitalize(assnName) + 'Id'
+ }
+ else {
+ ownerModelName = self.name;
+ ownedModelName = modelName;
+ idKey = utils.string.decapitalize(ownerModelName) + 'Id'
+ }
+
+ if (!reg[ownedModelName].properties[idKey]) {
+ def = model[ownerModelName];
+ datatype = def.autoIncrementId ? 'int' : 'string';
+
+ reg[ownedModelName].properties[idKey] =
+ new model.PropertyDescription(idKey, datatype);
+ }
+ }
+ };
+
+ setTimeout(createForeignKey(assnName), 0);
};
});
};
@@ -281,7 +281,7 @@ tests = {
});
}
-, 'test hasOne association, set from owner': function (next) {
+, 'test hasOne association': function (next) {
var u = User.create({
login: 'asdf'
, password: 'zerb'
@@ -316,7 +316,7 @@ tests = {
});
}
-, 'test hasOne association, set from owned': function (next) {
+, 'test belongsTo association': function (next) {
var u = User.create({
login: 'asdf'
, password: 'zerb'
@@ -339,8 +339,8 @@ tests = {
if (err) {
throw err;
}
- user.getProfile(function (err, data) {
- assert.equal(profile.id, data.id);
+ profile.getUser(function (err, data) {
+ assert.equal('asdf', data.login);
if (err) {
throw err;
}
@@ -351,7 +351,7 @@ tests = {
});
}
-, 'test hasMany association, set from owner': function (next) {
+, 'test hasMany association': function (next) {
var u = User.create({
login: 'asdf'
, password: 'zerb'
@@ -386,9 +386,52 @@ tests = {
});
}
+, 'test named hasMany': function (next) {
+ var u = User.create({
+ login: 'asdf'
+ , password: 'zerb'
+ , confirmPassword: 'zerb'
+ });
+ u.save(function (err, data) {
+ if (err) {
+ throw err;
+ }
+ currentId = u.id;
+ User.first(currentId, {}, function (err, data) {
+ var user = data
+ , account;
+ if (err) {
+ throw err;
+ }
+ user.addFriend(User.create({
+ login: 'qwer'
+ , password: 'zerb'
+ , confirmPassword: 'zerb'
+ }));
+ user.addFriend(User.create({
+ login: 'zxcv'
+ , password: 'zerb'
+ , confirmPassword: 'zerb'
+ }));
+ user.save(function (err, data) {
+ if (err) {
+ throw err;
+ }
+ user.getFriends(function (err, data) {
+ assert.equal(2, data.length);
+ if (err) {
+ throw err;
+ }
+ next();
+ });
+ });
+ });
+ });
+ }
+
, 'test Static methods on model': function (next) {
User.findByLogin('asdf', function (err, data) {
- assert.equal(data.length, 3);
+ assert.equal(data.length, 4);
if (err) {
throw err;
}
@@ -3,9 +3,7 @@ var model = require('../../lib');
var Account = function () {
this.property('location', 'string');
- this.belongsTo('User');
-
- this.autoIncrementId = false;
+ // Do this one without an inverse belongsTo
};
Account.prototype.someMethod = function () {
@@ -8,8 +8,6 @@ var Profile = function () {
this.property('setting2', 'boolean');
this.belongsTo('User');
-
- //this.autoIncrementId = true;
};
Profile.prototype.someMethod = function () {
@@ -14,8 +14,7 @@ var User = function () {
this.hasOne('Profile');
this.hasMany('Accounts');
-
- //this.autoIncrementId = true;
+ this.hasMany('Friends', {model: 'Users'});
};
User.prototype.someMethod = function () {

0 comments on commit ba841bb

Please sign in to comment.