Skip to content

Commit

Permalink
Reimplement as a plugin for Bookshelf/Knex 0.8.x
Browse files Browse the repository at this point in the history
  - Changed constructor signature to require Bookshelf instance as first argument, and made `root` an optional property of the second `options` argument
  - Added Manager.plugin() method to be used with Bookshelf.plugin() (exported on main index.js module)
  - Exposes self as `manager` property on Bookshelf instance when instantiated
  - Replaced Manager.manage() and Manager.manages() with manager.register() to improve compatibility with Bookshelf Registry plugin
  - Removed `hooker` dependency and manager.debug() method
  - Use NPM devDependencies and peerDependencies instead of dependencies since it is intended to be used as a plugin now
  - Changed test database from MySQL to SQLite for easier testing setup
  - Updated README.md
  - Bumped version to 0.1.0 (not fully backwards-compatible)
  - Add JSHint/JSCS configs
  • Loading branch information
blah238 committed Jun 26, 2015
1 parent 5e4a52e commit c1e3f26
Show file tree
Hide file tree
Showing 38 changed files with 504 additions and 544 deletions.
7 changes: 7 additions & 0 deletions .jscs.json
@@ -0,0 +1,7 @@
{
"preset": "node-style-guide",
"requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties",
"maximumLineLength": null,
"requireTrailingComma": null,
"disallowTrailingComma": true
}
3 changes: 3 additions & 0 deletions .jshintrc
@@ -0,0 +1,3 @@
{
"node": true
}
41 changes: 41 additions & 0 deletions README.md
Expand Up @@ -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

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion index.js
@@ -1 +1 @@
module.exports = require('./lib/manager');
module.exports = require('./lib/manager').plugin;
170 changes: 49 additions & 121 deletions lib/manager.js
@@ -1,61 +1,51 @@
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;
this.schema = this.knex.schema;

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) {
var Model = this.get(name);
var model = new Model();
Expand All @@ -66,76 +56,11 @@ Manager.prototype.create = function(name, properties) {
});
};

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(' '), '</' + name + '>');

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) {
var model = this.forge(name, properties);

return model.fetch({
withRelated: related,
withRelated: related
});
};

Expand Down Expand Up @@ -188,42 +113,45 @@ 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) {

var file = path.join(this.root, name);

try {
Model = require(file);
} catch (e) {
throw new Error('Could not find module `' + name + '` at `' + file + '.js`');
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') {
Model.prototype.model = this.get(Model.prototype.model);
}

if (name) {
if (this.isModel(Model)) {
this._cache[name] = this.bookshelf.model(name, Model);
} else if (this.isCollection(Model)) {
this._cache[name] = 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;
Expand Down Expand Up @@ -276,7 +204,7 @@ Manager.prototype.saveModel = function(model, properties) {
console.error(error.stack);
throw error;
});
}
};

Manager.prototype.set = function(model, properties) {
if (this.isModel(model)) {
Expand All @@ -291,7 +219,7 @@ Manager.prototype.set = function(model, properties) {
Manager.prototype.setModel = function(model, properties) {
var promises = [];

properties = properties || {};
properties = (typeof properties === 'object' && !Array.isArray(properties) && properties !== null) ? properties : {};

if (model.isNew() && properties && properties.id) {
promises.push(function() {
Expand All @@ -300,12 +228,12 @@ Manager.prototype.setModel = function(model, properties) {
});
}.bind(this));
} else {
promises.push(function () {
promises.push(function() {
return model;
});
}

function setProperties (propertyType) {
function setProperties(propertyType) {

Object.keys(properties).forEach(function(key) {
var value = properties[key];
Expand All @@ -326,7 +254,7 @@ Manager.prototype.setModel = function(model, properties) {

setProperties.bind(this)('scalar');

promises.push(function (result) {
promises.push(function(result) {
return (result.isNew() || result.hasChanged()) ? result.save() : result;
});

Expand Down
19 changes: 11 additions & 8 deletions 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": {
Expand All @@ -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.1",
"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"
}
}
4 changes: 4 additions & 0 deletions test/.jshintrc
@@ -0,0 +1,4 @@
{
"extends": "../.jshintrc",
"mocha": true
}
10 changes: 0 additions & 10 deletions test/databases/mysql.js

This file was deleted.

11 changes: 0 additions & 11 deletions test/databases/test.js

This file was deleted.

0 comments on commit c1e3f26

Please sign in to comment.