diff --git a/lib/waterline/model/lib/associationMethods/add.js b/lib/waterline/model/lib/associationMethods/add.js index e494492d8..e75f7c535 100644 --- a/lib/waterline/model/lib/associationMethods/add.js +++ b/lib/waterline/model/lib/associationMethods/add.js @@ -290,17 +290,17 @@ Add.prototype.createManyToMany = function(collection, attribute, pk, key, cb) { // Grab the associated collection's primaryKey var collectionAttributes = this.collection.waterline.schema[attribute.collection.toLowerCase()]; - var associationKey = collectionAttributes.attributes[attribute.on].via; + var associationKey = null; // If this is a throughTable, look into the meta data cache for what key to use if (collectionAttributes.throughTable) { - var cacheKey = collectionAttributes.throughTable[attribute.on + '.' + key]; + var cacheKey = collectionAttributes.throughTable[attribute.via + '.' + key]; if (!cacheKey) { return cb(new Error('Unable to find the proper cache key in the through table definition')); } associationKey = cacheKey; - } + }else associationKey = collectionAttributes.attributes[attribute.on].via; if (!associationKey) { return cb(new Error('No Primary Key set on the child record you ' + @@ -313,7 +313,7 @@ Add.prototype.createManyToMany = function(collection, attribute, pk, key, cb) { var _values = {}; criteria[associationKey] = pk; - criteria[attribute.onKey] = this.proto[this.primaryKey]; + criteria[attribute.on] = this.proto[this.primaryKey]; _values = _.clone(criteria); async.auto({ diff --git a/lib/waterline/model/lib/associationMethods/remove.js b/lib/waterline/model/lib/associationMethods/remove.js index cfecf34a0..3f6a45151 100644 --- a/lib/waterline/model/lib/associationMethods/remove.js +++ b/lib/waterline/model/lib/associationMethods/remove.js @@ -228,17 +228,17 @@ Remove.prototype.removeManyToMany = function(collection, attribute, pk, key, cb) // Grab the associated collection's primaryKey var collectionAttributes = this.collection.waterline.schema[attribute.collection.toLowerCase()]; - var associationKey = collectionAttributes.attributes[attribute.on].via; + var associationKey = null; // If this is a throughTable, look into the meta data cache for what key to use if (collectionAttributes.throughTable) { - var cacheKey = collectionAttributes.throughTable[attribute.on + '.' + key]; + var cacheKey = collectionAttributes.throughTable[attribute.via + '.' + key]; if (!cacheKey) { return cb(new Error('Unable to find the proper cache key in the through table definition')); } associationKey = cacheKey; - } + }else associationKey =collectionAttributes.attributes[attribute.on].via; if (!associationKey) { return cb(new Error('No Primary Key set on the child record you ' + diff --git a/test/integration/model/association.add.manyToMany.through.WithColumnName.id.js b/test/integration/model/association.add.manyToMany.through.WithColumnName.id.js new file mode 100644 index 000000000..e569c23e9 --- /dev/null +++ b/test/integration/model/association.add.manyToMany.through.WithColumnName.id.js @@ -0,0 +1,129 @@ +var Waterline = require('../../../lib/waterline'), + assert = require('assert'); + +describe('Model', function() { + describe('associations Many To Many Through with columnName', function() { + describe('.add() with an id', function() { + + ///////////////////////////////////////////////////// + // TEST SETUP + //////////////////////////////////////////////////// + + var collections = {}; + var prefValues = []; + + before(function(done) { + var waterline = new Waterline(); + + var User = Waterline.Collection.extend({ + connection: 'my_foo', + tableName: 'person', + attributes: { + preferences: { + collection: 'preference', + via: 'person', + through: 'person_preference' + } + } + }); + + var Preference = Waterline.Collection.extend({ + connection: 'my_foo', + tableName: 'preference', + attributes: { + foo: 'string', + people: { + collection: 'person', + via: 'preference', + through: 'person_preference' + } + } + }); + + var UserPreference = Waterline.Collection.extend({ + connection: 'my_foo', + tableName: 'person_preference', + attributes: { + person:{ + model: 'person', + columnName: 'person_id' + }, + preference: { + model: 'preference', + columnName: 'preference_id' + } + } + }); + + + waterline.loadCollection(User); + waterline.loadCollection(Preference); + waterline.loadCollection(UserPreference); + + var _values = [ + { id: 1, preferences: [{ foo: 'bar' }, { foo: 'foobar' }] }, + { id: 2, preferences: [{ foo: 'a' }, { foo: 'b' }] }, + ]; + + var i = 1; + + var adapterDef = { + find: function(con, col, criteria, cb) { + if(col === 'person_preference') return cb(null, []); + cb(null, _values); + }, + update: function(con, col, criteria, values, cb) { + if(col === 'preference') { + prefValues.push(values); + } + + return cb(null, values); + }, + create: function(con, col, values, cb) { + prefValues.push(values); + return cb(null, values); + }, + }; + + var connections = { + 'my_foo': { + adapter: 'foobar' + } + }; + + waterline.initialize({ adapters: { foobar: adapterDef }, connections: connections }, function(err, colls) { + if(err) done(err); + collections = colls.collections; + done(); + }); + }); + + + ///////////////////////////////////////////////////// + // TEST METHODS + //////////////////////////////////////////////////// + + it('should pass foreign key values to update method for each relationship', function(done) { + collections.person.find().exec(function(err, models) { + if(err) return done(err); + + var person = models[0]; + + person.preferences.add(1); + person.preferences.add(2); + + person.save(function(err) { + if(err) return done(err); + assert(prefValues.length === 2); + + assert(prefValues[0].preference_id === 1); + assert(prefValues[1].preference_id === 2); + + done(); + }); + }); + }); + + }); + }); +}); diff --git a/test/integration/model/association.add.manyToMany.through.WithColumnName.obj.js b/test/integration/model/association.add.manyToMany.through.WithColumnName.obj.js new file mode 100644 index 000000000..4d4133910 --- /dev/null +++ b/test/integration/model/association.add.manyToMany.through.WithColumnName.obj.js @@ -0,0 +1,139 @@ +var Waterline = require('../../../lib/waterline'), + assert = require('assert'); + +describe('Model', function() { + describe('associations Many To Many Through with columnName', function() { + describe('.add() with an object', function() { + + ///////////////////////////////////////////////////// + // TEST SETUP + //////////////////////////////////////////////////// + + var collections = {}; + var fooValues = []; + + before(function(done) { + var waterline = new Waterline(); + + var User = Waterline.Collection.extend({ + connection: 'my_foo', + tableName: 'person', + attributes: { + preferences: { + collection: 'preference', + via: 'person', + through: 'person_preference' + } + } + }); + + var Preference = Waterline.Collection.extend({ + connection: 'my_foo', + tableName: 'preference', + attributes: { + foo: 'string', + people: { + collection: 'person', + via: 'preference', + through: 'person_preference' + } + } + }); + + var UserPreference = Waterline.Collection.extend({ + connection: 'my_foo', + tableName: 'person_preference', + attributes: { + person:{ + model: 'person', + columnName: 'person_id' + }, + preference: { + model: 'preference', + columnName: 'preference_id' + } + } + }); + + waterline.loadCollection(User); + waterline.loadCollection(Preference); + waterline.loadCollection(UserPreference); + + var _values = [ + { id: 1, preferences: [{ id: 1, foo: 'bar' }, { id: 2, foo: 'foobar' }] }, + { id: 2, preferences: [{ id: 3, foo: 'a' }, { id: 4, foo: 'b' }] }, + ]; + + var i = 1; + var added = false; + + var adapterDef = { + find: function(con, col, criteria, cb) { + if(col === 'person_preference') { + if(!added) return cb(); + if(criteria === fooValues[0]) return cb(null, fooValues[0]); + return cb(null, []); + } + + return cb(null, _values); + }, + create: function(con, col, values, cb) { + if(col !== 'person_preference') { + values.id = i; + i++; + return cb(null, values); + } + + added = true; + fooValues.push(values); + return cb(null, values); + }, + update: function(con, col, criteria, values, cb) { return cb(null, values); } + }; + + var connections = { + 'my_foo': { + adapter: 'foobar' + } + }; + + waterline.initialize({ adapters: { foobar: adapterDef }, connections: connections }, function(err, colls) { + if(err) done(err); + collections = colls.collections; + done(); + }); + }); + + + ///////////////////////////////////////////////////// + // TEST METHODS + //////////////////////////////////////////////////// + + it('should pass model values to create method for each relationship', function(done) { + collections.person.find().exec(function(err, models) { + if(err) return done(err); + + var person = models[0]; + + person.preferences.add({ foo: 'foo' }); + person.preferences.add({ foo: 'bar' }); + + person.save(function(err) { + if(err) return done(err); + + assert(fooValues.length === 2); + + assert(fooValues[0].preference_id === 1); + assert(fooValues[0].person_id === 1); + + assert(fooValues[1].preference_id === 2); + assert(fooValues[1].person_id === 1); + + done(); + }); + }); + }); + + }); + }); +}); diff --git a/test/integration/model/association.add.manyToMany.through.id.js b/test/integration/model/association.add.manyToMany.through.id.js new file mode 100644 index 000000000..f66d0363a --- /dev/null +++ b/test/integration/model/association.add.manyToMany.through.id.js @@ -0,0 +1,127 @@ +var Waterline = require('../../../lib/waterline'), + assert = require('assert'); + +describe('Model', function() { + describe('associations Many To Many Through', function() { + describe('.add() with an id', function() { + + ///////////////////////////////////////////////////// + // TEST SETUP + //////////////////////////////////////////////////// + + var collections = {}; + var prefValues = []; + + before(function(done) { + var waterline = new Waterline(); + + var User = Waterline.Collection.extend({ + connection: 'my_foo', + tableName: 'person', + attributes: { + preferences: { + collection: 'preference', + via: 'person', + through: 'user_preference' + } + } + }); + + var Preference = Waterline.Collection.extend({ + connection: 'my_foo', + tableName: 'preference', + attributes: { + foo: 'string', + people: { + collection: 'person', + via: 'preference', + through: 'user_preference' + } + } + }); + + var UserPreference = Waterline.Collection.extend({ + connection: 'my_foo', + tableName: 'user_preference', + attributes: { + person:{ + model: 'person' + }, + preference: { + model: 'preference' + } + } + }); + + + waterline.loadCollection(User); + waterline.loadCollection(Preference); + waterline.loadCollection(UserPreference); + + var _values = [ + { id: 1, preferences: [{ foo: 'bar' }, { foo: 'foobar' }] }, + { id: 2, preferences: [{ foo: 'a' }, { foo: 'b' }] }, + ]; + + var i = 1; + + var adapterDef = { + find: function(con, col, criteria, cb) { + if(col === 'user_preference') return cb(null, []); + cb(null, _values); + }, + update: function(con, col, criteria, values, cb) { + if(col === 'preference') { + prefValues.push(values); + } + + return cb(null, values); + }, + create: function(con, col, values, cb) { + prefValues.push(values); + return cb(null, values); + }, + }; + + var connections = { + 'my_foo': { + adapter: 'foobar' + } + }; + + waterline.initialize({ adapters: { foobar: adapterDef }, connections: connections }, function(err, colls) { + if(err) done(err); + collections = colls.collections; + done(); + }); + }); + + + ///////////////////////////////////////////////////// + // TEST METHODS + //////////////////////////////////////////////////// + + it('should pass foreign key values to update method for each relationship', function(done) { + collections.person.find().exec(function(err, models) { + if(err) return done(err); + + var person = models[0]; + + person.preferences.add(1); + person.preferences.add(2); + + person.save(function(err) { + if(err) return done(err); + assert(prefValues.length === 2); + + assert(prefValues[0].preference === 1); + assert(prefValues[1].preference === 2); + + done(); + }); + }); + }); + + }); + }); +}); diff --git a/test/integration/model/association.add.manyToMany.through.object.js b/test/integration/model/association.add.manyToMany.through.object.js new file mode 100644 index 000000000..703953c22 --- /dev/null +++ b/test/integration/model/association.add.manyToMany.through.object.js @@ -0,0 +1,137 @@ +var Waterline = require('../../../lib/waterline'), + assert = require('assert'); + +describe('Model', function() { + describe('associations Many To Many Through', function() { + describe('.add() with an object', function() { + + ///////////////////////////////////////////////////// + // TEST SETUP + //////////////////////////////////////////////////// + + var collections = {}; + var fooValues = []; + + before(function(done) { + var waterline = new Waterline(); + + var User = Waterline.Collection.extend({ + connection: 'my_foo', + tableName: 'person', + attributes: { + preferences: { + collection: 'preference', + via: 'person', + through: 'user_preference' + } + } + }); + + var Preference = Waterline.Collection.extend({ + connection: 'my_foo', + tableName: 'preference', + attributes: { + foo: 'string', + people: { + collection: 'person', + via: 'preference', + through: 'user_preference' + } + } + }); + + var UserPreference = Waterline.Collection.extend({ + connection: 'my_foo', + tableName: 'user_preference', + attributes: { + person:{ + model: 'person' + }, + preference: { + model: 'preference' + } + } + }); + + waterline.loadCollection(User); + waterline.loadCollection(Preference); + waterline.loadCollection(UserPreference); + + var _values = [ + { id: 1, preferences: [{ id: 1, foo: 'bar' }, { id: 2, foo: 'foobar' }] }, + { id: 2, preferences: [{ id: 3, foo: 'a' }, { id: 4, foo: 'b' }] }, + ]; + + var i = 1; + var added = false; + + var adapterDef = { + find: function(con, col, criteria, cb) { + if(col === 'user_preference') { + if(!added) return cb(); + if(criteria === fooValues[0]) return cb(null, fooValues[0]); + return cb(null, []); + } + + return cb(null, _values); + }, + create: function(con, col, values, cb) { + if(col !== 'user_preference') { + values.id = i; + i++; + return cb(null, values); + } + + added = true; + fooValues.push(values); + return cb(null, values); + }, + update: function(con, col, criteria, values, cb) { return cb(null, values); } + }; + + var connections = { + 'my_foo': { + adapter: 'foobar' + } + }; + + waterline.initialize({ adapters: { foobar: adapterDef }, connections: connections }, function(err, colls) { + if(err) done(err); + collections = colls.collections; + done(); + }); + }); + + + ///////////////////////////////////////////////////// + // TEST METHODS + //////////////////////////////////////////////////// + + it('should pass model values to create method for each relationship', function(done) { + collections.person.find().exec(function(err, models) { + if(err) return done(err); + + var person = models[0]; + + person.preferences.add({ foo: 'foo' }); + person.preferences.add({ foo: 'bar' }); + + person.save(function(err) { + if(err) return done(err); + + assert(fooValues.length === 2); + + assert(fooValues[0].preference === 1); + assert(fooValues[0].person === 1); + + assert(fooValues[1].preference === 2); + assert(fooValues[1].person === 1); + + done(); + }); + }); + }); + + }); + }); +}); diff --git a/test/integration/model/association.remove.manyToMany.through.WithColumnName.js b/test/integration/model/association.remove.manyToMany.through.WithColumnName.js new file mode 100644 index 000000000..b6525b778 --- /dev/null +++ b/test/integration/model/association.remove.manyToMany.through.WithColumnName.js @@ -0,0 +1,156 @@ +var Waterline = require('../../../lib/waterline'), + assert = require('assert'); + +describe('Model', function() { + describe('associations Many To Many through with columnName', function() { + describe('.remove()', function() { + + ///////////////////////////////////////////////////// + // TEST SETUP + //////////////////////////////////////////////////// + + var collections = {}; + var prefValues = []; + + before(function(done) { + var waterline = new Waterline(); + + var User = Waterline.Collection.extend({ + connection: 'my_foo', + tableName: 'person', + attributes: { + preferences: { + collection: 'preference', + via: 'person', + through: 'person_preference' + } + } + }); + + var Preference = Waterline.Collection.extend({ + connection: 'my_foo', + tableName: 'preference', + attributes: { + foo: 'string', + people: { + collection: 'person', + via: 'preference', + through: 'person_preference' + } + } + }); + + var UserPreference = Waterline.Collection.extend({ + connection: 'my_foo', + tableName: 'person_preference', + attributes: { + person: { + model: 'person', + columnName: 'person_id' + }, + preference: { + model: 'preference', + columnName: 'preference_id' + } + } + }); + + + waterline.loadCollection(User); + waterline.loadCollection(Preference); + waterline.loadCollection(UserPreference); + + var _values = [ + { id: 1, preference: [{ foo: 'bar' }, { foo: 'foobar' }] }, + { id: 2, preference: [{ foo: 'a' }, { foo: 'b' }] }, + ]; + + var i = 1; + + var adapterDef = { + find: function(con, col, criteria, cb) { + if(col === 'person_preference') return cb(null, []); + cb(null, _values); + }, + destroy: function(con, col, criteria, cb) { + if(col === 'person_preference') { + prefValues.push(criteria.where); + } + return cb(null, criteria); + }, + update: function(con, col, criteria, values, cb) { + return cb(null, values); + }, + create: function(con, col, values, cb) { + prefValues.push(values); + return cb(null, values); + }, + }; + + var connections = { + 'my_foo': { + adapter: 'foobar' + } + }; + + waterline.initialize({ adapters: { foobar: adapterDef }, connections: connections }, function(err, colls) { + if(err) done(err); + collections = colls.collections; + done(); + }); + }); + + + ///////////////////////////////////////////////////// + // TEST METHODS + //////////////////////////////////////////////////// + + it('should pass foreign key values to update method for each relationship', function(done) { + collections.person.find().exec(function(err, models) { + if(err) return done(err); + + var person = models[0]; + + person.preferences.remove(1); + person.preferences.remove(2); + + person.save(function(err) { + if(err) return done(err); + + assert(prefValues.length === 2); + + assert(prefValues[0].person_id === 1); + assert(prefValues[0].preference_id === 1); + assert(prefValues[1].person_id === 1); + assert(prefValues[1].preference_id === 2); + + done(); + }); + }); + }); + + it('should error with a failed transaction when an object is used', function(done) { + collections.person.find().exec(function(err, models) { + if(err) return done(err); + + var person = models[0]; + + person.preferences.remove({ foo: 'foo' }); + person.preferences.remove({ foo: 'bar' }); + + person.save(function(err) { + assert(err); + assert(err.failedTransactions); + assert(Array.isArray(err.failedTransactions)); + assert(err.failedTransactions.length === 2); + assert(err.failedTransactions[0].type === 'remove'); + assert(err.failedTransactions[1].type === 'remove'); + + done(); + }); + }); + }); + + }); + }); +}); diff --git a/test/integration/model/association.remove.manyToMany.through.js b/test/integration/model/association.remove.manyToMany.through.js new file mode 100644 index 000000000..e698b07f0 --- /dev/null +++ b/test/integration/model/association.remove.manyToMany.through.js @@ -0,0 +1,154 @@ +var Waterline = require('../../../lib/waterline'), + assert = require('assert'); + +describe('Model', function() { + describe('associations Many To Many through', function() { + describe('.remove()', function() { + + ///////////////////////////////////////////////////// + // TEST SETUP + //////////////////////////////////////////////////// + + var collections = {}; + var prefValues = []; + + before(function(done) { + var waterline = new Waterline(); + + var User = Waterline.Collection.extend({ + connection: 'my_foo', + tableName: 'person', + attributes: { + preferences: { + collection: 'preference', + via: 'person', + through: 'person_preference' + } + } + }); + + var Preference = Waterline.Collection.extend({ + connection: 'my_foo', + tableName: 'preference', + attributes: { + foo: 'string', + people: { + collection: 'person', + via: 'preference', + through: 'person_preference' + } + } + }); + + var UserPreference = Waterline.Collection.extend({ + connection: 'my_foo', + tableName: 'person_preference', + attributes: { + person: { + model: 'person', + }, + preference: { + model: 'preference', + } + } + }); + + + waterline.loadCollection(User); + waterline.loadCollection(Preference); + waterline.loadCollection(UserPreference); + + var _values = [ + { id: 1, preference: [{ foo: 'bar' }, { foo: 'foobar' }] }, + { id: 2, preference: [{ foo: 'a' }, { foo: 'b' }] }, + ]; + + var i = 1; + + var adapterDef = { + find: function(con, col, criteria, cb) { + if(col === 'person_preference') return cb(null, []); + cb(null, _values); + }, + destroy: function(con, col, criteria, cb) { + if(col === 'person_preference') { + prefValues.push(criteria.where); + } + return cb(null, criteria); + }, + update: function(con, col, criteria, values, cb) { + return cb(null, values); + }, + create: function(con, col, values, cb) { + prefValues.push(values); + return cb(null, values); + }, + }; + + var connections = { + 'my_foo': { + adapter: 'foobar' + } + }; + + waterline.initialize({ adapters: { foobar: adapterDef }, connections: connections }, function(err, colls) { + if(err) done(err); + collections = colls.collections; + done(); + }); + }); + + + ///////////////////////////////////////////////////// + // TEST METHODS + //////////////////////////////////////////////////// + + it('should pass foreign key values to update method for each relationship', function(done) { + collections.person.find().exec(function(err, models) { + if(err) return done(err); + + var person = models[0]; + + person.preferences.remove(1); + person.preferences.remove(2); + + person.save(function(err) { + if(err) return done(err); + + assert(prefValues.length === 2); + + assert(prefValues[0].person === 1); + assert(prefValues[0].preference === 1); + assert(prefValues[1].person === 1); + assert(prefValues[1].preference === 2); + + done(); + }); + }); + }); + + it('should error with a failed transaction when an object is used', function(done) { + collections.person.find().exec(function(err, models) { + if(err) return done(err); + + var person = models[0]; + + person.preferences.remove({ foo: 'foo' }); + person.preferences.remove({ foo: 'bar' }); + + person.save(function(err) { + assert(err); + assert(err.failedTransactions); + assert(Array.isArray(err.failedTransactions)); + assert(err.failedTransactions.length === 2); + assert(err.failedTransactions[0].type === 'remove'); + assert(err.failedTransactions[1].type === 'remove'); + + done(); + }); + }); + }); + + }); + }); +});