mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-25 03:44:29 +03:00
Revert "Refactored Bookshelf CRUD functions into plugin"
this reverts the following until tests have been fixed: -e51d505abb
-c86ac27dcf
-3ffba967f2
This commit is contained in:
parent
caea330647
commit
f91daffdad
@ -1,220 +0,0 @@
|
||||
const _ = require('lodash');
|
||||
const errors = require('@tryghost/errors');
|
||||
|
||||
/**
|
||||
* @param {Bookshelf} Bookshelf
|
||||
*/
|
||||
module.exports = function (Bookshelf) {
|
||||
Bookshelf.Model = Bookshelf.Model.extend({}, {
|
||||
/**
|
||||
* ### Find All
|
||||
* Fetches all the data for a particular model
|
||||
* @param {Object} unfilteredOptions (optional)
|
||||
* @return {Promise<Bookshelf['Collection']>} Collection of all Models
|
||||
*/
|
||||
findAll: async function findAll(unfilteredOptions) {
|
||||
const options = this.filterOptions(unfilteredOptions, 'findAll');
|
||||
const itemCollection = this.getFilteredCollection(options);
|
||||
|
||||
// @TODO: we can't use order raw when running migrations (see https://github.com/tgriesser/knex/issues/2763)
|
||||
if (this.orderDefaultRaw && !options.migrating) {
|
||||
itemCollection.query((qb) => {
|
||||
qb.orderByRaw(this.orderDefaultRaw(options));
|
||||
});
|
||||
}
|
||||
|
||||
const result = await itemCollection.fetchAll(options);
|
||||
if (options.withRelated) {
|
||||
_.each(result.models, function each(item) {
|
||||
item.withRelated = options.withRelated;
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* ### Find Page
|
||||
* Find results by page - returns an object containing the
|
||||
* information about the request (page, limit), along with the
|
||||
* info needed for pagination (pages, total).
|
||||
*
|
||||
* **response:**
|
||||
*
|
||||
* {
|
||||
* data: [
|
||||
* {...}, ...
|
||||
* ],
|
||||
* meta: {
|
||||
* pagination: {
|
||||
* page: __,
|
||||
* limit: __,
|
||||
* pages: __,
|
||||
* total: __
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @param {Object} unfilteredOptions
|
||||
*/
|
||||
findPage: async function findPage(unfilteredOptions) {
|
||||
const options = this.filterOptions(unfilteredOptions, 'findPage');
|
||||
const itemCollection = this.getFilteredCollection(options);
|
||||
const requestedColumns = options.columns;
|
||||
|
||||
// Set this to true or pass ?debug=true as an API option to get output
|
||||
itemCollection.debug = unfilteredOptions.debug && process.env.NODE_ENV !== 'production';
|
||||
|
||||
// Ensure only valid fields/columns are added to query
|
||||
// and append default columns to fetch
|
||||
if (options.columns) {
|
||||
options.columns = _.intersection(options.columns, this.prototype.permittedAttributes());
|
||||
options.columns = _.union(options.columns, this.prototype.defaultColumnsToFetch());
|
||||
}
|
||||
|
||||
if (options.order) {
|
||||
const {order, orderRaw, eagerLoad} = itemCollection.parseOrderOption(options.order, options.withRelated);
|
||||
options.orderRaw = orderRaw;
|
||||
options.order = order;
|
||||
options.eagerLoad = eagerLoad;
|
||||
} else if (options.autoOrder) {
|
||||
options.orderRaw = options.autoOrder;
|
||||
} else if (this.orderDefaultRaw) {
|
||||
options.orderRaw = this.orderDefaultRaw(options);
|
||||
} else if (this.orderDefaultOptions) {
|
||||
options.order = this.orderDefaultOptions();
|
||||
}
|
||||
|
||||
const response = await itemCollection.fetchPage(options);
|
||||
// Attributes are being filtered here, so they are not leaked into calling layer
|
||||
// where models are serialized to json and do not do more filtering.
|
||||
// Re-add and pick any computed properties that were stripped before fetchPage call.
|
||||
const data = response.collection.models.map((model) => {
|
||||
if (requestedColumns) {
|
||||
model.attributes = _.pick(model.attributes, requestedColumns);
|
||||
model._previousAttributes = _.pick(model._previousAttributes, requestedColumns);
|
||||
}
|
||||
|
||||
return model;
|
||||
});
|
||||
|
||||
return {
|
||||
data: data,
|
||||
meta: {pagination: response.pagination}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* ### Find One
|
||||
* Naive find one where data determines what to match on
|
||||
* @param {Object} data
|
||||
* @param {Object} unfilteredOptions (optional)
|
||||
* @return {Promise<Bookshelf['Model']>} Single Model
|
||||
*/
|
||||
findOne: function findOne(data, unfilteredOptions) {
|
||||
const options = this.filterOptions(unfilteredOptions, 'findOne');
|
||||
data = this.filterData(data);
|
||||
const model = this.forge(data);
|
||||
|
||||
// @NOTE: The API layer decides if this option is allowed
|
||||
if (options.filter) {
|
||||
model.applyDefaultAndCustomFilters(options);
|
||||
}
|
||||
|
||||
// Ensure only valid fields/columns are added to query
|
||||
if (options.columns) {
|
||||
options.columns = _.intersection(options.columns, this.prototype.permittedAttributes());
|
||||
}
|
||||
|
||||
return model.fetch(options);
|
||||
},
|
||||
|
||||
/**
|
||||
* ### Edit
|
||||
* Naive edit
|
||||
*
|
||||
* We always forward the `method` option to Bookshelf, see http://bookshelfjs.org/#Model-instance-save.
|
||||
* Based on the `method` option Bookshelf and Ghost can determine if a query is an insert or an update.
|
||||
*
|
||||
* @param {Object} data
|
||||
* @param {Object} unfilteredOptions (optional)
|
||||
* @return {Promise<Bookshelf['Model']>} Edited Model
|
||||
*/
|
||||
edit: async function edit(data, unfilteredOptions) {
|
||||
const options = this.filterOptions(unfilteredOptions, 'edit');
|
||||
const id = options.id;
|
||||
const model = this.forge({id: id});
|
||||
|
||||
data = this.filterData(data);
|
||||
|
||||
// @NOTE: The API layer decides if this option is allowed
|
||||
if (options.filter) {
|
||||
model.applyDefaultAndCustomFilters(options);
|
||||
}
|
||||
|
||||
// We allow you to disable timestamps when run migration, so that the posts `updated_at` value is the same
|
||||
if (options.importing) {
|
||||
model.hasTimestamps = false;
|
||||
}
|
||||
|
||||
const object = await model.fetch(options);
|
||||
|
||||
if (object) {
|
||||
options.method = 'update';
|
||||
return object.save(data, options);
|
||||
}
|
||||
|
||||
throw new errors.NotFoundError();
|
||||
},
|
||||
|
||||
/**
|
||||
* ### Add
|
||||
* Naive add
|
||||
* @param {Object} data
|
||||
* @param {Object} unfilteredOptions (optional)
|
||||
* @return {Promise<Bookshelf['Model']>} Newly Added Model
|
||||
*/
|
||||
add: function add(data, unfilteredOptions) {
|
||||
const options = this.filterOptions(unfilteredOptions, 'add');
|
||||
let model;
|
||||
|
||||
data = this.filterData(data);
|
||||
model = this.forge(data);
|
||||
|
||||
// We allow you to disable timestamps when importing posts so that the new posts `updated_at` value is the same
|
||||
// as the import json blob. More details refer to https://github.com/TryGhost/Ghost/issues/1696
|
||||
if (options.importing) {
|
||||
model.hasTimestamps = false;
|
||||
}
|
||||
|
||||
// Bookshelf determines whether an operation is an update or an insert based on the id
|
||||
// Ghost auto-generates Object id's, so we need to tell Bookshelf here that we are inserting data
|
||||
options.method = 'insert';
|
||||
return model.save(null, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* ### Destroy
|
||||
* Naive destroy
|
||||
* @param {Object} unfilteredOptions (optional)
|
||||
* @return {Promise<Bookshelf['Model']>} Empty Model
|
||||
*/
|
||||
destroy: async function destroy(unfilteredOptions) {
|
||||
const options = this.filterOptions(unfilteredOptions, 'destroy');
|
||||
|
||||
if (!options.destroyBy) {
|
||||
options.destroyBy = {
|
||||
id: options.id
|
||||
};
|
||||
}
|
||||
|
||||
// Fetch the object before destroying it, so that the changed data is available to events
|
||||
const obj = await this.forge(options.destroyBy).fetch(options);
|
||||
return obj.destroy(options);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {import('bookshelf')} Bookshelf
|
||||
*/
|
@ -13,6 +13,7 @@ const moment = require('moment');
|
||||
const Promise = require('bluebird');
|
||||
const ObjectId = require('bson-objectid');
|
||||
const debug = require('ghost-ignition').debug('models:base');
|
||||
const config = require('../../../shared/config');
|
||||
const db = require('../../data/db');
|
||||
const events = require('../../lib/common/events');
|
||||
const logging = require('@tryghost/logging');
|
||||
@ -68,8 +69,6 @@ ghostBookshelf.plugin(plugins.collision);
|
||||
// Load hasPosts plugin for authors models
|
||||
ghostBookshelf.plugin(plugins.hasPosts);
|
||||
|
||||
ghostBookshelf.plugin(require('./crud'));
|
||||
|
||||
// Manages nested updates (relationships)
|
||||
ghostBookshelf.plugin('bookshelf-relations', {
|
||||
allowedOptions: ['context', 'importing', 'migrating'],
|
||||
@ -909,12 +908,228 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
|
||||
return filteredCollectionQuery;
|
||||
},
|
||||
|
||||
/**
|
||||
* ### Find All
|
||||
* Fetches all the data for a particular model
|
||||
* @param {Object} unfilteredOptions (optional)
|
||||
* @return {Promise<ghostBookshelf.Collection>} Collection of all Models
|
||||
*/
|
||||
findAll: function findAll(unfilteredOptions) {
|
||||
const options = this.filterOptions(unfilteredOptions, 'findAll');
|
||||
const itemCollection = this.getFilteredCollection(options);
|
||||
|
||||
// @TODO: we can't use order raw when running migrations (see https://github.com/tgriesser/knex/issues/2763)
|
||||
if (this.orderDefaultRaw && !options.migrating) {
|
||||
itemCollection.query((qb) => {
|
||||
qb.orderByRaw(this.orderDefaultRaw(options));
|
||||
});
|
||||
}
|
||||
|
||||
return itemCollection.fetchAll(options).then(function then(result) {
|
||||
if (options.withRelated) {
|
||||
_.each(result.models, function each(item) {
|
||||
item.withRelated = options.withRelated;
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* ### Find Page
|
||||
* Find results by page - returns an object containing the
|
||||
* information about the request (page, limit), along with the
|
||||
* info needed for pagination (pages, total).
|
||||
*
|
||||
* **response:**
|
||||
*
|
||||
* {
|
||||
* data: [
|
||||
* {...}, ...
|
||||
* ],
|
||||
* meta: {
|
||||
* pagination: {
|
||||
* page: __,
|
||||
* limit: __,
|
||||
* pages: __,
|
||||
* total: __
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @param {Object} unfilteredOptions
|
||||
*/
|
||||
findPage: function findPage(unfilteredOptions) {
|
||||
const options = this.filterOptions(unfilteredOptions, 'findPage');
|
||||
const itemCollection = this.getFilteredCollection(options);
|
||||
const requestedColumns = options.columns;
|
||||
|
||||
// Set this to true or pass ?debug=true as an API option to get output
|
||||
itemCollection.debug = unfilteredOptions.debug && config.get('env') !== 'production';
|
||||
|
||||
// Ensure only valid fields/columns are added to query
|
||||
// and append default columns to fetch
|
||||
if (options.columns) {
|
||||
options.columns = _.intersection(options.columns, this.prototype.permittedAttributes());
|
||||
options.columns = _.union(options.columns, this.prototype.defaultColumnsToFetch());
|
||||
}
|
||||
|
||||
if (options.order) {
|
||||
const {order, orderRaw, eagerLoad} = itemCollection.parseOrderOption(options.order, options.withRelated);
|
||||
options.orderRaw = orderRaw;
|
||||
options.order = order;
|
||||
options.eagerLoad = eagerLoad;
|
||||
} else if (options.autoOrder) {
|
||||
options.orderRaw = options.autoOrder;
|
||||
} else if (this.orderDefaultRaw) {
|
||||
options.orderRaw = this.orderDefaultRaw(options);
|
||||
} else if (this.orderDefaultOptions) {
|
||||
options.order = this.orderDefaultOptions();
|
||||
}
|
||||
|
||||
return itemCollection.fetchPage(options).then(function formatResponse(response) {
|
||||
// Attributes are being filtered here, so they are not leaked into calling layer
|
||||
// where models are serialized to json and do not do more filtering.
|
||||
// Re-add and pick any computed properties that were stripped before fetchPage call.
|
||||
const data = response.collection.models.map((model) => {
|
||||
if (requestedColumns) {
|
||||
model.attributes = _.pick(model.attributes, requestedColumns);
|
||||
model._previousAttributes = _.pick(model._previousAttributes, requestedColumns);
|
||||
}
|
||||
|
||||
return model;
|
||||
});
|
||||
|
||||
return {
|
||||
data: data,
|
||||
meta: {pagination: response.pagination}
|
||||
};
|
||||
}).catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* ### Find One
|
||||
* Naive find one where data determines what to match on
|
||||
* @param {Object} data
|
||||
* @param {Object} unfilteredOptions (optional)
|
||||
* @return {Promise<ghostBookshelf.Model>} Single Model
|
||||
*/
|
||||
findOne: function findOne(data, unfilteredOptions) {
|
||||
const options = this.filterOptions(unfilteredOptions, 'findOne');
|
||||
data = this.filterData(data);
|
||||
const model = this.forge(data);
|
||||
|
||||
// @NOTE: The API layer decides if this option is allowed
|
||||
if (options.filter) {
|
||||
model.applyDefaultAndCustomFilters(options);
|
||||
}
|
||||
|
||||
// Ensure only valid fields/columns are added to query
|
||||
if (options.columns) {
|
||||
options.columns = _.intersection(options.columns, this.prototype.permittedAttributes());
|
||||
}
|
||||
|
||||
return model.fetch(options);
|
||||
},
|
||||
|
||||
/**
|
||||
* ### Edit
|
||||
* Naive edit
|
||||
*
|
||||
* We always forward the `method` option to Bookshelf, see http://bookshelfjs.org/#Model-instance-save.
|
||||
* Based on the `method` option Bookshelf and Ghost can determine if a query is an insert or an update.
|
||||
*
|
||||
* @param {Object} data
|
||||
* @param {Object} unfilteredOptions (optional)
|
||||
* @return {Promise<ghostBookshelf.Model>} Edited Model
|
||||
*/
|
||||
edit: function edit(data, unfilteredOptions) {
|
||||
const options = this.filterOptions(unfilteredOptions, 'edit');
|
||||
const id = options.id;
|
||||
const model = this.forge({id: id});
|
||||
|
||||
data = this.filterData(data);
|
||||
|
||||
// @NOTE: The API layer decides if this option is allowed
|
||||
if (options.filter) {
|
||||
model.applyDefaultAndCustomFilters(options);
|
||||
}
|
||||
|
||||
// We allow you to disable timestamps when run migration, so that the posts `updated_at` value is the same
|
||||
if (options.importing) {
|
||||
model.hasTimestamps = false;
|
||||
}
|
||||
|
||||
return model
|
||||
.fetch(options)
|
||||
.then((object) => {
|
||||
if (object) {
|
||||
options.method = 'update';
|
||||
return object.save(data, options);
|
||||
}
|
||||
|
||||
throw new errors.NotFoundError();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* ### Add
|
||||
* Naive add
|
||||
* @param {Object} data
|
||||
* @param {Object} unfilteredOptions (optional)
|
||||
* @return {Promise<ghostBookshelf.Model>} Newly Added Model
|
||||
*/
|
||||
add: function add(data, unfilteredOptions) {
|
||||
const options = this.filterOptions(unfilteredOptions, 'add');
|
||||
let model;
|
||||
|
||||
data = this.filterData(data);
|
||||
model = this.forge(data);
|
||||
|
||||
// We allow you to disable timestamps when importing posts so that the new posts `updated_at` value is the same
|
||||
// as the import json blob. More details refer to https://github.com/TryGhost/Ghost/issues/1696
|
||||
if (options.importing) {
|
||||
model.hasTimestamps = false;
|
||||
}
|
||||
|
||||
// Bookshelf determines whether an operation is an update or an insert based on the id
|
||||
// Ghost auto-generates Object id's, so we need to tell Bookshelf here that we are inserting data
|
||||
options.method = 'insert';
|
||||
return model.save(null, options);
|
||||
},
|
||||
|
||||
bulkAdd: function bulkAdd(data, tableName) {
|
||||
tableName = tableName || this.prototype.tableName;
|
||||
|
||||
return bulkOperations.insert(tableName, data);
|
||||
},
|
||||
|
||||
/**
|
||||
* ### Destroy
|
||||
* Naive destroy
|
||||
* @param {Object} unfilteredOptions (optional)
|
||||
* @return {Promise<ghostBookshelf.Model>} Empty Model
|
||||
*/
|
||||
destroy: function destroy(unfilteredOptions) {
|
||||
const options = this.filterOptions(unfilteredOptions, 'destroy');
|
||||
|
||||
if (!options.destroyBy) {
|
||||
options.destroyBy = {
|
||||
id: options.id
|
||||
};
|
||||
}
|
||||
|
||||
// Fetch the object before destroying it, so that the changed data is available to events
|
||||
return this.forge(options.destroyBy)
|
||||
.fetch(options)
|
||||
.then(function then(obj) {
|
||||
return obj.destroy(options);
|
||||
});
|
||||
},
|
||||
|
||||
bulkDestroy: function bulkDestroy(data, tableName) {
|
||||
tableName = tableName || this.prototype.tableName;
|
||||
|
||||
|
@ -1,243 +0,0 @@
|
||||
const errors = require('@tryghost/errors');
|
||||
const should = require('should');
|
||||
const sinon = require('sinon');
|
||||
const models = require('../../../../core/server/models');
|
||||
|
||||
describe('Models: crud', function () {
|
||||
before(function () {
|
||||
models.init();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
describe('destroy', function () {
|
||||
it('forges model using destroyBy, fetches it, and calls destroy, passing filtered options', function () {
|
||||
const unfilteredOptions = {
|
||||
destroyBy: {
|
||||
prop: 'whatever'
|
||||
}
|
||||
};
|
||||
const model = models.Base.Model.forge({});
|
||||
const filterOptionsSpy = sinon.spy(models.Base.Model, 'filterOptions');
|
||||
const forgeStub = sinon.stub(models.Base.Model, 'forge')
|
||||
.returns(model);
|
||||
const fetchStub = sinon.stub(model, 'fetch')
|
||||
.resolves(model);
|
||||
const destroyStub = sinon.stub(model, 'destroy');
|
||||
|
||||
return models.Base.Model.destroy(unfilteredOptions).then(() => {
|
||||
should.equal(filterOptionsSpy.args[0][0], unfilteredOptions);
|
||||
should.equal(filterOptionsSpy.args[0][1], 'destroy');
|
||||
|
||||
should.deepEqual(forgeStub.args[0][0], {
|
||||
prop: 'whatever'
|
||||
});
|
||||
|
||||
const filteredOptions = filterOptionsSpy.returnValues[0];
|
||||
|
||||
should.equal(fetchStub.args[0][0], filteredOptions);
|
||||
should.equal(destroyStub.args[0][0], filteredOptions);
|
||||
});
|
||||
});
|
||||
|
||||
it('uses options.id to forge model, if no destroyBy is provided', function () {
|
||||
const unfilteredOptions = {
|
||||
id: 23
|
||||
};
|
||||
const model = models.Base.Model.forge({});
|
||||
const filterOptionsSpy = sinon.spy(models.Base.Model, 'filterOptions');
|
||||
const forgeStub = sinon.stub(models.Base.Model, 'forge')
|
||||
.returns(model);
|
||||
const fetchStub = sinon.stub(model, 'fetch')
|
||||
.resolves(model);
|
||||
const destroyStub = sinon.stub(model, 'destroy');
|
||||
|
||||
return models.Base.Model.destroy(unfilteredOptions).then(() => {
|
||||
should.equal(filterOptionsSpy.args[0][0], unfilteredOptions);
|
||||
should.equal(filterOptionsSpy.args[0][1], 'destroy');
|
||||
|
||||
should.deepEqual(forgeStub.args[0][0], {
|
||||
id: 23
|
||||
});
|
||||
|
||||
const filteredOptions = filterOptionsSpy.returnValues[0];
|
||||
|
||||
should.equal(fetchStub.args[0][0], filteredOptions);
|
||||
should.equal(destroyStub.args[0][0], filteredOptions);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('findOne', function () {
|
||||
it('forges model using filtered data, fetches it passing filtered options and resolves with the fetched model', function () {
|
||||
const data = {
|
||||
id: 670
|
||||
};
|
||||
const unfilteredOptions = {
|
||||
donny: 'donson'
|
||||
};
|
||||
const model = models.Base.Model.forge({});
|
||||
const fetchedModel = models.Base.Model.forge({});
|
||||
const filterOptionsSpy = sinon.spy(models.Base.Model, 'filterOptions');
|
||||
const filterDataSpy = sinon.spy(models.Base.Model, 'filterData');
|
||||
const forgeStub = sinon.stub(models.Base.Model, 'forge')
|
||||
.returns(model);
|
||||
const fetchStub = sinon.stub(model, 'fetch')
|
||||
.resolves(fetchedModel);
|
||||
|
||||
const findOneReturnValue = models.Base.Model.findOne(data, unfilteredOptions);
|
||||
|
||||
should.equal(findOneReturnValue, fetchStub.returnValues[0]);
|
||||
|
||||
return findOneReturnValue.then((result) => {
|
||||
should.equal(result, fetchedModel);
|
||||
|
||||
should.equal(filterOptionsSpy.args[0][0], unfilteredOptions);
|
||||
should.equal(filterOptionsSpy.args[0][1], 'findOne');
|
||||
|
||||
should.equal(filterDataSpy.args[0][0], data);
|
||||
|
||||
const filteredData = filterDataSpy.returnValues[0];
|
||||
should.deepEqual(forgeStub.args[0][0], filteredData);
|
||||
|
||||
const filteredOptions = filterOptionsSpy.returnValues[0];
|
||||
should.equal(fetchStub.args[0][0], filteredOptions);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('edit', function () {
|
||||
it('resolves with the savedModel after forges model w/ id, fetches w/ filtered options, saves w/ filtered data and options and method=update', function () {
|
||||
const data = {
|
||||
life: 'suffering'
|
||||
};
|
||||
const unfilteredOptions = {
|
||||
id: 'something real special'
|
||||
};
|
||||
const model = models.Base.Model.forge({});
|
||||
const savedModel = models.Base.Model.forge({});
|
||||
const filterOptionsSpy = sinon.spy(models.Base.Model, 'filterOptions');
|
||||
const filterDataSpy = sinon.spy(models.Base.Model, 'filterData');
|
||||
const forgeStub = sinon.stub(models.Base.Model, 'forge')
|
||||
.returns(model);
|
||||
const fetchStub = sinon.stub(model, 'fetch')
|
||||
.resolves(model);
|
||||
const saveStub = sinon.stub(model, 'save')
|
||||
.resolves(savedModel);
|
||||
|
||||
return models.Base.Model.edit(data, unfilteredOptions).then((result) => {
|
||||
should.equal(result, savedModel);
|
||||
|
||||
should.equal(filterOptionsSpy.args[0][0], unfilteredOptions);
|
||||
should.equal(filterOptionsSpy.args[0][1], 'edit');
|
||||
|
||||
should.equal(filterDataSpy.args[0][0], data);
|
||||
|
||||
const filteredOptions = filterOptionsSpy.returnValues[0];
|
||||
should.deepEqual(forgeStub.args[0][0], {id: filteredOptions.id});
|
||||
|
||||
should.equal(fetchStub.args[0][0], filteredOptions);
|
||||
|
||||
const filteredData = filterDataSpy.returnValues[0];
|
||||
should.equal(saveStub.args[0][0], filteredData);
|
||||
should.equal(saveStub.args[0][1].method, 'update');
|
||||
should.deepEqual(saveStub.args[0][1], filteredOptions);
|
||||
});
|
||||
});
|
||||
|
||||
it('sets model.hasTimestamps to false if options.importing is truthy', function () {
|
||||
const data = {
|
||||
base: 'cannon'
|
||||
};
|
||||
const unfilteredOptions = {
|
||||
importing: true
|
||||
};
|
||||
const model = models.Base.Model.forge({});
|
||||
const forgeStub = sinon.stub(models.Base.Model, 'forge')
|
||||
.returns(model);
|
||||
const fetchStub = sinon.stub(model, 'fetch')
|
||||
.resolves();
|
||||
|
||||
return models.Base.Model.findOne(data, unfilteredOptions).then(() => {
|
||||
should.equal(model.hasTimestamps, true);
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error if model cannot be found on edit', function () {
|
||||
const data = {
|
||||
db: 'cooper'
|
||||
};
|
||||
const unfilteredOptions = {
|
||||
id: 'something real special'
|
||||
};
|
||||
const model = models.Base.Model.forge({});
|
||||
const filterOptionsSpy = sinon.spy(models.Base.Model, 'filterOptions');
|
||||
const filterDataSpy = sinon.spy(models.Base.Model, 'filterData');
|
||||
const forgeStub = sinon.stub(models.Base.Model, 'forge')
|
||||
.returns(model);
|
||||
const fetchStub = sinon.stub(model, 'fetch')
|
||||
.resolves();
|
||||
const saveSpy = sinon.stub(model, 'save');
|
||||
|
||||
return models.Base.Model.edit(data, unfilteredOptions).then(() => {
|
||||
throw new Error('That should not happen');
|
||||
}).catch((err) => {
|
||||
(err instanceof errors.NotFoundError).should.be.true();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('add', function () {
|
||||
it('forges model w/ filtered data, saves w/ null and options and method=insert', function () {
|
||||
const data = {
|
||||
rum: 'ham'
|
||||
};
|
||||
const unfilteredOptions = {};
|
||||
const model = models.Base.Model.forge({});
|
||||
const savedModel = models.Base.Model.forge({});
|
||||
const filterOptionsSpy = sinon.spy(models.Base.Model, 'filterOptions');
|
||||
const filterDataSpy = sinon.spy(models.Base.Model, 'filterData');
|
||||
const forgeStub = sinon.stub(models.Base.Model, 'forge')
|
||||
.returns(model);
|
||||
const saveStub = sinon.stub(model, 'save')
|
||||
.resolves(savedModel);
|
||||
|
||||
return models.Base.Model.add(data, unfilteredOptions).then((result) => {
|
||||
should.equal(result, savedModel);
|
||||
|
||||
should.equal(filterOptionsSpy.args[0][0], unfilteredOptions);
|
||||
should.equal(filterOptionsSpy.args[0][1], 'add');
|
||||
|
||||
should.equal(filterDataSpy.args[0][0], data);
|
||||
|
||||
const filteredData = filterDataSpy.returnValues[0];
|
||||
should.deepEqual(forgeStub.args[0][0], filteredData);
|
||||
|
||||
const filteredOptions = filterOptionsSpy.returnValues[0];
|
||||
should.equal(saveStub.args[0][0], null);
|
||||
should.equal(saveStub.args[0][1].method, 'insert');
|
||||
should.deepEqual(saveStub.args[0][1], filteredOptions);
|
||||
});
|
||||
});
|
||||
|
||||
it('sets model.hasTimestamps to false if options.importing is truthy', function () {
|
||||
const data = {
|
||||
newham: 'generals'
|
||||
};
|
||||
const unfilteredOptions = {
|
||||
importing: true
|
||||
};
|
||||
const model = models.Base.Model.forge({});
|
||||
const forgeStub = sinon.stub(models.Base.Model, 'forge')
|
||||
.returns(model);
|
||||
const saveStub = sinon.stub(model, 'save')
|
||||
.resolves();
|
||||
|
||||
return models.Base.Model.add(data, unfilteredOptions).then(() => {
|
||||
should.equal(model.hasTimestamps, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -183,4 +183,233 @@ describe('Models: base', function () {
|
||||
base.get('b').should.eql('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('destroy', function () {
|
||||
it('forges model using destroyBy, fetches it, and calls destroy, passing filtered options', function () {
|
||||
const unfilteredOptions = {
|
||||
destroyBy: {
|
||||
prop: 'whatever'
|
||||
}
|
||||
};
|
||||
const model = models.Base.Model.forge({});
|
||||
const filterOptionsSpy = sinon.spy(models.Base.Model, 'filterOptions');
|
||||
const forgeStub = sinon.stub(models.Base.Model, 'forge')
|
||||
.returns(model);
|
||||
const fetchStub = sinon.stub(model, 'fetch')
|
||||
.resolves(model);
|
||||
const destroyStub = sinon.stub(model, 'destroy');
|
||||
|
||||
return models.Base.Model.destroy(unfilteredOptions).then(() => {
|
||||
should.equal(filterOptionsSpy.args[0][0], unfilteredOptions);
|
||||
should.equal(filterOptionsSpy.args[0][1], 'destroy');
|
||||
|
||||
should.deepEqual(forgeStub.args[0][0], {
|
||||
prop: 'whatever'
|
||||
});
|
||||
|
||||
const filteredOptions = filterOptionsSpy.returnValues[0];
|
||||
|
||||
should.equal(fetchStub.args[0][0], filteredOptions);
|
||||
should.equal(destroyStub.args[0][0], filteredOptions);
|
||||
});
|
||||
});
|
||||
|
||||
it('uses options.id to forge model, if no destroyBy is provided', function () {
|
||||
const unfilteredOptions = {
|
||||
id: 23
|
||||
};
|
||||
const model = models.Base.Model.forge({});
|
||||
const filterOptionsSpy = sinon.spy(models.Base.Model, 'filterOptions');
|
||||
const forgeStub = sinon.stub(models.Base.Model, 'forge')
|
||||
.returns(model);
|
||||
const fetchStub = sinon.stub(model, 'fetch')
|
||||
.resolves(model);
|
||||
const destroyStub = sinon.stub(model, 'destroy');
|
||||
|
||||
return models.Base.Model.destroy(unfilteredOptions).then(() => {
|
||||
should.equal(filterOptionsSpy.args[0][0], unfilteredOptions);
|
||||
should.equal(filterOptionsSpy.args[0][1], 'destroy');
|
||||
|
||||
should.deepEqual(forgeStub.args[0][0], {
|
||||
id: 23
|
||||
});
|
||||
|
||||
const filteredOptions = filterOptionsSpy.returnValues[0];
|
||||
|
||||
should.equal(fetchStub.args[0][0], filteredOptions);
|
||||
should.equal(destroyStub.args[0][0], filteredOptions);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('findOne', function () {
|
||||
it('forges model using filtered data, fetches it passing filtered options and resolves with the fetched model', function () {
|
||||
const data = {
|
||||
id: 670
|
||||
};
|
||||
const unfilteredOptions = {
|
||||
donny: 'donson'
|
||||
};
|
||||
const model = models.Base.Model.forge({});
|
||||
const fetchedModel = models.Base.Model.forge({});
|
||||
const filterOptionsSpy = sinon.spy(models.Base.Model, 'filterOptions');
|
||||
const filterDataSpy = sinon.spy(models.Base.Model, 'filterData');
|
||||
const forgeStub = sinon.stub(models.Base.Model, 'forge')
|
||||
.returns(model);
|
||||
const fetchStub = sinon.stub(model, 'fetch')
|
||||
.resolves(fetchedModel);
|
||||
|
||||
const findOneReturnValue = models.Base.Model.findOne(data, unfilteredOptions);
|
||||
|
||||
should.equal(findOneReturnValue, fetchStub.returnValues[0]);
|
||||
|
||||
return findOneReturnValue.then((result) => {
|
||||
should.equal(result, fetchedModel);
|
||||
|
||||
should.equal(filterOptionsSpy.args[0][0], unfilteredOptions);
|
||||
should.equal(filterOptionsSpy.args[0][1], 'findOne');
|
||||
|
||||
should.equal(filterDataSpy.args[0][0], data);
|
||||
|
||||
const filteredData = filterDataSpy.returnValues[0];
|
||||
should.deepEqual(forgeStub.args[0][0], filteredData);
|
||||
|
||||
const filteredOptions = filterOptionsSpy.returnValues[0];
|
||||
should.equal(fetchStub.args[0][0], filteredOptions);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('edit', function () {
|
||||
it('resolves with the savedModel after forges model w/ id, fetches w/ filtered options, saves w/ filtered data and options and method=update', function () {
|
||||
const data = {
|
||||
life: 'suffering'
|
||||
};
|
||||
const unfilteredOptions = {
|
||||
id: 'something real special'
|
||||
};
|
||||
const model = models.Base.Model.forge({});
|
||||
const savedModel = models.Base.Model.forge({});
|
||||
const filterOptionsSpy = sinon.spy(models.Base.Model, 'filterOptions');
|
||||
const filterDataSpy = sinon.spy(models.Base.Model, 'filterData');
|
||||
const forgeStub = sinon.stub(models.Base.Model, 'forge')
|
||||
.returns(model);
|
||||
const fetchStub = sinon.stub(model, 'fetch')
|
||||
.resolves(model);
|
||||
const saveStub = sinon.stub(model, 'save')
|
||||
.resolves(savedModel);
|
||||
|
||||
return models.Base.Model.edit(data, unfilteredOptions).then((result) => {
|
||||
should.equal(result, savedModel);
|
||||
|
||||
should.equal(filterOptionsSpy.args[0][0], unfilteredOptions);
|
||||
should.equal(filterOptionsSpy.args[0][1], 'edit');
|
||||
|
||||
should.equal(filterDataSpy.args[0][0], data);
|
||||
|
||||
const filteredOptions = filterOptionsSpy.returnValues[0];
|
||||
should.deepEqual(forgeStub.args[0][0], {id: filteredOptions.id});
|
||||
|
||||
should.equal(fetchStub.args[0][0], filteredOptions);
|
||||
|
||||
const filteredData = filterDataSpy.returnValues[0];
|
||||
should.equal(saveStub.args[0][0], filteredData);
|
||||
should.equal(saveStub.args[0][1].method, 'update');
|
||||
should.deepEqual(saveStub.args[0][1], filteredOptions);
|
||||
});
|
||||
});
|
||||
|
||||
it('sets model.hasTimestamps to false if options.importing is truthy', function () {
|
||||
const data = {
|
||||
base: 'cannon'
|
||||
};
|
||||
const unfilteredOptions = {
|
||||
importing: true
|
||||
};
|
||||
const model = models.Base.Model.forge({});
|
||||
const forgeStub = sinon.stub(models.Base.Model, 'forge')
|
||||
.returns(model);
|
||||
const fetchStub = sinon.stub(model, 'fetch')
|
||||
.resolves();
|
||||
|
||||
return models.Base.Model.findOne(data, unfilteredOptions).then(() => {
|
||||
should.equal(model.hasTimestamps, true);
|
||||
});
|
||||
});
|
||||
|
||||
it('throws an error if model cannot be found on edit', function () {
|
||||
const data = {
|
||||
db: 'cooper'
|
||||
};
|
||||
const unfilteredOptions = {
|
||||
id: 'something real special'
|
||||
};
|
||||
const model = models.Base.Model.forge({});
|
||||
const filterOptionsSpy = sinon.spy(models.Base.Model, 'filterOptions');
|
||||
const filterDataSpy = sinon.spy(models.Base.Model, 'filterData');
|
||||
const forgeStub = sinon.stub(models.Base.Model, 'forge')
|
||||
.returns(model);
|
||||
const fetchStub = sinon.stub(model, 'fetch')
|
||||
.resolves();
|
||||
const saveSpy = sinon.stub(model, 'save');
|
||||
|
||||
return models.Base.Model.edit(data, unfilteredOptions).then(() => {
|
||||
throw new Error('That should not happen');
|
||||
}).catch((err) => {
|
||||
(err instanceof errors.NotFoundError).should.be.true();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('add', function () {
|
||||
it('forges model w/ filtered data, saves w/ null and options and method=insert', function () {
|
||||
const data = {
|
||||
rum: 'ham'
|
||||
};
|
||||
const unfilteredOptions = {};
|
||||
const model = models.Base.Model.forge({});
|
||||
const savedModel = models.Base.Model.forge({});
|
||||
const filterOptionsSpy = sinon.spy(models.Base.Model, 'filterOptions');
|
||||
const filterDataSpy = sinon.spy(models.Base.Model, 'filterData');
|
||||
const forgeStub = sinon.stub(models.Base.Model, 'forge')
|
||||
.returns(model);
|
||||
const saveStub = sinon.stub(model, 'save')
|
||||
.resolves(savedModel);
|
||||
|
||||
return models.Base.Model.add(data, unfilteredOptions).then((result) => {
|
||||
should.equal(result, savedModel);
|
||||
|
||||
should.equal(filterOptionsSpy.args[0][0], unfilteredOptions);
|
||||
should.equal(filterOptionsSpy.args[0][1], 'add');
|
||||
|
||||
should.equal(filterDataSpy.args[0][0], data);
|
||||
|
||||
const filteredData = filterDataSpy.returnValues[0];
|
||||
should.deepEqual(forgeStub.args[0][0], filteredData);
|
||||
|
||||
const filteredOptions = filterOptionsSpy.returnValues[0];
|
||||
should.equal(saveStub.args[0][0], null);
|
||||
should.equal(saveStub.args[0][1].method, 'insert');
|
||||
should.deepEqual(saveStub.args[0][1], filteredOptions);
|
||||
});
|
||||
});
|
||||
|
||||
it('sets model.hasTimestamps to false if options.importing is truthy', function () {
|
||||
const data = {
|
||||
newham: 'generals'
|
||||
};
|
||||
const unfilteredOptions = {
|
||||
importing: true
|
||||
};
|
||||
const model = models.Base.Model.forge({});
|
||||
const forgeStub = sinon.stub(models.Base.Model, 'forge')
|
||||
.returns(model);
|
||||
const saveStub = sinon.stub(model, 'save')
|
||||
.resolves();
|
||||
|
||||
return models.Base.Model.add(data, unfilteredOptions).then(() => {
|
||||
should.equal(model.hasTimestamps, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user