Skip to content

Commit

Permalink
Version 0.3.0
Browse files Browse the repository at this point in the history
  - 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
  • Loading branch information
blah238 committed Sep 21, 2016
1 parent f05ca99 commit 7679459
Show file tree
Hide file tree
Showing 13 changed files with 195 additions and 117 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
80 changes: 37 additions & 43 deletions lib/manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,24 +46,24 @@ 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();

return this.save(model, properties, options).catch(function(error) {
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 = [];
Expand Down Expand Up @@ -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);
}
Expand All @@ -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() {
Expand All @@ -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);
}
Expand All @@ -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 : {};
Expand Down Expand Up @@ -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();
Expand All @@ -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) {
Expand All @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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;
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand All @@ -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"
}
}
70 changes: 43 additions & 27 deletions test/manager.create.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});

Expand All @@ -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');
});
});

Expand All @@ -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');
});
});

Expand All @@ -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');
});
});
});
Expand All @@ -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');
});
});

Expand All @@ -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'));
});
});

Expand Down Expand Up @@ -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') + '`');
});
});

Expand All @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 7679459

Please sign in to comment.