Skip to content

Instantly share code, notes, and snippets.

@ssteffl
Created September 1, 2015 15:15
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ssteffl/f58ce60105c365a8d482 to your computer and use it in GitHub Desktop.
Save ssteffl/f58ce60105c365a8d482 to your computer and use it in GitHub Desktop.
#!/usr/bin/env node
//MIXIN MODULE
/**
revisionsLib mixin.
This mixin creates a revisions table for the model that it is mixed into. Each revision has the exact
same fields as the original item + revisionId, revisionValidFrom and revisionValidTo. revisionValidTo
is null for any current items that have not been deleted from the original table yet.
Hooks are added to the original model on creates/updates/deletes in order to create/update the revisions.
NOTE: if you use revisionsLib on a model that has the hierarchyLib mixed in, you need to mix
in the revisionsLib into the closureModel if you want the hierarchy relationships to be kept in their
own revision table (highly recommended to do this).
These are the requirements for any model that uses the revisions_lib mixin:
if there are foreign key constraints, you MUST use the revision_lib mixin for the other model as well
In other words: any model that has a relationship with other models will need to mixin the revisions_lib
if any of the other models mixin the revisions_lib.
This is so we can keep foreign key constraints in the revision history tables.
*/
module.exports = function(sequelize, config){
var _ = require('lodash'),
Sequelize = require('sequelize'),
idLib = require(config.fsConfig.fsDbIdLib)(sequelize, config),
modelNameSuffix = '_revision';
return {
/**
@method makeRevisionsModel
*/
makeRevisionsModel: function(revisionsConfig){
var referenceModel = revisionsConfig.referenceModel,
referenceModelPrimaryKey = revisionsConfig.referenceModelPrimaryKey,
fieldsToIgnore = ['primaryKey','unique','onUpdate','onDelete','references'],
revisionModel;
if((!referenceModel || !referenceModel.name) ||
typeof referenceModelPrimaryKey !== 'string')
throw new Error('invalid makeRevisionsModel input');
function getRevisionModelAttributes(){
return _.merge({
revisionId: idLib.getIdFieldDef(),
revisionValidFrom: {
type: Sequelize.DATE,
defaultValue: null,
validate: [function(value){
if(!(value instanceof Date)) throw new Error('revisionValidFrom is Date');
}]
},
revisionValidTo: {
type: Sequelize.DATE,
defaultValue: null,
validate: [function(value){
if(!(value instanceof Date)) throw new Error('revisionValidTo is Date');
}]
}
},
_.reduce(_.omit(referenceModel.attributes, ['createdAt', 'updatedAt','deletedAt']),
function(map, attributeDef, attributeName){
map[attributeName] = _.omit(attributeDef, fieldsToIgnore);
return map;
}, {})
);
}
function associateFunction(models){
function ensureNoOtherRevisionExistsOnCreate(record, options){
var findOptions = {where:{revisionValidTo:null}, transaction:options.transaction};
findOptions.where[referenceModelPrimaryKey] = record[referenceModelPrimaryKey];
return revisionModel.findOne(findOptions).then(function(revisionRecord){
if(revisionRecord) return Sequelize.Promise.reject('previous revision on create');
});
}
function ensurePreviousRevisionExistsOnUpdateOrDelete(record, options){
var findOptions = {where:{revisionValidTo:null}, transaction:options.transaction};
findOptions.where[referenceModelPrimaryKey] = record[referenceModelPrimaryKey];
return revisionModel.findOne(findOptions).then(function(revisionRecord){
if(!revisionRecord) return Sequelize.Promise.reject('no previous revision on update or delete');
});
}
function addRevisionOnCreateOrUpdate(record, options){
var newFields = _.omit(record.dataValues,['createdAt', 'updatedAt','destroyedAt']);
return revisionModel.create(newFields, {transaction: options.transaction});
}
function setFinalRevisionValidToOnDelete(record, options){
var findOptions = {where:{revisionValidTo:null}, transaction:options.transaction};
findOptions.where[referenceModelPrimaryKey] = record[referenceModelPrimaryKey];
return revisionModel.findOne(findOptions).then(function(finalRevisionRecord){
return finalRevisionRecord.update({revisionValidTo:new Date()}, {transaction:options.transaction});
});
}
//need to insert/update/delete revisions BEFORE we do anything else (closure table revisions will get created before these then)
referenceModel.options.hooks.afterCreate = referenceModel.options.hooks.afterCreate || [];
referenceModel.options.hooks.afterCreate.unshift(addRevisionOnCreateOrUpdate); //runs 2nd
referenceModel.options.hooks.afterCreate.unshift(ensureNoOtherRevisionExistsOnCreate); //runs 1st
referenceModel.options.hooks.afterUpdate = referenceModel.options.hooks.afterUpdate || [];
referenceModel.options.hooks.afterUpdate.unshift(addRevisionOnCreateOrUpdate); //runs 2nd
referenceModel.options.hooks.afterUpdate.unshift(ensurePreviousRevisionExistsOnUpdateOrDelete); //runs 1st
referenceModel.options.hooks.afterDestroy = referenceModel.options.hooks.afterDestroy || [];
referenceModel.options.hooks.afterDestroy.unshift(setFinalRevisionValidToOnDelete); //runs 2nd
referenceModel.options.hooks.afterDestroy.unshift(ensurePreviousRevisionExistsOnUpdateOrDelete); //runs 1st
}
function addHooksToRevisionModel(revisionModel){
function ensureValidFromAndValidToNotSet(record, options){
if(record.revisionValidFrom !== null || record.revisionValidTo !== null)
throw new Error('cannot set revisionValidFrom,revisionValidTo');
}
function setValidToOnPreviousAndValidFromOnCurrent(record, options){
var findOptions = {where:{revisionValidTo:null}, transaction:options.transaction};
findOptions.where[referenceModelPrimaryKey] = record[referenceModelPrimaryKey];
return revisionModel.findOne(findOptions).then(function(previousRecord){
var timestamp = new Date();
record.revisionValidFrom = timestamp;
return previousRecord ? previousRecord.update({revisionValidTo:timestamp}, {transaction:options.transaction}) : undefined;
});
}
function ensureOnlyUpdatingValidToOnce(record){
if(_.contains(_.values(_.omit(record._changed, ['revisionValidTo'])), true)) throw new Error('cannot update revision');
else if(record._previousDataValues.revisionValidTo) throw new Error('revisionValidTo already set');
}
function ensureNotDeleting(record){
throw new Error('cannot delete revision');
}
revisionModel.addHook('beforeCreate', ensureValidFromAndValidToNotSet);
revisionModel.addHook('beforeCreate', setValidToOnPreviousAndValidFromOnCurrent);
revisionModel.addHook('beforeUpdate', ensureOnlyUpdatingValidToOnce);
revisionModel.addHook('beforeDestroy', ensureNotDeleting);
idLib.addIdHooks(revisionModel);
}
revisionModel = sequelize.define(referenceModel.name + modelNameSuffix, getRevisionModelAttributes(), {
timestamps: false,
paranoid: false,
indexes: [
{fields:['revisionValidFrom']},
{fields:['revisionValidTo']},
{fields:[referenceModelPrimaryKey]}
],
classMethods: {
associate: associateFunction
}
});
addHooksToRevisionModel(revisionModel);
return revisionModel;
}
};
};
#!/usr/bin/env node
//EXAMPLE MIXIN USAGE
module.exports = function(sequelize, config){
var Sequelize = require('sequelize'),
hierarchyLib = require(config.fsConfig.fsDbHierarchyLib)(sequelize, config),
revisionsLib = require(config.fsConfig.fsDbRevisionsLib)(sequelize, config),
testModelB = sequelize.define('testModelForRevisionsLibB', {
id: {
type: Sequelize.TEXT,
primaryKey: true,
allowNull: false
},
name: {
type: Sequelize.TEXT,
allowNull: false
},
parentId: {
type: Sequelize.TEXT,
references: {
model:'testModelForRevisionsLibB',
key:'id'
}
},
cModelId: {
type: Sequelize.TEXT,
allowNull: false,
references: {
model:'testModelForRevisionsLibC',
key:'id'
}
}
},{
paranoid: true,
classMethods: {
associate: function(models){
testModelB.belongsTo(testModelB, {as: 'parent',foreignKey:'parentId'});
testModelB.hasMany(testModelB, {as: 'children', foreignKey:'parentId'});
testModelB.belongsTo(models.testModelForRevisionsLibC, {as: 'cModel', foreignKey:'cModelId', onUpdate:'set null', onDelete:'cascade'});
models.testModelForRevisionsLibC.hasOne(testModelB, {as: 'bModel', foreignKey:'cModelId', onUpdate:'set null', onDelete:'cascade' });
}
}
}),
testModelBClosureModel = hierarchyLib.makeClosureModel({
referenceModel: testModelB,
referenceModelKey: 'id',
referenceModelParentKey: 'parentId',
parentCollectionName: 'deepParents',
childCollectionName: 'deepChildren',
closureParentForeignKey: 'parentId',
closureChildForeignKey: 'childId'
}),
testModelBRevisionModel = revisionsLib.makeRevisionsModel({
referenceModel: testModelB,
referenceModelPrimaryKey: 'id'
}),
testModelBClosureRevisionModel = revisionsLib.makeRevisionsModel({
referenceModel: testModelBClosureModel,
referenceModelPrimaryKey: 'id'
});
return [testModelB, testModelBClosureModel, testModelBRevisionModel, testModelBClosureRevisionModel];
};
#!/usr/bin/env node
//MIXIN TESTS
module.exports = function(db, testDir){
require('shelljs/global');
var basename = require('path').basename,
resolve = require('path').resolve,
_ = require('lodash'),
rimraf = require('rimraf'),
Promise = require('bluebird'),
expect = require('chai').expect,
testConfig = require(resolve(__dirname, '../../test_config.js')),
revisionsLibFn = require(testConfig.fsConfig.fsDbRevisionsLib),
revisionsLib,
testModelA,
testModelB,
testModelC,
testRevisionModelB,
testRevisionModelC,
testClosureRevisionModelB,
txn,
getOptions = function(){ return {transaction: txn, individualHooks: true}; },
txnSuccess = function(result){ return txn.commit().then(function(){ return result; }); },
txnFail = function(result){ return txn.rollback().then(function(){ return Promise.reject(result); }); };
return function(){
beforeEach(function(){
if(!test('-d', testDir)) mkdir(testDir);
cp(testConfig.fsConfig.fsDb, testDir);
cp('-R', testConfig.fsConfig.fsTestModelDir + '/*', resolve(testDir, 'model'));
db = require(resolve(testDir, basename(testConfig.fsConfig.fsDb)))(testConfig);
});
beforeEach(function(){
return db.open().then(function(){
revisionsLib = revisionsLibFn(db._sequelize, testConfig);
testModelA = db.get('testModelForRevisionsLibA');
testModelB = db.get('testModelForRevisionsLibB');
testModelC = db.get('testModelForRevisionsLibC');
testRevisionModelB = db.get('testModelForRevisionsLibB_revision');
testRevisionModelC = db.get('testModelForRevisionsLibC_revision');
testClosureRevisionModelB = db.get('testModelForRevisionsLibB_closure_revision');
});
});
beforeEach(function(){
return db.transaction().then(function(transaction){ txn = transaction; });
});
afterEach(function(done){
db.close().then(function(){ rimraf(testDir, done); }).done();
});
describe('makeRevisionsModel method', function(){
var configA;
beforeEach(function(){
configA = {
referenceModel: testModelA,
referenceModelPrimaryKey: 'id'
};
});
it('should not accept invalid referenceModel', function(){
expect(function(){ revisionsLib.makeRevisionsModel(_.merge({}, configA, {referenceModel:null})); }).to.throw(Error);
expect(function(){ revisionsLib.makeRevisionsModel(_.merge({}, configA, {referenceModel:10})); }).to.throw(Error);
expect(function(){ revisionsLib.makeRevisionsModel(_.merge({}, configA, {referenceModel:{}})); }).to.throw(Error);
return txnSuccess();
});
it('should not accept invalid referenceModelPrimaryKey', function(){
expect(function(){ revisionsLib.makeRevisionsModel(_.merge({}, configA, {referenceModelPrimaryKey:null})); }).to.throw(Error);
expect(function(){ revisionsLib.makeRevisionsModel(_.merge({}, configA, {referenceModelPrimaryKey:10})); }).to.throw(Error);
expect(function(){ revisionsLib.makeRevisionsModel(_.merge({}, configA, {referenceModelPrimaryKey:{}})); }).to.throw(Error);
return txnSuccess();
});
describe('revisions model', function(){
var revisionModelA;
beforeEach(function(){
return db.transaction(function(t){
return testModelA.bulkCreate([
{id:'1', name: 'item1'},
{id:'2', name: 'item2'}
], {individualHooks: true, transaction:t});
});
});
beforeEach(function(){
return db.transaction(function(t){
return testModelC.bulkCreate([
{id:'1', name: 'item1'},
{id:'2', name: 'item2'}
], {individualHooks: true, transaction:t});
});
});
beforeEach(function(){
return db.transaction(function(t){
return testModelB.bulkCreate([
{id:'1', name: 'item1', cModelId:'1'},
{id:'2', name: 'item2', cModelId:'2'}
], {individualHooks: true, transaction:t}).then(function(records){
return records[0].addChild(records[1], {individualHooks: true, transaction:t});
});
});
});
beforeEach(function(){
revisionModelA = revisionsLib.makeRevisionsModel(configA);
return db._sequelize.sync();
});
it('should make name $tableName + _revision', function(){
expect(revisionModelA.name).to.equal(configA.referenceModel.name + '_revision');
return txnSuccess();
});
it('should have revisionId, revisionFrom and revisionTo field', function(){
expect(revisionModelA.attributes.revisionId).to.be.ok;
expect(revisionModelA.attributes.revisionValidFrom).to.be.ok;
expect(revisionModelA.attributes.revisionValidTo).to.be.ok;
return txnSuccess();
});
it('should copy all the original fields into the revisions Table', function(){
expect(revisionModelA.attributes.id).to.be.ok;
expect(revisionModelA.attributes.name).to.be.ok;
expect(testRevisionModelB.attributes.parentId).to.be.ok;
expect(testRevisionModelB.attributes.cModelId).to.be.ok;
return txnSuccess();
});
it('should strip primaryKey,unique,references,onDelete,onUpdate from original table attributes', function(){
expect(testModelA.attributes.id.primaryKey).to.be.ok;
expect(testModelA.attributes.name.unique).to.be.ok;
expect(revisionModelA.attributes.id.primaryKey).to.not.be.ok;
expect(revisionModelA.attributes.name.unique).to.not.be.ok;
expect(testModelB.attributes.cModelId.onDelete.toUpperCase()).to.equal('CASCADE');
expect(testModelB.attributes.cModelId.onUpdate.toUpperCase()).to.not.equal('CASCADE');
expect(testModelB.attributes.cModelId.references).to.be.ok;
expect(testRevisionModelB.attributes.cModelId.onDelete).to.not.be.ok;
expect(testRevisionModelB.attributes.cModelId.onUpdate).to.not.be.ok;
expect(testRevisionModelB.attributes.cModelId.references).to.not.be.ok;
return txnSuccess();
});
it('should remove timestamps and have no timestamps fields', function(){
expect(testModelA.attributes.createdAt).to.be.ok;
expect(testModelA.attributes.updatedAt).to.be.ok;
expect(testModelA.attributes.deletedAt).to.be.ok;
expect(revisionModelA.attributes.createdAt).to.not.be.ok;
expect(revisionModelA.attributes.updatedAt).to.not.be.ok;
expect(revisionModelA.attributes.deletedAt).to.not.be.ok;
return txnSuccess();
});
describe('hierarchies', function(){
it('should make closure revision name $tableName + _closure_revision', function(){
expect(testClosureRevisionModelB.name).to.equal(testModelB.name + '_closure_revision');
return txnSuccess();
});
});
describe('hook functionality', function(){
var tmplA;
beforeEach(function(){
return testModelA.findOne({}).then(function(item){
tmplA = {id: item.id, name: item.name };
});
});
it('should not allow revisionValidFrom to be set', function(){
return expect(revisionModelA.create(_.merge({}, tmplA, {revisionValidFrom:new Date()}), getOptions())
.then(txnSuccess, txnFail)).to.eventually.be.rejected;
});
it('should not allow revisionValidTo to be set', function(){
return expect(revisionModelA.create(_.merge({}, tmplA, {revisionValidTo:new Date()}), getOptions())
.then(txnSuccess, txnFail)).to.eventually.be.rejected;
});
it('should set revisionValidFrom to date and revisionValidTo to null on the first revision', function(){
return revisionModelA.create(tmplA, getOptions()).then(txnSuccess, txnFail).then(function(revisionRecord){
expect(revisionRecord.revisionValidFrom).to.be.ok;
expect(revisionRecord.revisionValidTo).to.be.null;
});
});
it('should set revisionValidTo on previous revision and revisionValidFrom on current revision', function(){
return revisionModelA.create(tmplA, getOptions()).then(function(){ return Promise.delay(10); }).then(function(){
return revisionModelA.create(_.merge({}, tmplA, {name:'hi'}), getOptions()).then(function(){
return revisionModelA.findAll({where:{id:tmplA.id}, transaction:txn}).then(function(revisions){
expect(revisions.length).to.equal(2);
expect(revisions[0].revisionValidFrom).to.be.below(revisions[1].revisionValidFrom);
expect(revisions[0].revisionValidTo.toString()).to.equal(revisions[1].revisionValidFrom.toString());
expect(revisions[1].revisionValidTo).to.be.null;
});
});
})
.then(txnSuccess, txnFail);
});
it('should not allow updating fields other than revisionValidTo', function(){
return revisionModelA.create(tmplA, getOptions()).then(function(revisionRecord){
return expect(revisionRecord.update({name:'hi'}, getOptions())).to.eventually.be.rejected;
})
.then(txnSuccess, txnFail);
});
it('should only update revisionValidTo once', function(){
return revisionModelA.create(tmplA, getOptions()).then(function(revisionRecord){
return revisionRecord.update({revisionValidTo: new Date()}, getOptions()).then(function(){
return expect(revisionRecord.update({revisionValidTo: new Date()}, getOptions())).to.eventually.be.rejected;
});
})
.then(txnSuccess, txnFail);
});
it('should not allow deleting', function(){
return revisionModelA.create(tmplA, getOptions()).then(function(revisionRecord){
return expect(revisionRecord.destroy(getOptions())).to.eventually.be.rejected;
})
.then(txnSuccess, txnFail);
});
it('should mixin idLib -- id should be generated', function(){
return revisionModelA.create(tmplA, getOptions()).then(function(revisionRecord){
expect(revisionRecord.revisionId).to.be.ok;
expect(typeof revisionRecord.revisionId).to.equal('string');
})
.then(txnSuccess, txnFail);
});
});
});
describe('parent model hooks', function(){
beforeEach(function(){
return db.transaction(function(t){
return testModelC.bulkCreate([
{id:'1', name: 'item1'},
{id:'2', name: 'item2'},
{id:'3', name: 'item3'}
], {individualHooks: true, transaction:t});
});
});
beforeEach(function(){
return db.transaction(function(t){
return testModelB.bulkCreate([
{id:'1', name: 'item1', cModelId:'1'},
{id:'2', name: 'item2', cModelId:'2'}
], {individualHooks: true, transaction:t}).then(function(records){
return records[0].addChild(records[1], {individualHooks: true, transaction:t});
});
});
});
describe('create tests', function(){
var tmplB = {id:'3', name:'item3', parentId:'2', cModelId:'3'};
it('should not create if transaction is missing', function(){
return expect(testModelB.create(_.merge({}, tmplB), {individualHooks: true}).then(txnSuccess, txnFail)).to.eventually.be.rejected;
});
it('should not bulkCreate if individualHooks is missing', function(){
return expect(testModelB.bulkCreate([_.merge({}, tmplB)], {transaction: txn}).then(txnSuccess, txnFail)).to.eventually.be.rejected;
});
it('should not allow other revisions on create', function(){
var newOptions = _.merge({}, tmplB, {parentId:'3', cModelId: '2'});
return testRevisionModelB.create(newOptions, getOptions()).then(function(item){
expect(item).to.be.ok;
return expect(testModelB.create(tmplB, getOptions())).to.eventually.be.rejected;
})
.then(txnSuccess, txnFail);
});
it('should create revision on create', function(){
return testModelB.create(tmplB, getOptions()).then(function(item){
return testRevisionModelB.count({where:{id:item.id}, transaction:txn}).then(function(count){
expect(count).to.equal(1);
});
})
.then(txnSuccess, txnFail);
});
describe('hierarchies', function(){
it('should create identity hierarchy link revision if hierarchy', function(){
return testModelB.create(tmplB, getOptions()).then(function(item){
return testClosureRevisionModelB.findAll({where:{parentId:item.id}, transaction:txn}).then(function(closureRevisionItems){
expect(closureRevisionItems).to.have.length(1);
});
})
.then(txnSuccess, txnFail);
});
});
});
describe('update tests', function(){
it('should not update if transaction is missing', function(){
return testModelB.findById('1').then(function(record){
return expect(record.update({parentId:'2'}).then(txnSuccess, txnFail)).to.eventually.be.rejected;
});
});
it('should not bulkUpdate if individualHooks is missing', function(){
return expect(testModelB.update({parentId:'2'}, {where: {id:'1'}, transaction: txn}).then(txnSuccess, txnFail)).to.eventually.be.rejected;
});
it('should have previous revision on update', function(){
return db.query('DELETE FROM testModelForRevisionsLibB_revision WHERE id="1";').then(function(){
return testModelB.findById('1').then(function(record){
return expect(record.update({parentId:'2'}, getOptions())).to.eventually.be.rejected;
});
})
.then(txnSuccess, txnFail);
});
it('should set old revisions revisionValidFrom and create new revision', function(){
return Promise.delay(10).then(function(){
return testModelB.findById('1').then(function(record){
return record.update({parentId:'2'}, getOptions()).then(function(){
return testRevisionModelB.findAll({where:{id:record.id}, transaction:txn}).then(function(revisions){
expect(revisions.length).to.equal(2);
expect(revisions[0].revisionValidFrom).to.be.below(revisions[1].revisionValidFrom);
expect(revisions[0].revisionValidTo.toString()).to.equal(revisions[1].revisionValidFrom.toString());
expect(revisions[1].revisionValidTo).to.be.null;
});
});
});
})
.then(txnSuccess, txnFail);
});
it('should not create new revision if no fields are updated', function(){
return testModelB.findById('2').then(function(record){
return testRevisionModelB.count({where:{id:record.id}, transaction:txn}).then(function(oldRevisionCount){
return record.update({parentId:'1'}, getOptions()).then(function(){
return testRevisionModelB.count({where:{id:record.id}, transaction:txn}).then(function(newRevisionCount){
expect(oldRevisionCount).to.equal(newRevisionCount);
});
});
});
})
.then(txnSuccess, txnFail);
});
describe('hierarchies', function(){
it('should create closure revision when heirarchy association added', function(){
return testModelB.findById('1').then(function(record){
return record.update({parentId:'2'}, getOptions()).then(function(record){
return testClosureRevisionModelB.findAll({where:{childId:record.id, revisionValidTo:null}, transaction:txn})
.then(function(closureRecords){
expect(closureRecords).to.have.length(2);
expect(closureRecords[0].parentId).to.equal('1');
expect(closureRecords[1].parentId).to.equal('2');
});
});
})
.then(txnSuccess, txnFail);
});
it('should set closure revision revisionValidTo when heirarchy association removed', function(){
return testModelB.findById('2').then(function(record){
return record.update({parentId:null}, getOptions()).then(function(record){
return testClosureRevisionModelB.findAll({where:{childId:record.id, revisionValidTo:null}, transaction:txn})
.then(function(closureRecords){
expect(closureRecords).to.have.length(1);
expect(closureRecords[0].parentId).to.equal('2');
});
});
})
.then(txnSuccess, txnFail);
});
});
});
describe('delete tests', function(){
it('should not delete if transaction is missing', function(){
return testModelB.findById('1').then(function(record){
return expect(record.destroy().then(txnSuccess, txnFail)).to.eventually.be.rejected;
});
});
it('should not bulkDelete if individualHooks is missing', function(){
return expect(testModelB.destroy({where: {id:'1'}, transaction: txn}).then(txnSuccess, txnFail)).to.eventually.be.rejected;
});
it('should have previous revision on delete', function(){
return db.query('DELETE FROM testModelForRevisionsLibB_revision WHERE id="1";').then(function(){
return testModelB.findById('1').then(function(record){
return expect(record.destroy(getOptions())).to.eventually.be.rejected;
});
})
.then(txnSuccess, txnFail);
});
it('should set revisionValidTo on the last revision', function(){
return testModelB.findById('1').then(function(record){
return record.destroy(getOptions()).then(function(){
return testRevisionModelB.findAll({where:{id:'1'}, transaction:txn}).then(function(revisions){
expect(revisions.length).to.equal(1);
expect(revisions[0].revisionValidTo).to.be.ok;
});
});
})
.then(txnSuccess, txnFail);
});
describe('hierarchies', function(){
it('should set closure revision revisionValidTo when item deleted', function(){
return testModelB.findById('2').then(function(record){
return record.destroy(getOptions()).then(txnSuccess, txnFail).then(function(){
return testClosureRevisionModelB.findAll({where:{childId:'2'}}).then(function(revisions){
debugger;
expect(revisions).to.have.length(2);
expect(revisions[0].revisionValidTo).to.be.ok;
expect(revisions[1].revisionValidTo).to.be.ok;
});
});
})
;
});
});
});
});
});
};
};
@patrickdbakke
Copy link

@ssteffl What's the license for these snippets / permission to reproduce?

@ssteffl
Copy link
Author

ssteffl commented Jul 12, 2017

@patrickdbakke there are no legal restrictions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment