From 7679459b0276e127335807bd7ec850f0042bd644 Mon Sep 17 00:00:00 2001 From: blah238 Date: Tue, 20 Sep 2016 19:45:49 -0500 Subject: [PATCH] Version 0.3.0 - Update dev/peerDependencies (should be no breaking changes, but bumping minor version to be safe) - Add setHasOne functionality (based on #12) and add tests - Fix tests where assert.equal was used with incorrectly ordered parameters - Use Promise.method to clean up a few parts and more clearly delineate which functions return promises vs. which are synchronous - Closes #10 --- README.md | 1 + lib/manager.js | 80 +++++++++++++--------------- package.json | 8 +-- test/manager.create.js | 70 ++++++++++++++---------- test/manager.fetch.js | 24 +++++---- test/manager.forge.js | 4 +- test/manager.get.js | 8 +-- test/manager.register.js | 4 +- test/manager.save.js | 85 +++++++++++++++++++++--------- test/models/car.js | 4 ++ test/models/title.js | 11 ++++ test/support/bootstrap.fixtures.js | 5 ++ test/support/bootstrap.tables.js | 8 +++ 13 files changed, 195 insertions(+), 117 deletions(-) create mode 100644 test/models/title.js diff --git a/README.md b/README.md index 2fe6dce..9b994ca 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ ## Changelog +- v0.3.0 - Add `setHasOne` functionality (#12) - v0.2.1 - Several breaking changes occurred with this version due to updating `devDependencies` and `peerDependencies`: - Knex and Bookshelf updated their `bluebird` and `lodash` dependencies - Knex changed how undefined values are inserted diff --git a/lib/manager.js b/lib/manager.js index 53c0558..a5de2ed 100644 --- a/lib/manager.js +++ b/lib/manager.js @@ -46,7 +46,7 @@ Manager.prototype.initialize = function(bookshelf) { return this; }; -Manager.prototype.create = function(name, properties, options) { +Manager.prototype.create = Promise.method(function(name, properties, options) { var Model = this.get(name); var model = new Model(); @@ -54,16 +54,16 @@ Manager.prototype.create = function(name, properties, options) { console.error(error.stack); throw error; }); -}; +}); -Manager.prototype.fetch = function(name, properties, related, options) { +Manager.prototype.fetch = Promise.method(function(name, properties, related, options) { var model = this.forge(name, properties); return model.fetch({ withRelated: related, transacting: options && options.transacting ? options.transacting : null }); -}; +}); Manager.prototype.findRelated = function(properties, paths) { var related = []; @@ -177,7 +177,7 @@ Manager.prototype.isCollection = function(model) { return model instanceof this.bookshelf.Collection || model.prototype instanceof this.bookshelf.Collection; }; -Manager.prototype.save = function(model, properties, options) { +Manager.prototype.save = Promise.method(function(model, properties, options) { if (this.isModel(model)) { return this.saveModel(model, properties, options); } @@ -187,9 +187,9 @@ Manager.prototype.save = function(model, properties, options) { } throw new Error('Object should be an instance of Model or Collection, not ' + typeof model); -}; +}); -Manager.prototype.saveCollection = function(collection, models, options) { +Manager.prototype.saveCollection = Promise.method(function(collection, models, options) { return collection.mapThen(function(model) { return (model.isNew() || model.hasChanged()) ? model.save(null, options) : model; }).then(function() { @@ -206,18 +206,18 @@ Manager.prototype.saveCollection = function(collection, models, options) { console.error(error.stack); throw error; }); -}; +}); -Manager.prototype.saveModel = function(model, properties, options) { +Manager.prototype.saveModel = Promise.method(function(model, properties, options) { return this.setModel(model, properties, options).then(function(result) { return (result.isNew() || result.hasChanged()) ? result.save(null, options) : result; }).catch(function(error) { console.error(error.stack); throw error; }); -}; +}); -Manager.prototype.set = function(model, properties, options) { +Manager.prototype.set = Promise.method(function(model, properties, options) { if (this.isModel(model)) { return this.setModel(model, properties, options); } @@ -227,9 +227,9 @@ Manager.prototype.set = function(model, properties, options) { } throw new Error('Object should be an instance of Model or Collection, not ' + typeof model); -}; +}); -Manager.prototype.setModel = function(model, properties, options) { +Manager.prototype.setModel = Promise.method(function(model, properties, options) { var promises = []; properties = (typeof properties === 'object' && !Array.isArray(properties) && properties !== null) ? properties : {}; @@ -276,9 +276,9 @@ Manager.prototype.setModel = function(model, properties, options) { return Promise.reduce(promises, function(result, promise) { return promise(result); }, []); -}; +}); -Manager.prototype.setBelongsTo = function(model, key, value, relation, options) { +Manager.prototype.setBelongsTo = Promise.method(function(model, key, value, relation, options) { var Target = relation.relatedData.target; var existing = model.related(key); var target = existing.isNew() ? Target.forge() : existing.clone(); @@ -288,7 +288,7 @@ Manager.prototype.setBelongsTo = function(model, key, value, relation, options) if (model.get(fk)) { model.set(fk, null); } - return Promise.resolve((model.isNew() || model.hasChanged()) ? model.save(null, options) : model); + return (model.isNew() || model.hasChanged()) ? model.save(null, options) : model; } return this.save(target, value, options).then(function(target) { @@ -300,12 +300,12 @@ Manager.prototype.setBelongsTo = function(model, key, value, relation, options) return (model.isNew() || model.hasChanged()) ? model.save(null, options) : model; }); -}; +}); -Manager.prototype.setBelongsToMany = function(model, key, models, relation, options) { +Manager.prototype.setBelongsToMany = Promise.method(function(model, key, models, relation, options) { var existing = model.related(key); - return Promise.cast(existing.length ? existing : existing.fetch(options)).then(function() { + return Promise.resolve(existing.length ? existing : existing.fetch(options)).then(function() { return this.setCollection(existing, models, options); }.bind(this)).then(function(targets) { // Enforce attach/detach IDs @@ -326,31 +326,25 @@ Manager.prototype.setBelongsToMany = function(model, key, models, relation, opti }).then(function() { return model; }); -}; +}); -Manager.prototype.setHasOne = function(model, key, value, relation, options) { - var Target = relation.relatedData.target; - var existing = Target.forge(model.related(key).attributes); - var target = Target.forge(value); +Manager.prototype.setHasOne = Promise.method(function(model, key, value, relation, options) { + var Target = relation.relatedData.target; + var existing = Target.forge(model.related(key).attributes); + var target = Target.forge(value); var fk = relation.relatedData.foreignKey; - if (value !== null) { - if (existing.get(fk)) { - existing.set(fk, null); - } else { - target.set(fk, model.id); - return target.save(); - } - - return existing.save() - .then(function() { - target.set(fk, model.id); - return target.save(); - }); - } -}; + return Promise.resolve(existing.isNew() ? null : existing.save(fk, null, options)) + .then(function() { + return target.save(fk, model.id, options); + }) + .then(function(target) { + model.relations[key] = target; + return model; + }); +}); -Manager.prototype.setHasMany = function(model, key, models, relation, options) { +Manager.prototype.setHasMany = Promise.method(function(model, key, models, relation, options) { var existing = model.related(key); var fk = relation.relatedData.foreignKey; @@ -380,7 +374,7 @@ Manager.prototype.setHasMany = function(model, key, models, relation, options) { }).then(function() { return model; }); -}; +}); Manager.prototype.setScalar = Promise.method(function(model, key, value) { if (key.indexOf('_pivot_') === 0) { @@ -396,7 +390,7 @@ Manager.prototype.setScalar = Promise.method(function(model, key, value) { return model; }); -Manager.prototype.setCollection = function(existing, models, options) { +Manager.prototype.setCollection = Promise.method(function(existing, models, options) { models = models || []; return Promise.map(models, function(properties) { @@ -406,6 +400,6 @@ Manager.prototype.setCollection = function(existing, models, options) { }.bind(this)).then(function(results) { return this.bookshelf.Collection.forge(results); }.bind(this)); -}; +}); module.exports = Manager; diff --git a/package.json b/package.json index 46c5fbf..ae9c504 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bookshelf-manager", - "version": "0.2.1", + "version": "0.3.0", "description": "Easily wire up models to APIs with supported for complex, nested saving.", "main": "index.js", "scripts": { @@ -26,13 +26,13 @@ "bluebird": "^3.4.1", "bookshelf": "^0.10.0", "deep-diff": "^0.3.4", - "knex": "^0.11.7", - "mocha": "^2.5.3", + "knex": "^0.12.1", + "mocha": "^3.0.2", "sqlite3": "^3.1.4" }, "peerDependencies": { "bluebird": "3.x", "bookshelf": "^0.10.0", - "knex": "^0.11.0" + "knex": "^0.11.0 || ^0.12.0" } } diff --git a/test/manager.create.js b/test/manager.create.js index 4fc0f67..88904f9 100644 --- a/test/manager.create.js +++ b/test/manager.create.js @@ -29,8 +29,8 @@ describe('manager', function() { return manager.create('car', { quantity: 1 }).then(function(car) { - assert.equal(1, car.id, 'Car should have ID 1'); - assert.equal(1, car.get('quantity'), 'Car should have quantity of 1'); + assert.equal(car.id, 1, 'Car should have ID 1'); + assert.equal(car.get('quantity'), 1, 'Car should have quantity of 1'); }); }); @@ -41,8 +41,8 @@ describe('manager', function() { ]).then(function(cars) { cars.sortBy('quantity'); - assert.equal(2, cars.length, 'Cars collection should have 2 Car models'); - assert.equal(2, cars.pluck('quantity').length, 'Quantities should be set'); + assert.equal(cars.length, 2, 'Cars collection should have 2 Car models'); + assert.equal(cars.pluck('quantity').length, 2, 'Quantities should be set'); }); }); @@ -54,11 +54,27 @@ describe('manager', function() { }, quantity: 1 }).then(function(car) { - assert.equal(1, car.id, 'Car should have ID 1, not ' + car.id); - assert.equal(1, car.get('quantity'), 'Car should have quantity of 1'); - assert.equal(1, car.related('color').id, 'Color should have ID 1, not ' + car.related('color').id); - assert.equal('White', car.related('color').get('name'), 'Color name should be White'); - assert.equal('#fff', car.related('color').get('hex_value'), 'Color hex_value should be #fff'); + assert.equal(car.id, 1, 'Car should have ID 1, not ' + car.id); + assert.equal(car.get('quantity'), 1, 'Car should have quantity of 1'); + assert.equal(car.related('color').id, 1, 'Color should have ID 1, not ' + car.related('color').id); + assert.equal(car.related('color').get('name'), 'White', 'Color name should be White'); + assert.equal(car.related('color').get('hex_value'), '#fff', 'Color hex_value should be #fff'); + }); + }); + + it('should create a model within a new model (hasOne)', function() { + return manager.create('car', { + title: { + state: 'FL', + issue_date: '2017-01-01' + }, + quantity: 1 + }).then(function(car) { + assert.equal(car.id, 1, 'Car should have ID 1, not ' + car.id); + assert.equal(car.get('quantity'), 1, 'Car should have quantity of 1'); + assert.equal(car.related('title').id, 1, 'Title should have ID 1, not ' + car.related('title').id); + assert.equal(car.related('title').get('state'), 'FL', 'Title state should be FL'); + assert.equal(car.related('title').get('issue_date'), '2017-01-01', 'Title issue_date should be 2017-01-01'); }); }); @@ -75,9 +91,9 @@ describe('manager', function() { }, quantity: 2 }).then(function(car) { - assert.equal(color.id, car.related('color').id, 'Color ID should stay the same, not ' + car.related('color').id); - assert.equal('Grey', car.related('color').get('name'), 'Color name should be Grey'); - assert.equal('#666', car.related('color').get('hex_value'), 'Color hex_value should be #666'); + assert.equal(car.related('color').id, color.id, 'Color ID should stay the same, not ' + car.related('color').id); + assert.equal(car.related('color').get('name'), 'Grey', 'Color name should be Grey'); + assert.equal(car.related('color').get('hex_value'), '#666', 'Color hex_value should be #666'); }); }); }); @@ -92,9 +108,9 @@ describe('manager', function() { }).then(function(car) { car.related('features').sortBy('name'); - assert.equal(1, car.id, 'Car should have ID 1'); - assert.equal(2, car.related('features').length, 'There should be 2 features'); - assert.equal(2, car.related('features').pluck('name').length, 'There should be 2 names'); + assert.equal(car.id, 1, 'Car should have ID 1'); + assert.equal(car.related('features').length, 2, 'There should be 2 features'); + assert.equal(car.related('features').pluck('name').length, 2, 'There should be 2 names'); }); }); @@ -107,12 +123,12 @@ describe('manager', function() { }).then(function(make) { make.related('models').sortBy('name'); - assert.equal(1, make.id, 'Make should have ID 1'); - assert.equal(2, make.related('models').length); + assert.equal(make.id, 1, 'Make should have ID 1'); + assert.equal(make.related('models').length, 2); assert.ok(make.related('models').at(0).id, 'Model #1 should have ID, not ' + make.related('models').at(0).id); assert.ok(make.related('models').at(1).id, 'Model #2 should have ID, not ' + make.related('models').at(1).id); - assert.equal('X3', make.related('models').at(0).get('name'), 'Model #1 name should be X3, not ' + make.related('models').at(0).get('name')); - assert.equal('X5', make.related('models').at(1).get('name'), 'Model #2 name should be X5, not ' + make.related('models').at(1).get('name')); + assert.equal(make.related('models').at(0).get('name'), 'X3', 'Model #1 name should be X3, not ' + make.related('models').at(0).get('name')); + assert.equal(make.related('models').at(1).get('name'), 'X5', 'Model #2 name should be X5, not ' + make.related('models').at(1).get('name')); }); }); @@ -169,7 +185,7 @@ describe('manager', function() { }); return manager.create(ValidatedModel, { name: 'test' }).then(function(model) { - assert.equal('test', model.get('name'), 'Model should have a name of `test`, not `' + model.get('name') + '`'); + assert.equal(model.get('name'), 'test', 'Model should have a name of `test`, not `' + model.get('name') + '`'); }); }); @@ -183,7 +199,7 @@ describe('manager', function() { }, validateSave: function() { - assert.equal('number', typeof this.get('make_id'), 'Model make_id must be a number, not ' + typeof this.get('make_id')); + assert.equal(typeof this.get('make_id'), 'number', 'Model make_id must be a number, not ' + typeof this.get('make_id')); } }), 'model'); return Bootstrap.tables(manager).then(function() { @@ -211,7 +227,7 @@ describe('manager', function() { }, validateSave: function() { - assert.equal('number', typeof this.get('make_id'), 'Model make_id must be a number, not ' + typeof this.get('make_id')); + assert.equal(typeof this.get('make_id'), 'number', 'Model make_id must be a number, not ' + typeof this.get('make_id')); } }), 'model'); return Bootstrap.tables(manager).then(function() { @@ -242,16 +258,16 @@ describe('manager', function() { }, { transacting: t }).then(function(car) { - assert.equal(color.id, car.related('color').id, 'Color ID should stay the same, not ' + car.related('color').id); - assert.equal('Grey', car.related('color').get('name'), 'Color name should be Grey'); - assert.equal('#666', car.related('color').get('hex_value'), 'Color hex_value should be #666'); + assert.equal(car.related('color').id, color.id, 'Color ID should stay the same, not ' + car.related('color').id); + assert.equal(car.related('color').get('name'), 'Grey', 'Color name should be Grey'); + assert.equal(car.related('color').get('hex_value'), '#666', 'Color hex_value should be #666'); throw new Error('test'); }); }).catch(function(err) { if (!(err instanceof assert.AssertionError)) { return manager.fetch('color', { id: color.id }).then(function(color) { - assert.equal('White', color.get('name'), 'Color name should be White'); - assert.equal('#fff', color.get('hex_value'), 'Color hex_value should be #fff'); + assert.equal(color.get('name'), 'White', 'Color name should be White'); + assert.equal(color.get('hex_value'), '#fff', 'Color hex_value should be #fff'); }); } throw err; diff --git a/test/manager.fetch.js b/test/manager.fetch.js index 06bfe2c..c67eae4 100644 --- a/test/manager.fetch.js +++ b/test/manager.fetch.js @@ -21,7 +21,7 @@ describe('manager', function() { }) .then(function(make) { assert.ok(make instanceof manager.bookshelf.Model); - assert.equal('BMW', make.get('name')); + assert.equal(make.get('name'), 'BMW'); }); }); @@ -30,8 +30,8 @@ describe('manager', function() { .fetch('makes') .then(function(makes) { assert.ok(makes instanceof manager.bookshelf.Collection); - assert.equal(1, makes.length); - assert.equal('BMW', makes.get(1).get('name')); + assert.equal(makes.length, 1); + assert.equal(makes.get(1).get('name'), 'BMW'); }); }); @@ -44,6 +44,7 @@ describe('manager', function() { 'models.type', 'dealers', 'dealers.cars', + 'dealers.cars.title', 'dealers.cars.color', 'dealers.cars.model', 'dealers.cars.features', @@ -52,14 +53,15 @@ describe('manager', function() { .then(function(make) { var json = make.toJSON(); - assert.equal('BMW', json.name); - assert.equal('X5', json.models[0].name); - assert.equal(2, json.models[0].specs.length); - assert.equal('Crossover', json.models[0].type.name); - assert.equal('Houston', json.dealers[0].name); - assert.equal('Grey', json.dealers[0].cars[0].color.name); - assert.equal('X5', json.dealers[0].cars[0].model.name); - assert.equal(2, json.dealers[0].cars[0].features.length); + assert.equal(json.name, 'BMW'); + assert.equal(json.models[0].name, 'X5'); + assert.equal(json.models[0].specs.length, 2); + assert.equal(json.models[0].type.name, 'Crossover'); + assert.equal(json.dealers[0].name, 'Houston'); + assert.equal(json.dealers[0].cars[0].title.state, 'TX'); + assert.equal(json.dealers[0].cars[0].color.name, 'Grey'); + assert.equal(json.dealers[0].cars[0].model.name, 'X5'); + assert.equal(json.dealers[0].cars[0].features.length, 2); }); }); }); diff --git a/test/manager.forge.js b/test/manager.forge.js index 4880060..4075785 100644 --- a/test/manager.forge.js +++ b/test/manager.forge.js @@ -17,8 +17,8 @@ describe('manager', function() { }); assert.ok(manager.isModel(make), 'Expected a Model'); - assert.equal(undefined, make.get('id'), 'ID should not be defined yet'); - assert.equal('Ford', make.get('name'), 'Name should be set'); + assert.equal(make.get('id'), undefined, 'ID should not be defined yet'); + assert.equal(make.get('name'), 'Ford', 'Name should be set'); }); }); }); diff --git a/test/manager.get.js b/test/manager.get.js index f6d7867..a40c3fb 100644 --- a/test/manager.get.js +++ b/test/manager.get.js @@ -33,13 +33,13 @@ describe('manager', function() { it('should return a Model', function() { var Model = bookshelf.Model.extend({}); - assert.equal(Model, manager.get(Model)); + assert.equal(manager.get(Model), Model); }); it('should return a Collection', function() { var Collection = bookshelf.Collection.extend(); - assert.equal(Collection, manager.get(Collection)); + assert.equal(manager.get(Collection), Collection); }); }); }); @@ -73,13 +73,13 @@ describe('manager', function() { it('should return a Model', function() { var Model = bookshelf.Model.extend({}); - assert.equal(Model, manager.get(Model)); + assert.equal(manager.get(Model), Model); }); it('should return a Collection', function() { var Collection = bookshelf.Collection.extend(); - assert.equal(Collection, manager.get(Collection)); + assert.equal(manager.get(Collection), Collection); }); }); }); diff --git a/test/manager.register.js b/test/manager.register.js index d8bbc6a..008af0c 100644 --- a/test/manager.register.js +++ b/test/manager.register.js @@ -18,8 +18,8 @@ describe('manager', function() { }); describe('.register', function() { - it('should return function', function() { - assert.equal('function', typeof manager.register(Model, 'fake')); + it('should return a function', function() { + assert.equal(typeof manager.register(Model, 'fake'), 'function'); }); it('must be called with both a Model and a model name', function() { diff --git a/test/manager.save.js b/test/manager.save.js index d12f595..dff1c7f 100644 --- a/test/manager.save.js +++ b/test/manager.save.js @@ -27,7 +27,7 @@ describe('manager', function() { var car = manager.forge('car'); return manager.save(car).then(function(car) { - assert.equal(2, car.id, 'Car should have an ID of 2, not ' + car.id); + assert.equal(car.id, 2, 'Car should have an ID of 2, not ' + car.id); }); }); @@ -43,50 +43,60 @@ describe('manager', function() { name: 'Chevy' }); }).then(function(make) { - assert.equal(original.id, make.id, 'Should have overriden original model ID'); + assert.equal(make.id, original.id, 'Should have overriden original model ID'); }).then(function() { return manager.fetch('makes'); }).then(function(makes) { - assert.equal(2, makes.length, 'Should only have 2 makes, not ' + makes.length); + assert.equal(makes.length, 2, 'Should only have 2 makes, not ' + makes.length); }); }); it('should modify the model', function() { return manager.fetch('car', { id: 1 }).then(function(car) { - assert.equal(1, car.get('quantity'), 'Car #1 should start with quantity of 1'); + assert.equal(car.get('quantity'), 1, 'Car #1 should start with quantity of 1'); return manager.save(car, { quantity: 2 }); }).then(function(car) { - assert.equal(2, car.get('quantity'), 'Car #1 should end with quantity of 2'); + assert.equal(car.get('quantity'), 2, 'Car #1 should end with quantity of 2'); }); }); - it('should modify a nested model', function() { - return manager.fetch('car', { id: 1 }, 'color').then(function(car) { - assert.equal(1, car.related('color').id); - assert.equal('Grey', car.related('color').get('name')); + it('should modify nested models', function() { + return manager.fetch('car', { id: 1 }, ['color', 'title']).then(function(car) { + assert.equal(car.related('color').id, 1); + assert.equal(car.related('color').get('name'), 'Grey'); + assert.equal(car.related('title').id, 1); + assert.equal(car.related('title').get('state'), 'TX'); return manager.save(car, { color: { id: 1, name: 'Dark Grey' + }, + title: { + id: 1, + state: 'FL', + issue_date: '2017-01-01' } }); }).then(function(car) { return car.fetch({ - withRelated: 'color' + withRelated: ['color', 'title'] }); }).then(function(car) { - assert.equal(1, car.related('color').id); - assert.equal('Dark Grey', car.related('color').get('name')); + assert.equal(car.related('color').id, 1); + assert.equal(car.related('color').get('name'), 'Dark Grey'); + assert.equal(car.related('title').id, 1); + assert.equal(car.related('title').get('state'), 'FL'); + assert.equal(car.related('title').get('issue_date'), '2017-01-01'); }); }); it('should modify a deep nested model', function() { return manager.fetch('car', { id: 1 }, 'model.type').then(function(car) { - assert.equal('Crossover', car.related('model').related('type').get('name')); + assert.equal(car.related('model').related('type').get('name'), 'Crossover'); return manager.save(car, { model: { @@ -102,7 +112,7 @@ describe('manager', function() { withRelated: 'model.type' }); }).then(function(car) { - assert.equal('SUV', car.related('model').related('type').get('name')); + assert.equal(car.related('model').related('type').get('name'), 'SUV'); }); }); @@ -115,19 +125,19 @@ describe('manager', function() { return manager.save(feature, json); }).then(function(feature) { - assert.equal('GPSv2', feature.get('name')); + assert.equal(feature.get('name'), 'GPSv2'); }); }); it('should orphan models in collection', function() { return manager.fetch('car', { id: 1 }, 'features').then(function(car) { - assert.equal(2, car.related('features').length, 'Car should have 2 existing features'); + assert.equal(car.related('features').length, 2, 'Car should have 2 existing features'); return manager.save(car, { id: 1, features: [] }).then(function(car) { - assert.equal(0, car.related('features').length, 'Car should have all features removed, found: ' + car.related('features').toJSON()); + assert.equal(car.related('features').length, 0, 'Car should have all features removed, found: ' + car.related('features').toJSON()); }); }); }); @@ -142,6 +152,7 @@ describe('manager', function() { 'models.type', 'dealers', 'dealers.cars', + 'dealers.cars.title', 'dealers.cars.color', 'dealers.cars.model', 'dealers.cars.features', @@ -153,11 +164,11 @@ describe('manager', function() { }).then(function(make) { var diffs = deep.diff(expected, make.toJSON()) || []; - assert.equal(0, diffs.length, diffs); + assert.equal(diffs.length, 0, diffs); return manager.knex('models_specs').select(); }).then(function(results) { - assert.equal(2, results.length, 'Expected only 2 rows in `models_specs`, not ' + results.length); + assert.equal(results.length, 2, 'Expected only 2 rows in `models_specs`, not ' + results.length); }); }); @@ -171,16 +182,16 @@ describe('manager', function() { }, { transacting: t }).then(function(car) { - assert.equal(2, car.get('quantity', 'Car should have quantity 2, got: ' + car.get('quantity'))); - assert.equal(0, car.related('features').length, 'Car should have all features removed, found: ' + car.related('features').toJSON()); + assert.equal(car.get('quantity'), 2, 'Car should have quantity 2, got: ' + car.get('quantity')); + assert.equal(car.related('features').length, 0, 'Car should have all features removed, found: ' + car.related('features').toJSON()); throw new Error('test'); }); }); }).catch(function(err) { if (!(err instanceof assert.AssertionError)) { return manager.fetch('car', { id: 1 }, 'features').then(function(car) { - assert.equal(1, car.get('quantity', 'Car should have quantity 1, got: ' + car.get('quantity'))); - assert.equal(2, car.related('features').length, 'Car should have 2 existing features'); + assert.equal(car.get('quantity'), 1, 'Car should have quantity 1, got: ' + car.get('quantity')); + assert.equal(car.related('features').length, 2, 'Car should have 2 existing features'); }); } throw err; @@ -194,7 +205,33 @@ describe('manager', function() { name: 'X5', type: null }).then(function(model) { - assert.equal(null, model.get('type_id')); + assert.equal(model.get('type_id'), null); + }); + }); + }); + + it('should save a new hasOne model and orphan any existing one if its id is unspecified', function() { + return manager.fetch('car', { id: 1 }, 'title').then(function(car) { + assert.equal(car.related('title').get('state'), 'TX'); + return manager.save(car, { + title: { + state: 'FL', + issue_date: '2017-01-01' + } + }); + }) + .then(function() { + return manager.fetch('car', { id: 1 }, 'title').then(function(car) { + assert.equal(car.related('title').get('id'), 2); + assert.equal(car.related('title').get('state'), 'FL'); + assert.equal(car.related('title').get('issue_date'), '2017-01-01'); + }); + }) + .then(function() { + return manager.fetch('title', { id: 1 }).then(function(title) { + assert.equal(title.get('car_id'), null); + assert.equal(title.get('state'), 'TX'); + assert.equal(title.get('issue_date'), '2016-09-19'); }); }); }); diff --git a/test/models/car.js b/test/models/car.js index a2dde46..5f84cc0 100644 --- a/test/models/car.js +++ b/test/models/car.js @@ -6,6 +6,10 @@ var Car = function(Bookshelf) { quantity: 0 }, + title: function() { + return this.hasOne('title', 'car_id'); + }, + color: function() { return this.belongsTo('color'); }, diff --git a/test/models/title.js b/test/models/title.js new file mode 100644 index 0000000..4a9651a --- /dev/null +++ b/test/models/title.js @@ -0,0 +1,11 @@ +var Title = function(Bookshelf) { + return Bookshelf.Model.extend({ + tableName: 'titles', + + car: function() { + return this.belongsTo('car'); + } + }); +}; + +module.exports = Title; diff --git a/test/support/bootstrap.fixtures.js b/test/support/bootstrap.fixtures.js index 135f871..87cd81c 100644 --- a/test/support/bootstrap.fixtures.js +++ b/test/support/bootstrap.fixtures.js @@ -8,6 +8,7 @@ module.exports = function(manager) { var specs = manager.forge('specs', [ { name: '4 door' }, { name: 'v6' } ]); var color = manager.forge('color', { name: 'Grey', hex_value: '#666' }); var dealer = manager.forge('dealer', { name: 'Houston', zip_code: '77002' }); + var title = manager.forge('title', { state: 'TX', issue_date: '2016-09-19'}); var features = manager.forge('features', [ { name: 'GPS', cost: '500' }, { name: 'ABS', cost: '1250' }]); var car = manager.forge('car'); @@ -47,6 +48,10 @@ module.exports = function(manager) { return car.save(); }) + .then(function() { + return title.set('car_id', car.get('id')) + .save(); + }) .then(function() { return car.features().attach(features.toArray()); }) diff --git a/test/support/bootstrap.tables.js b/test/support/bootstrap.tables.js index 3b3c5ec..862c624 100644 --- a/test/support/bootstrap.tables.js +++ b/test/support/bootstrap.tables.js @@ -71,6 +71,14 @@ module.exports = function(manager) { table.increments('id'); table.string('name'); }); + }) + .then(function() { + return schema.createTableIfNotExists('titles', function(table) { + table.increments('id'); + table.integer('car_id'); + table.text('state'); + table.text('issue_date'); + }); }); }); };