diff --git a/.jscs.json b/.jscs.json new file mode 100644 index 0000000..96c3f0c --- /dev/null +++ b/.jscs.json @@ -0,0 +1,7 @@ +{ + "preset": "node-style-guide", + "requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties", + "maximumLineLength": null, + "requireTrailingComma": null, + "disallowTrailingComma": true +} diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..c1f2978 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,3 @@ +{ + "node": true +} diff --git a/README.md b/README.md index 6d4dce3..aae28eb 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,46 @@ > Model & Collection manager for [Bookshelf.js][1] to make it easy to create & > save deep, nested JSON structures from API requests. +## Installation + + npm install bookshelf-manager --save + +## Usage + + 1. Register as a plugin in Bookshelf: + + ```javascript + bookshelf.plugin('bookshelf-manager'); + ``` + + - Optionally, you can pass in an object with a `root` property to read models from a specified directory: + + ```javascript + bookshelf.plugin('bookshelf-manager', { root: 'path/to/models' }); + ``` + + 2. Register individual models (not required if you passed in a `root` model directory as above): + + ```javascript + bookshelf.manager.register(model, modelName); + ``` + + - Note: Also compatible with models registered with the [Bookshelf Registry](https://github.com/tgriesser/bookshelf/wiki/Plugin:-Model-Registry) plugin. + + 3. Use the methods on `bookshelf.manager` to create, fetch, and save models or collections with support for deeply-nested attributes. E.g.: + + ```javascript + return bookshelf.manager.create('car', { + features: [ + { name: 'ABS', cost: '1250' }, + { name: 'GPS', cost: '500' } + ], + quantity: 1 + }).then(function(car) { + // created car should now have the associated features + }); + ``` + ## API @@ -15,6 +55,7 @@ ## Changelog +- v0.1.0 - Reimplement as a plugin for Bookshelf/Knex 0.8.x - v0.0.10 - Enforce `belongsToMany` IDs - v0.0.9 - Destroy removed `hasMany` models - v0.0.8 - Fetch empty collections diff --git a/index.js b/index.js index c762781..2dc14e5 100644 --- a/index.js +++ b/index.js @@ -1 +1 @@ -module.exports = require('./lib/manager'); +module.exports = require('./lib/manager').plugin; diff --git a/lib/manager.js b/lib/manager.js index 3af47be..4a8b6a7 100644 --- a/lib/manager.js +++ b/lib/manager.js @@ -1,43 +1,38 @@ -var Bookshelf = require('bookshelf'); -var hooker = require('hooker'); var path = require('path'); var Promise = require('bluebird'); -var Manager = function(root, bookshelf) { - if (!root) { - throw new Error('Manager requires a path to model directory'); +function Manager(bookshelf, options) { + + if (!(arguments.length > 0 && bookshelf && bookshelf.VERSION)) { + throw new Error('Manager requires a Bookshelf instance'); } - this.root = path.normalize(root); + this.root = options && options.root ? path.normalize(options.root) : null; - if (bookshelf) { - this.initialize(bookshelf); - } else { - hooker.hook(Bookshelf, 'initialize', { - once: true, - post: this.initialize.bind(this) - }); - } + this.initialize(bookshelf); +} + +Manager.plugin = function(bookshelf, options) { + return new Manager(bookshelf, options); }; -Manager.manage = function(model) { - if (!Manager.manages(model)) { - Manager._managed.push(model); +Manager.prototype.register = function(model, name) { + + if (!(arguments.length >= 2 && model && typeof name === 'string')) { + throw new Error('Manager.register must be called with a model and a model name'); } - return model; -}; + var Model = this.get(model); -Manager.manages = function(model) { - if (!model instanceof Function) { - return false; + if (this.isModel(Model)) { + this.bookshelf.model(name, Model); + } else if (this.isCollection(Model)) { + this.bookshelf.collection(name, Model); } - return Manager._managed.indexOf(model) !== -1 + return Model; }; -Manager._managed = []; - Manager.prototype.initialize = function(bookshelf) { this.bookshelf = bookshelf; this.knex = this.bookshelf.knex; @@ -45,97 +40,28 @@ Manager.prototype.initialize = function(bookshelf) { bookshelf.plugin('registry'); - hooker.hook(bookshelf, ['collection', 'model'], function(name, value) { - if (!value) { - return hooker.preempt(this.get(name)); - } - }.bind(this)); + // Expose the Bookshelf Manager instance on the Bookshelf instance + bookshelf.manager = this; return this; }; -Manager.prototype._cache = {}; - -Manager.prototype.create = function(name, properties) { +Manager.prototype.create = function(name, properties, options) { var Model = this.get(name); var model = new Model(); - return this.save(model, properties).catch(function(error) { + return this.save(model, properties, options).catch(function(error) { console.error(error.stack); throw error; }); }; -Manager.prototype.debug = function(debug) { - var level = 1; - var methods = [ - 'create', - 'fetch', - 'forge', - 'get', - 'load', - 'save', - 'saveCollection', - 'saveModel', - 'set', - 'setBelongsTo', - 'setBelongsToMany', - 'setCollection', - 'setHasMany', - 'setModel', - 'setScalar', - ]; - - var pre = function(name) { - var indent = new Array(level++).join(' '); - var sliced = Array.prototype.slice.call(arguments, 1); - var args = sliced.map(function(arg) { - if (this.isCollection(arg)) { - return '[Collection] (' + arg.length + ')'; - } - - if (this.isModel(arg)) { - return '[Model `' + arg.tableName + '@' + arg.id + '`]'; - } - - return (JSON.stringify(arg) || 'null').split('\n').join('\n' + indent + ' '); - }.bind(this)).join(',\n' + indent + ' '); - - console.log(indent, '<' + name + '>', '\n' + indent + ' ', args); - }; - - var post = function(result, name) { - if (result && result.then) { - result.then(function() { - console.log(new Array(--level).join(' '), ''); - - return arguments[0]; - }); - }; - }; - - if (typeof debug === 'undefined') { - debug = true; - } - - if (debug) { - hooker.hook(this, methods, { - passName: true, - pre: pre, - post: post - }); - } else { - hooker.unhook(this, methods); - } - - return this; -}; - -Manager.prototype.fetch = function(name, properties, related) { +Manager.prototype.fetch = function(name, properties, related, options) { var model = this.forge(name, properties); return model.fetch({ withRelated: related, + transacting: options && options.transacting ? options.transacting : null }); }; @@ -188,25 +114,32 @@ Manager.prototype.get = function(Model) { if (typeof Model === 'string') { name = Model; - if (this._cache[name]) { - return this._cache[name]; + Model = this.bookshelf.collection(name) || this.bookshelf.model(name); + + if (this.isModelOrCollection(Model)) { + return Model; } - var file = path.join(this.root, name); + if (this.root) { - try { - Model = require(file); - } catch (e) { - throw new Error('Could not find module `' + name + '` at `' + file + '.js`'); + var file = path.join(this.root, name); + + try { + Model = require(file); + } catch (e) { + throw new Error('Could not find module `' + name + '` at `' + file + '.js`'); + } + } else { + throw new Error('No model named `' + name + '` has been registered and no model directory was specified'); } } - if (Manager.manages(Model)) { + if (!this.isModelOrCollection(Model)) { Model = Model(this.bookshelf); } - if (!this.isModel(Model) && !this.isCollection(Model)) { - throw new Error('Expected a String, Model, Collection, or a Managed Model/Collection, got: ' + typeof Model); + if (!this.isModelOrCollection(Model)) { + throw new Error('Expected a String, Model, or Collection, got: ' + typeof Model); } if (this.isCollection(Model) && typeof Model.prototype.model === 'string') { @@ -215,15 +148,19 @@ Manager.prototype.get = function(Model) { if (name) { if (this.isModel(Model)) { - this._cache[name] = this.bookshelf.model(name, Model); + this.bookshelf.model(name, Model); } else if (this.isCollection(Model)) { - this._cache[name] = this.bookshelf.collection(name, Model); + this.bookshelf.collection(name, Model); } } return Model; }; +Manager.prototype.isModelOrCollection = function(model) { + return (this.isModel(model) || this.isCollection(model)); +}; + Manager.prototype.isModel = function(model) { if (!model || this.isCollection(model)) { return false; @@ -240,26 +177,26 @@ Manager.prototype.isCollection = function(model) { return model instanceof this.bookshelf.Collection || model.prototype instanceof this.bookshelf.Collection; }; -Manager.prototype.save = function(model, properties) { +Manager.prototype.save = function(model, properties, options) { if (this.isModel(model)) { - return this.saveModel(model, properties); + return this.saveModel(model, properties, options); } else if (this.isCollection(model)) { - return this.saveCollection(model, properties); + return this.saveCollection(model, properties, options); } throw new Error('Object should be an instance of Model or Collection, not ' + typeof model); }; -Manager.prototype.saveCollection = function(collection, models) { +Manager.prototype.saveCollection = function(collection, models, options) { return collection.mapThen(function(model) { - return (model.isNew() || model.hasChanged()) ? model.save() : model; + return (model.isNew() || model.hasChanged()) ? model.save(null, options) : model; }).then(function() { - return this.setCollection(collection, models); + return this.setCollection(collection, models, options); }.bind(this)).then(function(targets) { return targets.mapThen(function(target) { collection.add(target); - return (target.isNew() || target.hasChanged()) ? target.save() : target; + return (target.isNew() || target.hasChanged()) ? target.save(null, options) : target; }); }).then(function() { return collection; @@ -269,84 +206,103 @@ Manager.prototype.saveCollection = function(collection, models) { }); }; -Manager.prototype.saveModel = function(model, properties) { - return this.setModel(model, properties).then(function(result) { - return (result.isNew() || result.hasChanged()) ? result.save() : result; +Manager.prototype.saveModel = 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) { +Manager.prototype.set = function(model, properties, options) { if (this.isModel(model)) { - return this.setModel(model, properties); + return this.setModel(model, properties, options); } else if (this.isCollection(model)) { - return this.setCollection(model, properties); + return this.setCollection(model, properties, options); } throw new Error('Object should be an instance of Model or Collection, not ' + typeof model); }; -Manager.prototype.setModel = function(model, properties) { +Manager.prototype.setModel = function(model, properties, options) { var promises = []; - properties = properties || {}; + properties = (typeof properties === 'object' && !Array.isArray(properties) && properties !== null) ? properties : {}; if (model.isNew() && properties && properties.id) { promises.push(function() { - return this.fetch(model, { id: properties.id }, this.findRelated(properties)).then(function(result) { + return this.fetch(model, { id: properties.id }, this.findRelated(properties), options).then(function(result) { return result; }); }.bind(this)); } else { promises.push(function() { - return (model.isNew() || model.hasChanged()) ? model.save() : model; + return model; }); } - Object.keys(properties).forEach(function(key) { - var value = properties[key]; - var relation = model[key] instanceof Function ? model[key].call(model) : null; - var type = relation ? relation.relatedData.type : 'scalar'; - var method = 'set' + type.charAt(0).toUpperCase() + type.slice(1); - var setter = this[method].bind(this); + function setProperties(propertyType) { + + Object.keys(properties).forEach(function(key) { + var value = properties[key]; + var relation = (model[key] instanceof Function && (typeof value === 'object' || Array.isArray(value))) ? model[key].call(model) : null; + var type = relation ? relation.relatedData.type : 'scalar'; + var method = 'set' + type.charAt(0).toUpperCase() + type.slice(1); + var setter = this[method].bind(this); + + if ((type === 'scalar' && propertyType === 'scalar') || (type !== 'scalar' && propertyType === 'related')) { + promises.push(function(result) { + return setter(result, key, value, relation, options).then(function() { + return result; + }); + }); + } + }.bind(this)); + } - promises.push(function(result) { - return setter(result, key, value, relation).then(function() { - return result; - }); - }); - }.bind(this)); + setProperties.bind(this)('scalar'); + + promises.push(function(result) { + return (result.isNew() || result.hasChanged()) ? result.save(null, options) : result; + }); + + setProperties.bind(this)('related'); return Promise.reduce(promises, function(result, promise) { return promise(result); }, []); }; -Manager.prototype.setBelongsTo = function(model, key, value, relation) { +Manager.prototype.setBelongsTo = function(model, key, value, relation, options) { var Target = relation.relatedData.target; var existing = model.related(key); var target = existing.isNew() ? Target.forge() : existing.clone(); + var fk = relation.relatedData.foreignKey; - return this.save(target, value).then(function(target) { - var fk = relation.relatedData.foreignKey; + if (value === null) { + if (model.get(fk)) { + model.set(fk, null); + } + return Promise.resolve((model.isNew() || model.hasChanged()) ? model.save(null, options) : model); + } + return this.save(target, value, options).then(function(target) { if (model.get(fk) !== target.id) { model.set(fk, target.id); } model.relations[key] = target; - return (model.isNew() || model.hasChanged()) ? model.save() : model; + return (model.isNew() || model.hasChanged()) ? model.save(null, options) : model; }); }; -Manager.prototype.setBelongsToMany = function(model, key, models, relation) { +Manager.prototype.setBelongsToMany = function(model, key, models, relation, options) { var existing = model.related(key); - return Promise.cast(existing.length ? existing : existing.fetch()).then(function() { - return this.setCollection(existing, models); + return Promise.cast(existing.length ? existing : existing.fetch(options)).then(function() { + return this.setCollection(existing, models, options); }.bind(this)).then(function(targets) { // Enforce attach/detach IDs existing.relatedData.parentId = model.id; @@ -354,12 +310,12 @@ Manager.prototype.setBelongsToMany = function(model, key, models, relation) { return targets.mapThen(function(target) { if (!existing.findWhere({ id: target.id })) { - return existing.attach(target); + return existing.attach(target, options); } }).then(function() { return existing.mapThen(function(target) { if (!targets.findWhere({ id: target.id })) { - return existing.detach(target); + return existing.detach(target, options); } }); }); @@ -368,32 +324,34 @@ Manager.prototype.setBelongsToMany = function(model, key, models, relation) { }); }; -Manager.prototype.setHasMany = function(model, key, models, relation) { +Manager.prototype.setHasMany = function(model, key, models, relation, options) { var existing = model.related(key); - return this.setCollection(existing, models).then(function(targets) { - var fk = relation.relatedData.foreignKey; + var fk = relation.relatedData.foreignKey; - if (!fk) { - throw new Error('`' + model.tableName + '#' + key + '` relation is missing `foreignKey` in `this.hasMany(Target, foreignKey)`'); + if (!fk) { + throw new Error('`' + model.tableName + '#' + key + '` relation is missing `foreignKey` in `this.hasMany(Target, foreignKey)`'); + } + + models = models.map(function(target) { + if (!target[fk]) { + target[fk] = model.id; } + return target; + }); - return targets.mapThen(function(target) { - var properties = {}; + return this.setCollection(existing, models, options).then(function(targets) { - properties[fk] = model.id; + targets.forEach(function(target) { + existing.add(target); + }); - return this.save(target, properties).then(function(target) { - existing.add(target); - }); - }.bind(this)).then(function() { - return existing.mapThen(function(target) { - if (!targets.findWhere({ id: target.id })) { - return target.destroy(); - } - }); + return existing.mapThen(function(target) { + if (!targets.findWhere({ id: target.id })) { + return target.destroy(options); + } }); - }.bind(this)).then(function() { + }).then(function() { return model; }); }; @@ -412,13 +370,13 @@ Manager.prototype.setScalar = Promise.method(function(model, key, value) { return model; }); -Manager.prototype.setCollection = function(existing, models) { +Manager.prototype.setCollection = function(existing, models, options) { models = models || []; return Promise.map(models, function(properties) { var model = existing.findWhere({ id: properties.id }) || existing.model.forge(); - return this.save(model, properties); + return this.save(model, properties, options); }.bind(this)).then(function(results) { return this.bookshelf.Collection.forge(results); }.bind(this)); diff --git a/package.json b/package.json index 48dc9c4..decd73c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bookshelf-manager", - "version": "0.0.10", + "version": "0.1.0", "description": "Easily wire up models to APIs with supported for complex, nested saving.", "main": "index.js", "scripts": { @@ -23,13 +23,16 @@ "url": "https://github.com/ericclemmons/bookshelf-manager/issues" }, "devDependencies": { - "mocha": "~1.17.1", - "mysql": "~2.1.0" + "bluebird": "^2.9.30", + "bookshelf": "^0.8.2", + "deep-diff": "^0.3.2", + "knex": "^0.8.6", + "mocha": "^2.2.5", + "sqlite3": "^3.0.8" }, - "dependencies": { - "bookshelf": "~0.6.4", - "bluebird": "~1.0.7", - "hooker": "^0.2.3", - "deep-diff": "^0.1.4" + "peerDependencies": { + "bluebird": "2.x", + "bookshelf": "^0.8.0", + "knex": "^0.8.0" } } diff --git a/test/.jshintrc b/test/.jshintrc new file mode 100644 index 0000000..e26f0d4 --- /dev/null +++ b/test/.jshintrc @@ -0,0 +1,4 @@ +{ + "extends": "../.jshintrc", + "mocha": true +} diff --git a/test/databases/mysql.js b/test/databases/mysql.js deleted file mode 100644 index f40f650..0000000 --- a/test/databases/mysql.js +++ /dev/null @@ -1,10 +0,0 @@ -var Bookshelf = require('bookshelf'); - -var MySql = Bookshelf.initialize({ - client: 'mysql', - connection: { - user: 'root', - } -}); - -module.exports = MySql; diff --git a/test/databases/test.js b/test/databases/test.js deleted file mode 100644 index d9de68b..0000000 --- a/test/databases/test.js +++ /dev/null @@ -1,11 +0,0 @@ -var Bookshelf = require('bookshelf'); - -var Test = Bookshelf.initialize({ - client: 'mysql', - connection: { - user: 'root', - database: 'bookshelf_manager_test', - } -}); - -module.exports = Test; diff --git a/test/manager.create.js b/test/manager.create.js index fe2109f..b5714c2 100644 --- a/test/manager.create.js +++ b/test/manager.create.js @@ -1,62 +1,56 @@ var assert = require('assert'); -var Promise = require('bluebird'); var Bootstrap = require('./support/bootstrap'); -var manager = require('./support/manager'); describe('manager', function() { describe('.create', function() { - beforeEach(function(done) { - Bootstrap.database().then(function() { - return Bootstrap.tables(); - }).then(function() { - done(); - }); + var manager; + + beforeEach(function() { + manager = Bootstrap.manager(Bootstrap.database()); + Bootstrap.models(manager); + return Bootstrap.tables(manager); }); - it('should create a new model', function(done) { - manager.create('car').then(function(car) { + it('should create a new model', function() { + return manager.create('car').then(function(car) { assert.ok(manager.isModel(car), 'Car should be a Model'); assert.ok(car.id, 'Car should have ID 1'); - done(); }); }); - it('should create a new collection', function(done) { - manager.create('cars').then(function(cars) { + it('should create a new collection', function() { + return manager.create('cars').then(function(cars) { assert.ok(manager.isCollection(cars), 'Car should be a Collection'); - done(); }); }); - it('should create a new, populated model', function(done) { - manager.create('car', { + it('should create a new, populated model', 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'); - done(); }); - }) + }); - it('should create a new, populated collection', function(done) { - manager.create('cars', [ + it('should create a new, populated collection', function() { + return manager.create('cars', [ { quantity: 1 }, - { quantity: 2 }, + { quantity: 2 } ]).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'); - done(); }); - }) + }); - it('should create a model within a new model (belongsTo)', function(done) { - manager.create('car', { + it('should create a model within a new model (belongsTo)', function() { + return manager.create('car', { color: { name: 'White', - hex_value: '#fff', + hex_value: '#fff' }, quantity: 1 }).then(function(car) { @@ -65,36 +59,34 @@ describe('manager', function() { 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'); - done(); - }) + }); }); - it('should modify an existing nested model', function(done) { - manager.create('color', { + it('should modify an existing nested model', function() { + return manager.create('color', { name: 'White', - hex_value: '#fff', + hex_value: '#fff' }).then(function(color) { - manager.create('car', { + return manager.create('car', { color: { id: color.id, name: 'Grey', - hex_value: '#666', + hex_value: '#666' }, 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'); - done(); }); }); }); - it('should create models within a nested collection (belongsToMany)', function(done) { - manager.create('car', { + it('should create models within a nested collection (belongsToMany)', function() { + return manager.create('car', { features: [ { name: 'ABS', cost: '1250' }, - { name: 'GPS', cost: '500' }, + { name: 'GPS', cost: '500' } ], quantity: 1 }).then(function(car) { @@ -103,15 +95,14 @@ describe('manager', function() { 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'); - done(); }); }); - it('should create models within a nested collection (hasMany)', function(done) { - manager.create('make', { + it('should create models within a nested collection (hasMany)', function() { + return manager.create('make', { models: [ { name: 'X3' }, - { name: 'X5' }, + { name: 'X5' } ] }).then(function(make) { make.related('models').sortBy('name'); @@ -122,12 +113,11 @@ describe('manager', function() { 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')); - done(); }); }); - it('should create a deep object', function(done) { - manager.create('make', { + it('should create a deep object', function() { + return manager.create('make', { name: 'BMW', models: [ { @@ -138,7 +128,7 @@ describe('manager', function() { }, specs: [ { name: '4 door' }, - { name: 'v6' }, + { name: 'v6' } ] } ] @@ -155,11 +145,136 @@ describe('manager', function() { ); assert.equal( - JSON.stringify(actual.related('models').at(0).related('type').toJSON(), null, 2), - JSON.stringify(result.related('models').at(0).related('type').toJSON(), null, 2) + actual.related('models').at(0).related('type').id, + result.related('models').at(0).related('type').id + ); + + assert.equal( + actual.related('models').at(0).related('type').name, + result.related('models').at(0).related('type').name ); + }); + }); + }); + + it('should set scalar attributes before saving new models', function() { + var ValidatedModel = manager.get('model').extend({ + initialize: function() { + this.on('saving', this.validateSave); + }, + + validateSave: function() { + assert(typeof this.get('name') === 'string', 'Model name must be a string, not ' + typeof this.get('name')); + } + }); + + 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') + '`'); + }); + }); + + describe('foreign key support', function() { + + it('should set foreign keys before saving child models', function() { + var manager = Bootstrap.manager(Bootstrap.database()); + manager.register(require('./models/model')(manager.bookshelf).extend({ + initialize: function() { + this.on('saving', this.validateSave); + }, + + validateSave: function() { + assert.equal('number', typeof this.get('make_id'), 'Model make_id must be a number, not ' + typeof this.get('make_id')); + } + }), 'model'); + return Bootstrap.tables(manager).then(function() { + return manager.create('make', { + name: 'BMW', + models: [ + { name: 'X3' }, + { name: 'X5' } + ] + }); + }); + }); + + it('even when the foreign key is the same as the name of the relation', function() { + var manager = Bootstrap.manager(Bootstrap.database()); + manager.register(manager.bookshelf.Model.extend({ + tableName: 'models', + + make_id: function() { + return this.belongsTo('make'); + }, + + initialize: function() { + this.on('saving', this.validateSave); + }, + + validateSave: function() { + assert.equal('number', typeof this.get('make_id'), 'Model make_id must be a number, not ' + typeof this.get('make_id')); + } + }), 'model'); + return Bootstrap.tables(manager).then(function() { + return manager.create('make', { + name: 'BMW', + models: [ + { name: 'X3' }, + { name: 'X5' } + ] + }); + }); + }); + }); + + it('should support transactions', function() { + return manager.create('color', { + name: 'White', + hex_value: '#fff' + }).then(function(color) { + return manager.bookshelf.transaction(function(t) { + return manager.create('car', { + color: { + id: color.id, + name: 'Grey', + hex_value: '#666' + }, + quantity: 2 + }, { + 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'); + 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'); + }); + } else { + throw err; + } + }); + }); + }); + + it('should not create empty models when a related attribute is present but set to null', function() { + var manager = Bootstrap.manager(Bootstrap.database()); + manager.register(require('./models/type')(manager.bookshelf).extend({ + initialize: function() { + this.on('saving', this.validateSave); + }, - done(); + validateSave: function() { + throw new Error('should not save models for null attributes!'); + } + }), 'type'); + return Bootstrap.tables(manager).then(function() { + return manager.create('model', { + name: 'X5', + type: null }); }); }); diff --git a/test/manager.fetch.js b/test/manager.fetch.js index fa2dd0f..06bfe2c 100644 --- a/test/manager.fetch.js +++ b/test/manager.fetch.js @@ -1,75 +1,66 @@ var assert = require('assert'); - var Bootstrap = require('./support/bootstrap'); -var manager = require('./support/manager'); describe('manager', function() { + var manager; + describe('.fetch', function() { - before(function(done) { - Bootstrap.database().then(function() { - return Bootstrap.tables(); - }).then(function() { - return Bootstrap.fixtures(); - }).then(function() { - done(); + before(function() { + manager = Bootstrap.manager(Bootstrap.database()); + Bootstrap.models(manager); + return Bootstrap.tables(manager) + .then(function() { + return Bootstrap.fixtures(manager); }); }); - it('should return a Model', function(done) { - manager + it('should return a Model', function() { + return manager .fetch('make', { name: 'BMW' }) .then(function(make) { assert.ok(make instanceof manager.bookshelf.Model); assert.equal('BMW', make.get('name')); - }) - .then(done.bind(this, null), done) - ; + }); }); - it('should return a Collection', function(done) { - manager + it('should return a Collection', function() { + return manager .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')); - }) - .then(done.bind(this, null), done) - ; + }); }); describe('related', function() { - it('should return nested Models', function(done) { - manager - .fetch('make', { name: 'BMW' }, [ - 'models', - 'models.specs', - 'models.type', - 'dealers', - 'dealers.cars', - 'dealers.cars.color', - 'dealers.cars.model', - 'dealers.cars.features', - 'dealers.cars.model.type', - ]).then(function(make) { - var json = make.toJSON(); - - // console.log(JSON.stringify(json, null, 2)); - - 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); + it('should return nested Models', function() { + return manager + .fetch('make', { name: 'BMW' }, [ + 'models', + 'models.specs', + 'models.type', + 'dealers', + 'dealers.cars', + 'dealers.cars.color', + 'dealers.cars.model', + 'dealers.cars.features', + 'dealers.cars.model.type' + ]) + .then(function(make) { + var json = make.toJSON(); - done(); - }) - ; + 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); + }); }); }); }); diff --git a/test/manager.forge.js b/test/manager.forge.js index 179680e..4880060 100644 --- a/test/manager.forge.js +++ b/test/manager.forge.js @@ -1,15 +1,14 @@ var assert = require('assert'); var Bootstrap = require('./support/bootstrap'); -var manager = require('./support/manager'); - describe('manager', function() { describe('.forge', function() { - beforeEach(function(done) { - return Bootstrap.database().then(function() { - done(); - }); + var manager; + + before(function() { + manager = Bootstrap.manager(Bootstrap.database()); + Bootstrap.models(manager); }); it('should create a new Model in memory', function() { diff --git a/test/manager.get.js b/test/manager.get.js index 96b6f3f..f6d7867 100644 --- a/test/manager.get.js +++ b/test/manager.get.js @@ -1,75 +1,86 @@ var assert = require('assert'); var path = require('path'); -var Promise = require('bluebird'); +var Manager = require('../lib/manager'); var Bootstrap = require('./support/bootstrap'); -var Manager = require('../lib/manager'); -var manager = require('./support/manager'); -var Test = require('./databases/test'); - -var ManagedModel = Manager.manage(function(Bookshelf) { - return Bookshelf.Model.extend({ - table: 'managed_model' - }); -}); describe('manager', function() { describe('.get', function() { - before(function(done) { - Bootstrap.database().then(function() { - done(); + var manager, bookshelf; + + describe('if no root was specified', function() { + before(function() { + bookshelf = Bootstrap.database(); + manager = new Manager(bookshelf); + Bootstrap.models(manager); }); - }); - it('should throw an error if not initialized', function() { - var manager = new Manager(path.join(__dirname, 'models')); - var expected = 'Manager has not been initialized with instance of Bookshelf'; + describe('with a name', function() { + describe('if the model hasn\'t been registered yet', function() { + it('should throw an error', function() { + assert.throws(function() { manager.get('fake'); }, 'No model named `fake` has been registered and no model directory was specified'); + }); + }); - assert.throws(function() { manager.create('make'); }, expected); - assert.throws(function() { manager.get('make'); }, expected); - assert.throws(function() { manager.fetch('make'); }, expected); - assert.throws(function() { manager.forge('make'); }, expected); - assert.throws(function() { manager.save('make'); }, expected); - }); + describe('if the model has been registered', function() { + it('should return the Model', function() { + assert.ok(manager.get('make').prototype instanceof bookshelf.Model); + }); + }); + }); - it('should throw an error if file not found', function() { - assert.throws(function() { manager.get('fake'); }, /Could not find module/); - }); + describe('with a Model', function() { + it('should return a Model', function() { + var Model = bookshelf.Model.extend({}); - it('should register in cache', function() { - assert.equal(manager.get('make'), manager._cache['make']); - assert.equal(manager.get('makes'), manager._cache['makes']); - assert.equal(manager.get('make'), Test.model('make')); - assert.equal(manager.get('makes'), Test.collection('makes')); - }); + assert.equal(Model, manager.get(Model)); + }); - describe('with a name', function() { - it('should return a Model', function() { - assert.ok(manager.get('make').prototype instanceof Test.Model); - }); + it('should return a Collection', function() { + var Collection = bookshelf.Collection.extend(); - it('should return a Collection', function() { - assert.ok(manager.get('makes').prototype instanceof Test.Collection); + assert.equal(Collection, manager.get(Collection)); + }); }); }); - describe('with a Model', function() { - it('should return a Model', function() { - var Model = Test.Model.extend({}); + describe('if root was specified', function() { + + before(function() { + bookshelf = Bootstrap.database(); + manager = Bootstrap.manager(bookshelf); + }); - assert.equal(Model, manager.get(Model)); + it('should throw an error if file not found', function() { + assert.throws(function() { manager.get('fake'); }, /Could not find module/); }); - it('should return a Collection', function() { - var Collection = Test.Collection.extend(); + describe('with a name', function() { + it('should return a Model', function() { + assert.ok(manager.get('make').prototype instanceof bookshelf.Model); + }); + + it('should return a Collection', function() { + assert.ok(manager.get('makes').prototype instanceof bookshelf.Collection); + }); - assert.equal(Collection, manager.get(Collection)); + it('should register the model in the registry', function() { + assert.ok(bookshelf.model('make').prototype instanceof bookshelf.Model); + }); }); - }); - describe('with a managed Model', function() { - it('should return a Model for Bookshelf instance', function() { - assert.ok(manager.get(ManagedModel).prototype instanceof Test.Model); + describe('with a Model', function() { + it('should return a Model', function() { + var Model = bookshelf.Model.extend({}); + + assert.equal(Model, manager.get(Model)); + }); + + it('should return a Collection', function() { + var Collection = bookshelf.Collection.extend(); + + assert.equal(Collection, manager.get(Collection)); + }); }); }); }); diff --git a/test/manager.js b/test/manager.js index 76d7164..6e38672 100644 --- a/test/manager.js +++ b/test/manager.js @@ -1,55 +1,32 @@ var assert = require('assert'); -var Bookshelf = require('bookshelf'); -var path = require('path'); +var fs = require('fs'); +var Bootstrap = require('./support/bootstrap'); var Manager = require('../lib/manager'); -var Test = require('./databases/test'); describe('Manager', function() { - describe('when instantiated', function() { - it('should require path as first argument', function() { + describe('constructor', function() { + it('should require a Bookshelf instance as the first argument', function() { assert.throws(function() { - new Manager(); - }, 'Manager requires a path to model directory'); - }); - - it('should accept a Bookshelf instance as second argument', function() { - var manager = new Manager(path.join(__dirname, 'models'), Test); - - assert.equal(Test, manager.bookshelf); - }); - - it('should initialize after Bookshelf only once', function() { - var manager = new Manager(path.join(__dirname, 'models')); - - assert.ok(manager.root); - assert.ok(!manager.bookshelf); - assert.ok(!manager.knex); - assert.ok(!manager.schema); + return new Manager(); + }, /Manager requires a Bookshelf instance/); - var first = Bookshelf.initialize({ - client: 'mysql', - connection: { user: 'root' } - }); - - assert.ok(manager.bookshelf); - assert.ok(manager.knex); - assert.ok(manager.schema); + assert.throws(function() { + return new Manager({ invalid: 'object' }); + }, /Manager requires a Bookshelf instance/); - var second = Bookshelf.initialize({ - client: 'mysql', - connection: { user: 'root' } + assert.doesNotThrow(function() { + return new Manager(Bootstrap.database()); }); - - assert.equal(first, manager.bookshelf); }); - }); - describe('.initialize', function() { - it('should be fluent', function() { - var manager = new Manager(path.join(__dirname, 'models')); + describe('if second options argument is specified', function() { - assert.equal(manager, manager.initialize(Test)); + it('should set the root model directory on the manager instance if provided on the options argument', function() { + var manager = Bootstrap.manager(Bootstrap.database()); + assert(manager.root); + assert(fs.lstatSync(manager.root).isDirectory()); + }); }); }); }); diff --git a/test/manager.manage.js b/test/manager.manage.js deleted file mode 100644 index 95b1e7f..0000000 --- a/test/manager.manage.js +++ /dev/null @@ -1,23 +0,0 @@ -var assert = require('assert'); - -var Manager = require('../lib/manager'); - -var ManagedModel = Manager.manage(function(Bookshelf) { - return Bookshelf.Model.extend({ - table: 'fake' - }); -}); - -describe('Manager', function() { - describe('.manage', function() { - it('should return function', function() { - assert.equal('function', typeof Manager.manage(ManagedModel)); - }); - }); - - describe ('.manages', function() { - it('should be managing a model', function() { - assert.ok(Manager.manages(ManagedModel)); - }); - }); -}); diff --git a/test/manager.plugin.js b/test/manager.plugin.js new file mode 100644 index 0000000..2111814 --- /dev/null +++ b/test/manager.plugin.js @@ -0,0 +1,15 @@ +var assert = require('assert'); + +var Bootstrap = require('./support/bootstrap'); +var Manager = require('../lib/manager'); + +describe('Manager', function() { + describe('.plugin', function() { + it('should instantiate and return a new Manager instance', function() { + var bookshelf = Bootstrap.database(); + bookshelf.plugin(Manager.plugin); + var manager = bookshelf.manager; + assert.equal(manager.bookshelf, bookshelf); + }); + }); +}); diff --git a/test/manager.register.js b/test/manager.register.js new file mode 100644 index 0000000..d8bbc6a --- /dev/null +++ b/test/manager.register.js @@ -0,0 +1,52 @@ +var assert = require('assert'); +var Bootstrap = require('./support/bootstrap'); +var Manager = require('../lib/manager'); + +describe('manager', function() { + + var manager; + var Model; + + beforeEach(function() { + manager = new Manager(Bootstrap.database()); + + Model = function(Bookshelf) { + return Bookshelf.Model.extend({ + table: 'fake' + }); + }; + }); + + describe('.register', function() { + it('should return function', function() { + assert.equal('function', typeof manager.register(Model, 'fake')); + }); + + it('must be called with both a Model and a model name', function() { + assert.throws(function() { + return manager.register(); + }, /Manager.register must be called with a model and a model name/); + + assert.throws(function() { + return manager.register(Model); + }, /Manager.register must be called with a model and a model name/); + + assert.throws(function() { + return manager.register('fake'); + }, /Manager.register must be called with a model and a model name/); + }); + + it('should register the model in the Bookshelf registry', function() { + manager.register(Model, 'fake'); + assert.ok(manager.bookshelf.model('fake').prototype instanceof manager.bookshelf.Model); + }); + + it('should not allow re-registering the same model name', function() { + manager.register(Model, 'fake'); + assert.throws(function() { + return manager.register(Model, 'fake'); + }, /fake is already defined in the registry/); + }); + + }); +}); diff --git a/test/manager.save.js b/test/manager.save.js index 7458149..981f4c1 100644 --- a/test/manager.save.js +++ b/test/manager.save.js @@ -1,19 +1,18 @@ var assert = require('assert'); -var Promise = require('bluebird'); var deep = require('deep-diff'); var Bootstrap = require('./support/bootstrap'); -var manager = require('./support/manager'); describe('manager', function() { describe('.save', function() { - before(function(done) { - Bootstrap.database().then(function() { - return Bootstrap.tables(); - }).then(function() { - return Bootstrap.fixtures(); - }).then(function() { - done(); + var manager; + + beforeEach(function() { + manager = Bootstrap.manager(Bootstrap.database()); + Bootstrap.models(manager); + return Bootstrap.tables(manager) + .then(function() { + return Bootstrap.fixtures(manager); }); }); @@ -24,25 +23,24 @@ describe('manager', function() { assert.ok(promise.then instanceof Function, 'Expected Function. `then` is ' + typeof promise.then); }); - it('should save a new model', function(done) { + it('should save a new model', function() { var car = manager.forge('car'); - manager.save(car).then(function(car) { - assert.equal(3, car.id, 'Car should have an ID of 3, not ' + car.id); - done(); + return manager.save(car).then(function(car) { + assert.equal(2, car.id, 'Car should have an ID of 2, not ' + car.id); }); }); - it('should save an existing model with same ID', function(done) { + it('should save an existing model with same ID', function() { var Make = manager.get('make'); var original = new Make({ - name: 'Ford', + name: 'Ford' }); - manager.save(original).then(function() { + return manager.save(original).then(function() { return manager.save(new Make(), { id: original.id, - name: 'Chevy', + name: 'Chevy' }); }).then(function(make) { assert.equal(original.id, make.id, 'Should have overriden original model ID'); @@ -50,47 +48,44 @@ describe('manager', function() { return manager.fetch('makes'); }).then(function(makes) { assert.equal(2, makes.length, 'Should only have 2 makes, not ' + makes.length); - done(); }); }); - it('should modify the model', function(done) { - manager.fetch('car', { id: 1 }).then(function(car) { + 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'); return manager.save(car, { - quantity: 2, + quantity: 2 }); }).then(function(car) { assert.equal(2, car.get('quantity'), 'Car #1 should end with quantity of 2'); - done(); }); }); - it('should modify a nested model', function(done) { - manager.fetch('car', { id: 1 }, 'color').then(function(car) { + 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')); return manager.save(car, { color: { id: 1, - name: 'Dark Grey', + name: 'Dark Grey' } }); }).then(function(car) { return car.fetch({ - withRelated: 'color', + withRelated: 'color' }); }).then(function(car) { assert.equal(1, car.related('color').id); assert.equal('Dark Grey', car.related('color').get('name')); - done(); }); }); - it('should modify a deep nested model', function(done) { - manager.fetch('car', { id: 1 }, 'model.type').then(function(car) { + 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')); return manager.save(car, { @@ -104,16 +99,15 @@ describe('manager', function() { }); }).then(function(car) { return car.fetch({ - withRelated: 'model.type', + withRelated: 'model.type' }); }).then(function(car) { assert.equal('SUV', car.related('model').related('type').get('name')); - done(); }); }); - it('should ignore _pivot_ keys', function(done) { - manager.fetch('car', { id: 1 }, 'features').then(function(car) { + it('should ignore _pivot_ keys', function() { + return manager.fetch('car', { id: 1 }, 'features').then(function(car) { var feature = car.related('features').at(0); var json = feature.toJSON(); @@ -122,12 +116,11 @@ describe('manager', function() { return manager.save(feature, json); }).then(function(feature) { assert.equal('GPSv2', feature.get('name')); - done(); }); }); - it('should orphan models in collection', function(done) { - manager.fetch('car', { id: 1 }, 'features').then(function(car) { + 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'); return manager.save(car, { @@ -135,15 +128,14 @@ describe('manager', function() { features: [] }).then(function(car) { assert.equal(0, car.related('features').length, 'Car should have all features removed, found: ' + car.related('features').toJSON()); - done(); }); }); }); - it('should support original fetched response', function(done) { + it('should support original fetched response', function() { var expected; - manager + return manager .fetch('make', { name: 'BMW' }, [ 'models', 'models.specs', @@ -153,7 +145,7 @@ describe('manager', function() { 'dealers.cars.color', 'dealers.cars.model', 'dealers.cars.features', - 'dealers.cars.model.type', + 'dealers.cars.model.type' ]).then(function(make) { expected = make.toJSON(); @@ -166,9 +158,46 @@ describe('manager', function() { return manager.knex('models_specs').select(); }).then(function(results) { assert.equal(2, results.length, 'Expected only 2 rows in `models_specs`, not ' + results.length); - done(); - }) - ; - }) + }); + }); + + it('should support transactions', function() { + return manager.bookshelf.transaction(function(t) { + return manager.fetch('car', { id: 1 }, 'features', { transacting: t }).then(function(car) { + return manager.save(car, { + id: 1, + quantity: 2, + features: [] + }, { + 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()); + 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'); + }); + } else { + throw err; + } + }); + }); + + it('should set belongsTo foreign keys to null if a related attribute is present but set to null', function() { + + return manager.fetch('model', { id: 1 }).then(function(model) { + return manager.save(model, { + name: 'X5', + type: null + }).then(function(model) { + assert.equal(null, model.get('type_id')); + }); + }); + }); }); }); diff --git a/test/models/car.js b/test/models/car.js index 67b9efe..a2dde46 100644 --- a/test/models/car.js +++ b/test/models/car.js @@ -1,11 +1,9 @@ -var Manager = require('../../lib/manager'); - -var Car = Manager.manage(function(Bookshelf) { +var Car = function(Bookshelf) { return Bookshelf.Model.extend({ tableName: 'cars', defaults: { - quantity: 0, + quantity: 0 }, color: function() { @@ -22,8 +20,8 @@ var Car = Manager.manage(function(Bookshelf) { features: function() { return this.belongsToMany('feature'); - }, + } }); -}); +}; module.exports = Car; diff --git a/test/models/cars.js b/test/models/cars.js index 2834eec..22f4bf4 100644 --- a/test/models/cars.js +++ b/test/models/cars.js @@ -1,9 +1,7 @@ -var Manager = require('../../lib/manager'); - -var Cars = Manager.manage(function(Bookshelf) { +var Cars = function(Bookshelf) { return Bookshelf.Collection.extend({ - model: 'car', + model: 'car' }); -}); +}; module.exports = Cars; diff --git a/test/models/color.js b/test/models/color.js index 691d8bf..989ec23 100644 --- a/test/models/color.js +++ b/test/models/color.js @@ -1,9 +1,7 @@ -var Manager = require('../../lib/manager'); - -var Color = Manager.manage(function(Bookshelf) { +var Color = function(Bookshelf) { return Bookshelf.Model.extend({ - tableName: 'colors', + tableName: 'colors' }); -}); +}; module.exports = Color; diff --git a/test/models/dealer.js b/test/models/dealer.js index b4de160..e19eac9 100644 --- a/test/models/dealer.js +++ b/test/models/dealer.js @@ -1,6 +1,4 @@ -var Manager = require('../../lib/manager'); - -var Dealer = Manager.manage(function(Bookshelf) { +var Dealer = function(Bookshelf) { return Bookshelf.Model.extend({ tableName: 'dealers', @@ -12,6 +10,6 @@ var Dealer = Manager.manage(function(Bookshelf) { return this.hasMany('car', 'dealer_id'); } }); -}); +}; module.exports = Dealer; diff --git a/test/models/feature.js b/test/models/feature.js index b757df2..533cd63 100644 --- a/test/models/feature.js +++ b/test/models/feature.js @@ -1,6 +1,4 @@ -var Manager = require('../../lib/manager'); - -var Feature = Manager.manage(function(Bookshelf) { +var Feature = function(Bookshelf) { return Bookshelf.Model.extend({ tableName: 'features', @@ -8,6 +6,6 @@ var Feature = Manager.manage(function(Bookshelf) { return this.belongsToMany('car'); } }); -}); +}; module.exports = Feature; diff --git a/test/models/features.js b/test/models/features.js index 8f56ac0..e3d37d8 100644 --- a/test/models/features.js +++ b/test/models/features.js @@ -1,9 +1,7 @@ -var Manager = require('../../lib/manager'); - -var Features = Manager.manage(function(Bookshelf) { +var Features = function(Bookshelf) { return Bookshelf.Collection.extend({ - model: 'feature', + model: 'feature' }); -}); +}; module.exports = Features; diff --git a/test/models/make.js b/test/models/make.js index faa152f..993914d 100644 --- a/test/models/make.js +++ b/test/models/make.js @@ -1,6 +1,4 @@ -var Manager = require('../../lib/manager'); - -var Make = Manager.manage(function(Bookshelf) { +var Make = function(Bookshelf) { return Bookshelf.Model.extend({ tableName: 'makes', @@ -10,8 +8,8 @@ var Make = Manager.manage(function(Bookshelf) { dealers: function() { return this.hasMany('dealer', 'make_id'); - }, + } }); -}); +}; module.exports = Make; diff --git a/test/models/makes.js b/test/models/makes.js index 5cfca7c..7f74f11 100644 --- a/test/models/makes.js +++ b/test/models/makes.js @@ -1,9 +1,7 @@ -var Manager = require('../../lib/manager'); - -var Makes = Manager.manage(function(Bookshelf) { +var Makes = function(Bookshelf) { return Bookshelf.Collection.extend({ model: 'make' }); -}); +}; module.exports = Makes; diff --git a/test/models/model.js b/test/models/model.js index 23d1ec5..675a837 100644 --- a/test/models/model.js +++ b/test/models/model.js @@ -1,6 +1,4 @@ -var Manager = require('../../lib/manager'); - -var Model = Manager.manage(function(Bookshelf) { +var Model = function(Bookshelf) { return Bookshelf.Model.extend({ tableName: 'models', @@ -16,6 +14,6 @@ var Model = Manager.manage(function(Bookshelf) { return this.belongsTo('type'); } }); -}); +}; module.exports = Model; diff --git a/test/models/models.js b/test/models/models.js index f78f818..473e8ab 100644 --- a/test/models/models.js +++ b/test/models/models.js @@ -1,9 +1,7 @@ -var Manager = require('../../lib/manager'); - -var Test = Manager.manage(function(Bookshelf) { +var Models = function(Bookshelf) { return Bookshelf.Collection.extend({ model: 'model' }); -}); +}; module.exports = Models; diff --git a/test/models/spec.js b/test/models/spec.js index 4a3c50f..16094df 100644 --- a/test/models/spec.js +++ b/test/models/spec.js @@ -1,6 +1,4 @@ -var Manager = require('../../lib/manager'); - -var Spec = Manager.manage(function(Bookshelf) { +var Spec = function(Bookshelf) { return Bookshelf.Model.extend({ tableName: 'specs', @@ -8,6 +6,6 @@ var Spec = Manager.manage(function(Bookshelf) { return this.belongsToMany('model'); } }); -}); +}; module.exports = Spec; diff --git a/test/models/specs.js b/test/models/specs.js index 1f640d8..33b1a19 100644 --- a/test/models/specs.js +++ b/test/models/specs.js @@ -1,9 +1,7 @@ -var Manager = require('../../lib/manager'); - -var Specs = Manager.manage(function(Bookshelf) { +var Specs = function(Bookshelf) { return Bookshelf.Collection.extend({ model: 'spec' }); -}); +}; module.exports = Specs; diff --git a/test/models/type.js b/test/models/type.js index 1e2dff9..f137c09 100644 --- a/test/models/type.js +++ b/test/models/type.js @@ -1,6 +1,4 @@ -var Manager = require('../../lib/manager'); - -var Type = Manager.manage(function(Bookshelf) { +var Type = function(Bookshelf) { return Bookshelf.Model.extend({ tableName: 'types', @@ -8,6 +6,6 @@ var Type = Manager.manage(function(Bookshelf) { return this.belongsTo('model'); } }); -}); +}; module.exports = Type; diff --git a/test/support/bootstrap.database.js b/test/support/bootstrap.database.js index 1e8a209..1ff4cb6 100644 --- a/test/support/bootstrap.database.js +++ b/test/support/bootstrap.database.js @@ -1,9 +1,11 @@ -var MySql = require('../databases/mysql'); +var Bookshelf = require('bookshelf'); +var Knex = require('knex'); module.exports = function() { - return MySql.knex.raw('DROP DATABASE IF EXISTS bookshelf_manager_test') - .then(function() { - return MySql.knex.raw('CREATE DATABASE bookshelf_manager_test') - }) - ; + return Bookshelf(Knex({ + client: 'sqlite3', + connection: { + filename: ':memory:' + } + })); }; diff --git a/test/support/bootstrap.fixtures.js b/test/support/bootstrap.fixtures.js index 0f653b9..ceeb7b7 100644 --- a/test/support/bootstrap.fixtures.js +++ b/test/support/bootstrap.fixtures.js @@ -1,11 +1,11 @@ var Promise = require('bluebird'); -var manager = require('./manager'); -module.exports = function() { +module.exports = function(manager) { + var make = manager.forge('make', { name: 'BMW' }); var model = manager.forge('model', { name: 'X5', cost: '50000.00' }); var type = manager.forge('type', { name: 'Crossover' }); - var specs = manager.forge('specs', [ { name: '4 door'}, { name: 'v6' } ]); + 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 features = manager.forge('features', [ { name: 'GPS', cost: '500' }, { name: 'ABS', cost: '1250' }]); diff --git a/test/support/bootstrap.js b/test/support/bootstrap.js index 44b3a8c..5104a86 100644 --- a/test/support/bootstrap.js +++ b/test/support/bootstrap.js @@ -1,9 +1,9 @@ -var Promise = require('bluebird'); - var Bootstrap = { database: require('./bootstrap.database'), + manager: require('./bootstrap.manager'), + models: require('./bootstrap.models'), fixtures: require('./bootstrap.fixtures'), - tables: require('./bootstrap.tables'), + tables: require('./bootstrap.tables') }; module.exports = Bootstrap; diff --git a/test/support/bootstrap.manager.js b/test/support/bootstrap.manager.js new file mode 100644 index 0000000..bb22b6c --- /dev/null +++ b/test/support/bootstrap.manager.js @@ -0,0 +1,8 @@ +var path = require('path'); +var Manager = require('../../lib/manager'); + +module.exports = function(bookshelf) { + return new Manager(bookshelf, { + root: path.join(__dirname, '..', 'models') + }); +}; diff --git a/test/support/bootstrap.models.js b/test/support/bootstrap.models.js new file mode 100644 index 0000000..8834da9 --- /dev/null +++ b/test/support/bootstrap.models.js @@ -0,0 +1,13 @@ +var path = require('path'); +var fs = require('fs'); + +module.exports = function(manager) { + var sourceFolder = path.join(process.cwd(), '/test/models'); + var files = fs.readdirSync(sourceFolder); + + files.forEach(function(file) { + var model = require(path.join(sourceFolder, file)); + var modelId = path.basename(file, path.extname(file)); + manager.register(model, modelId); + }); +}; diff --git a/test/support/bootstrap.tables.js b/test/support/bootstrap.tables.js index b57c9c6..3b3c5ec 100644 --- a/test/support/bootstrap.tables.js +++ b/test/support/bootstrap.tables.js @@ -1,68 +1,76 @@ -var Promise = require('bluebird'); -var manager = require('./manager'); +module.exports = function(manager) { -module.exports = function() { - return Promise.all([ - manager.schema.createTable('cars', function(table) { + return manager.knex.transaction(function(trx) { + var schema = trx.schema; + return schema.createTableIfNotExists('cars', function(table) { table.increments('id'); table.integer('color_id'); table.integer('dealer_id'); table.integer('model_id'); table.integer('quantity'); - }), - - manager.schema.createTable('cars_features', function(table) { - table.increments('id'); - table.integer('car_id'); - table.integer('feature_id'); - }), - - manager.schema.createTable('colors', function(table) { - table.increments('id'); - table.string('name'); - table.string('hex_value'); - }), - - manager.schema.createTable('dealers', function(table) { - table.increments('id'); - table.integer('make_id'); - table.string('name'); - table.string('zip_code'); - }), - - manager.schema.createTable('features', function(table) { - table.increments('id'); - table.string('name'); - table.decimal('cost'); - }), - - manager.schema.createTable('makes', function(table) { - table.increments('id'); - table.string('name'); - }), - - manager.schema.createTable('models', function(table) { - table.increments('id'); - table.integer('make_id'); - table.integer('type_id'); - table.string('name'); - table.decimal('cost'); - }), - - manager.schema.createTable('models_specs', function(table) { - table.increments('id'); - table.integer('model_id'); - table.integer('spec_id'); - }), - - manager.schema.createTable('specs', function(table) { - table.increments('id'); - table.string('name'); - }), - - manager.schema.createTable('types', function(table) { - table.increments('id'); - table.string('name'); - }), - ]); + }) + .then(function() { + return schema.createTableIfNotExists('cars_features', function(table) { + table.increments('id'); + table.integer('car_id'); + table.integer('feature_id'); + }); + }) + .then(function() { + return schema.createTableIfNotExists('colors', function(table) { + table.increments('id'); + table.string('name'); + table.string('hex_value'); + }); + }) + .then(function() { + return schema.createTableIfNotExists('dealers', function(table) { + table.increments('id'); + table.integer('make_id'); + table.string('name'); + table.string('zip_code'); + }); + }) + .then(function() { + return schema.createTableIfNotExists('features', function(table) { + table.increments('id'); + table.string('name'); + table.decimal('cost'); + }); + }) + .then(function() { + return schema.createTableIfNotExists('makes', function(table) { + table.increments('id'); + table.string('name'); + }); + }) + .then(function() { + return schema.createTableIfNotExists('models', function(table) { + table.increments('id'); + table.integer('make_id'); + table.integer('type_id'); + table.string('name'); + table.decimal('cost'); + }); + }) + .then(function() { + return schema.createTableIfNotExists('models_specs', function(table) { + table.increments('id'); + table.integer('model_id'); + table.integer('spec_id'); + }); + }) + .then(function() { + return schema.createTableIfNotExists('specs', function(table) { + table.increments('id'); + table.string('name'); + }); + }) + .then(function() { + return schema.createTableIfNotExists('types', function(table) { + table.increments('id'); + table.string('name'); + }); + }); + }); }; diff --git a/test/support/manager.js b/test/support/manager.js deleted file mode 100644 index a707cea..0000000 --- a/test/support/manager.js +++ /dev/null @@ -1,6 +0,0 @@ -var path = require('path'); - -var Manager = require('../../lib/manager'); -var Test = require('../databases/test'); - -module.exports = new Manager(path.join(__dirname, '..', 'models'), Test);