From 1927d4d53f8b14ce50a628489121fa198b9f1d46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franc=CC=A7ois=20Hodierne?= Date: Mon, 25 Mar 2019 09:59:41 +0100 Subject: [PATCH 01/37] chore: transform package in sequelize-historical --- README.md | 33 +++++++++++++++++---------------- package.json | 12 ++++++------ 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index c61e543..7a710ad 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,15 @@ -Temporal tables for Sequelize -============================= -(aka "Historical records") +Historical tables for Sequelize +=============================== -[![Build Status](https://travis-ci.org/bonaval/sequelize-temporal.svg?branch=master)](https://travis-ci.org/bonaval/sequelize-temporal) [![Dependency Status](https://david-dm.org/bonaval/sequelize-temporal.svg)](https://david-dm.org/bonaval/sequelize-temporal) [![NPM version](https://img.shields.io/npm/v/sequelize-temporal.svg)](https://www.npmjs.com/package/sequelize-temporal) +Warning: this is a fork of [sequelize-temporal](https://github.com/bonaval/sequelize-temporal) that adds support for Sequelize 5. + +[![Build Status](https://travis-ci.org/opencollective/sequelize-historical.svg?branch=master)](https://travis-ci.org/opencollective/sequelize-historical) [![Dependency Status](https://david-dm.org/opencollective/sequelize-historical.svg)](https://david-dm.org/opencollective/sequelize-historical) [![NPM version](https://img.shields.io/npm/v/sequelize-historical.svg)](https://www.npmjs.com/package/sequelize-historical) What is it? ----------- -Temporal tables maintain __historical versions__ of data. Modifying operations (UPDATE, DELETE) on these tables don't cause permanent changes to entries, but create new versions of them. Hence this might be used to: +Historical tables maintain __historical versions__ of data. Modifying operations (UPDATE, DELETE) on these tables don't cause permanent changes to entries, but create new versions of them. Hence this might be used to: - log changes (security/auditing) - undo functionalities @@ -25,17 +26,17 @@ Installation ------------ ``` -npm install sequelize-temporal +npm install sequelize-historical ``` How to use ---------- -### 1) Import `sequelize-temporal` +### 1) Import `sequelize-historical` ``` var Sequelize = require('sequelize'); -var Temporal = require('sequelize-temporal'); +var Historical = require('sequelize-historical'); ``` Create a sequelize instance and your models, e.g. @@ -47,26 +48,26 @@ var sequelize = new Sequelize('', '', '', { }); ``` -### 2) Add the *temporal* feature to your models +### 2) Add the *historical* feature to your models ``` -var User = Temporal(sequelize.define('User'), sequelize); +var User = Historical(sequelize.define('User'), sequelize); ``` -The output of `temporal` is its input model, so assigning it's output to your +The output of `historical` is its input model, so assigning it's output to your Model is not necessary, hence it's just the lazy version of: ``` var User = sequelize.define('User', {.types.}, {.options.}); // Sequelize Docu -Temporal(User, sequelize); +Historical(User, sequelize); ``` Options ------- -The default syntax for `Temporal` is: +The default syntax for `Historical` is: -`Temporal(model, sequelizeInstance, options)` +`Historical(model, sequelizeInstance, options)` whereas the options are listed here (with default value). @@ -75,8 +76,8 @@ whereas the options are listed here (with default value). /* runs the insert within the sequelize hook chain, disable for increased performance without warranties */ blocking: true, - /* By default sequelize-temporal persist only changes, and saves the previous state in the history table. - The "full" option saves all transactions into the temporal database + /* By default sequelize-historical persist only changes, and saves the previous state in the history table. + The "full" option saves all transactions into the historical database (i.e. this includes the latest state.) This allows to only query the hostory table to get the full history of an entity. */ diff --git a/package.json b/package.json index 61d97c7..cedee3e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "sequelize-temporal", - "version": "1.0.6", - "description": "Temporal tables for Sequelize", + "name": "sequelize-historical", + "version": "5.1.0", + "description": "Historical tables for Sequelize", "main": "index.js", "directories": { "test": "test" @@ -11,7 +11,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/bonaval/sequelize-temporal.git" + "url": "git+https://github.com/opencollective/sequelize-historical.git" }, "keywords": [ "sequelize", @@ -25,9 +25,9 @@ "author": "greenify (https://github.com/greenify)", "license": "MIT", "bugs": { - "url": "https://github.com/bonaval/sequelize-temporal/issues" + "url": "https://github.com/opencollective/sequelize-historical/issues" }, - "homepage": "https://github.com/bonaval/sequelize-temporal#readme", + "homepage": "https://github.com/opencollective/sequelize-historical#readme", "dependencies": { "lodash": "^3.10.1" }, From ddc1b84c5fd64f128d535e93a4b4af27dc5be3df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franc=CC=A7ois=20Hodierne?= Date: Wed, 27 Mar 2019 09:26:46 +0100 Subject: [PATCH 02/37] package: update mocha to version ^6.0.2 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index cedee3e..f3a63a7 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "test": "test" }, "scripts": { - "test": "./node_modules/mocha/bin/mocha" + "test": "mocha" }, "repository": { "type": "git", @@ -34,8 +34,8 @@ "devDependencies": { "chai": "^4.1.2", "chai-as-promised": "^7.1.1", - "mocha": "^5.2.0", "sequelize": "^4.38.0", "sqlite3": "^4.0.1" + "mocha": "^6.0.2", } } From 28204d913fae407d4460ac45c3b8f223c087c471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franc=CC=A7ois=20Hodierne?= Date: Wed, 27 Mar 2019 09:27:17 +0100 Subject: [PATCH 03/37] package: update chai to version 4.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f3a63a7..c4a894b 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "lodash": "^3.10.1" }, "devDependencies": { - "chai": "^4.1.2", + "chai": "^4.2.0", "chai-as-promised": "^7.1.1", "sequelize": "^4.38.0", "sqlite3": "^4.0.1" From de79d5d945aa836660a65cdedfd1a4b1fa9fa943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franc=CC=A7ois=20Hodierne?= Date: Wed, 27 Mar 2019 09:27:49 +0100 Subject: [PATCH 04/37] package: update sequelize to version 5.2.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c4a894b..ab2513d 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,8 @@ "devDependencies": { "chai": "^4.2.0", "chai-as-promised": "^7.1.1", - "sequelize": "^4.38.0", "sqlite3": "^4.0.1" "mocha": "^6.0.2", + "sequelize": "^5.2.1", } } From d16b0ac145c2c37e4caad7fb3b3a3e9218da1632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franc=CC=A7ois=20Hodierne?= Date: Wed, 27 Mar 2019 09:28:21 +0100 Subject: [PATCH 05/37] package: update sqlite3 to version 4.0.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ab2513d..d370b51 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,8 @@ "devDependencies": { "chai": "^4.2.0", "chai-as-promised": "^7.1.1", - "sqlite3": "^4.0.1" "mocha": "^6.0.2", "sequelize": "^5.2.1", + "sqlite3": "^4.0.6" } } From 76d7f86e704e28acd1e7b4789a6b51c4f8ea51b0 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 27 Mar 2019 08:30:40 +0000 Subject: [PATCH 06/37] chore(package): update dependencies --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d370b51..6511c9b 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/opencollective/sequelize-historical#readme", "dependencies": { - "lodash": "^3.10.1" + "lodash": "^4.17.11" }, "devDependencies": { "chai": "^4.2.0", From cc329c11cde47d7039ab5c29e48842af450fe551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franc=CC=A7ois=20Hodierne?= Date: Wed, 27 Mar 2019 09:31:01 +0100 Subject: [PATCH 07/37] chore: ignore package-lock.json --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index cc3ee3a..6a1afe5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .test.sqlite node_modules +package-lock.json From 5ece149b5090c708c0cb8fc69c9c950ad06d8e00 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 27 Mar 2019 08:30:43 +0000 Subject: [PATCH 08/37] docs(readme): add Greenkeeper badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7a710ad..36cbd18 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Historical tables for Sequelize Warning: this is a fork of [sequelize-temporal](https://github.com/bonaval/sequelize-temporal) that adds support for Sequelize 5. -[![Build Status](https://travis-ci.org/opencollective/sequelize-historical.svg?branch=master)](https://travis-ci.org/opencollective/sequelize-historical) [![Dependency Status](https://david-dm.org/opencollective/sequelize-historical.svg)](https://david-dm.org/opencollective/sequelize-historical) [![NPM version](https://img.shields.io/npm/v/sequelize-historical.svg)](https://www.npmjs.com/package/sequelize-historical) +[![Build Status](https://travis-ci.org/opencollective/sequelize-historical.svg?branch=master)](https://travis-ci.org/opencollective/sequelize-historical) [![Dependency Status](https://david-dm.org/opencollective/sequelize-historical.svg)](https://david-dm.org/opencollective/sequelize-historical) [![NPM version](https://img.shields.io/npm/v/sequelize-historical.svg)](https://www.npmjs.com/package/sequelize-historical) [![Greenkeeper badge](https://badges.greenkeeper.io/opencollective/sequelize-historical.svg)](https://greenkeeper.io/) What is it? From 208d5bbaa13eb684c6350ffbfaa68cc319f40d58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franc=CC=A7ois=20Hodierne?= Date: Wed, 27 Mar 2019 11:34:20 +0100 Subject: [PATCH 09/37] package: downgrade lodash to version 3.10.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6511c9b..d370b51 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/opencollective/sequelize-historical#readme", "dependencies": { - "lodash": "^4.17.11" + "lodash": "^3.10.1" }, "devDependencies": { "chai": "^4.2.0", From 6565f8835c0af044343ad6a0cce1a350bc988d11 Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Mon, 15 Apr 2019 21:37:41 +0900 Subject: [PATCH 10/37] Added ModelSuffix Added the 'modelSuffix' option to enable custom name to be appended to Model and Table --- README.md | 14 +++- index.js | 5 +- package.json | 4 +- test/test.js | 216 ++++++++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 212 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 36cbd18..3bbdb18 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Historical tables maintain __historical versions__ of data. Modifying operations - undo functionalities - track interactions (customer support) -Under the hood a history table with the same structure, but without constraints is created. +Under the hood a history table with the same structure, but without constraints is created in the model (you will need to make sure the table is created in the database). The normal singular/plural naming scheme in Sequelize is used: @@ -81,7 +81,17 @@ whereas the options are listed here (with default value). (i.e. this includes the latest state.) This allows to only query the hostory table to get the full history of an entity. */ - full: false + full: false, + /* By default sequelize-historical will add 'History' to the historical Model name and 'Histories' to the historical table. + By updating the modelSuffix value, you can decide what the naming will be. + The value will be appended to the historical Model name and its plural will be appended to the historical tablename. + + examples for table User: + modelSuffix: '_Hist' --> Historical Model Name: User_Hist --> Historical Table Name: User_Hists + modelSuffix: 'Memory' --> Historical Model Name: UserMemory --> Historical Table Name: UserMemories + modelSuffix: 'Pass' --> Historical Model Name: UserPass --> Historical Table Name: UserPasses + */ + modelSuffix: 'History' ``` Details diff --git a/index.js b/index.js index 2f7714b..96b1802 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,8 @@ var temporalDefaultOptions = { // runs the insert within the sequelize hook chain, disable // for increased performance blocking: true, - full: false + full: false, + modelSuffix: 'History' }; var excludeAttributes = function(obj, attrsToExclude){ @@ -17,7 +18,7 @@ var Temporal = function(model, sequelize, temporalOptions){ var Sequelize = sequelize.Sequelize; - var historyName = model.name + 'History'; + var historyName = model.name + temporalOptions.modelSuffix; //var historyName = model.getTableName() + 'History'; //var historyName = model.options.name.singular + 'History'; diff --git a/package.json b/package.json index d370b51..8eb4687 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,8 @@ "devDependencies": { "chai": "^4.2.0", "chai-as-promised": "^7.1.1", - "mocha": "^6.0.2", - "sequelize": "^5.2.1", + "mocha": "^6.1.3", + "sequelize": "^5.4.0", "sqlite3": "^4.0.6" } } diff --git a/test/test.js b/test/test.js index 0a7bc5c..42a6a15 100644 --- a/test/test.js +++ b/test/test.js @@ -8,31 +8,42 @@ var eventually = assert.eventually; describe('Read-only API', function(){ var sequelize, User, UserHistory; + + function newDB(paranoid, options){ + suffix = options && options.modelSuffix ? options.modelSuffix : 'History'; + options = options || {}; + + sequelize = new Sequelize('', '', '', { + dialect: 'sqlite', + storage: __dirname + '/.test.sqlite' + }); - function freshDB(){ - // overwrites the old SQLite DB - sequelize = new Sequelize('', '', '', { - dialect: 'sqlite', - storage: __dirname + '/.test.sqlite' - }); User = Temporal(sequelize.define('User', { name: Sequelize.TEXT - }), sequelize); - UserHistory = sequelize.models.UserHistory; - return sequelize.sync({ force: true }); + }, {paranoid: paranoid || false}), sequelize, options); + + UserHistory = sequelize.models['User' + suffix]; + return sequelize.sync({ force: true }); } - function freshDBWithFullModeAndParanoid() { - sequelize = new Sequelize('', '', '', { - dialect: 'sqlite', - storage: __dirname + '/.test.sqlite' - }); - User = Temporal(sequelize.define('User', { - name: Sequelize.TEXT - }, { paranoid: true }), sequelize, { full: true }); - UserHistory = sequelize.models.UserHistory; + function freshDB(){ + return newDB(); + } + + function freshDBWithFullModeAndParanoid(){ + return newDB(true,{ full: true }); + } - return sequelize.sync({ force: true }); + function freshDBWithSuffixEndingWithT(){ + return newDB(false, { modelSuffix: '_Hist'}); + } + + function freshDBWithSuffixEndingWithY(){ + return newDB(false, { modelSuffix: 'Memory'}); + } + + function freshDBWithSuffixEndingWithS(){ + return newDB(false, { modelSuffix: 'Pass'}); } function assertCount(modelHistory, n, opts){ @@ -45,6 +56,170 @@ describe('Read-only API', function(){ } } + //these tests are the same as hooks since the results should not change, even with a different model name + //Only added is to test for the model name + describe('test suffix ending in T', function(){ + beforeEach(freshDBWithSuffixEndingWithT); + it('onCreate: should not store the new version in history db' , function(){ + return User.create({ name: 'test' }).then(assertCount(UserHistory, 0)); + }); + it('onUpdate/onDestroy: should save to the historyDB' , function(){ + return User.create() + .then(assertCount(UserHistory,0)) + .then(function(user){ + user.name = "foo"; + return user.save(); + }).then(assertCount(UserHistory,1)) + .then(function(user){ + return user.destroy(); + }).then(assertCount(UserHistory,2)) + }); + it('onUpdate: should store the previous version to the historyDB' , function(){ + return User.create({name: "foo"}) + .then(assertCount(UserHistory,0)) + .then(function(user){ + user.name = "bar"; + return user.save(); + }).then(assertCount(UserHistory,1)) + .then(function(){ + return UserHistory.findAll(); + }).then(function(users){ + assert.equal(users.length,1, "only one entry in DB"); + assert.equal(users[0].name, "foo", "previous entry saved"); + }).then(function(user){ + return User.findOne(); + }).then(function(user){ + return user.destroy(); + }).then(assertCount(UserHistory,2)) + }); + it('onDelete: should store the previous version to the historyDB' , function(){ + return User.create({name: "foo"}) + .then(assertCount(UserHistory,0)) + .then(function(user){ + return user.destroy(); + }).then(assertCount(UserHistory,1)) + .then(function(){ + return UserHistory.findAll(); + }).then(function(users){ + assert.equal(users.length,1, "only one entry in DB"); + assert.equal(users[0].name, "foo", "previous entry saved"); + }); + }); + it('onCreate: check the model is using the custom suffix' , function(){ + return User.create({ name: 'test' }).then(function(){ + assert.equal(UserHistory.name, User.name + '_Hist'); + }); + }); + }); + + describe('test suffix ending in Y', function(){ + beforeEach(freshDBWithSuffixEndingWithY); + it('onCreate: should not store the new version in history db' , function(){ + return User.create({ name: 'test' }).then(assertCount(UserHistory, 0)); + }); + it('onUpdate/onDestroy: should save to the historyDB' , function(){ + return User.create() + .then(assertCount(UserHistory,0)) + .then(function(user){ + user.name = "foo"; + return user.save(); + }).then(assertCount(UserHistory,1)) + .then(function(user){ + return user.destroy(); + }).then(assertCount(UserHistory,2)) + }); + it('onUpdate: should store the previous version to the historyDB' , function(){ + return User.create({name: "foo"}) + .then(assertCount(UserHistory,0)) + .then(function(user){ + user.name = "bar"; + return user.save(); + }).then(assertCount(UserHistory,1)) + .then(function(){ + return UserHistory.findAll(); + }).then(function(users){ + assert.equal(users.length,1, "only one entry in DB"); + assert.equal(users[0].name, "foo", "previous entry saved"); + }).then(function(user){ + return User.findOne(); + }).then(function(user){ + return user.destroy(); + }).then(assertCount(UserHistory,2)) + }); + it('onDelete: should store the previous version to the historyDB' , function(){ + return User.create({name: "foo"}) + .then(assertCount(UserHistory,0)) + .then(function(user){ + return user.destroy(); + }).then(assertCount(UserHistory,1)) + .then(function(){ + return UserHistory.findAll(); + }).then(function(users){ + assert.equal(users.length,1, "only one entry in DB"); + assert.equal(users[0].name, "foo", "previous entry saved"); + }); + }); + it('onCreate: check the model is using the custom suffix' , function(){ + return User.create({ name: 'test' }).then(function(){ + assert.equal(UserHistory.name, User.name + 'Memory'); + }); + }); + }); + + describe('test suffix ending in S', function(){ + beforeEach(freshDBWithSuffixEndingWithS); + it('onCreate: should not store the new version in history db' , function(){ + return User.create({ name: 'test' }).then(assertCount(UserHistory, 0)); + }); + it('onUpdate/onDestroy: should save to the historyDB' , function(){ + return User.create() + .then(assertCount(UserHistory,0)) + .then(function(user){ + user.name = "foo"; + return user.save(); + }).then(assertCount(UserHistory,1)) + .then(function(user){ + return user.destroy(); + }).then(assertCount(UserHistory,2)) + }); + it('onUpdate: should store the previous version to the historyDB' , function(){ + return User.create({name: "foo"}) + .then(assertCount(UserHistory,0)) + .then(function(user){ + user.name = "bar"; + return user.save(); + }).then(assertCount(UserHistory,1)) + .then(function(){ + return UserHistory.findAll(); + }).then(function(users){ + assert.equal(users.length,1, "only one entry in DB"); + assert.equal(users[0].name, "foo", "previous entry saved"); + }).then(function(user){ + return User.findOne(); + }).then(function(user){ + return user.destroy(); + }).then(assertCount(UserHistory,2)) + }); + it('onDelete: should store the previous version to the historyDB' , function(){ + return User.create({name: "foo"}) + .then(assertCount(UserHistory,0)) + .then(function(user){ + return user.destroy(); + }).then(assertCount(UserHistory,1)) + .then(function(){ + return UserHistory.findAll(); + }).then(function(users){ + assert.equal(users.length,1, "only one entry in DB"); + assert.equal(users[0].name, "foo", "previous entry saved"); + }); + }); + it('onCreate: check the model is using the custom suffix' , function(){ + return User.create({ name: 'test' }).then(function(){ + assert.equal(UserHistory.name, User.name + 'Pass'); + }); + }); + }); + describe('hooks', function(){ beforeEach(freshDB); it('onCreate: should not store the new version in history db' , function(){ @@ -329,7 +504,6 @@ describe('Read-only API', function(){ }) .then(assertCount(UserHistory,0)); }); - - }); + }); }); From f2e4230902198dbb0fdac4424a630e0194d8d736 Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Mon, 15 Apr 2019 21:48:10 +0900 Subject: [PATCH 11/37] Fixed Read Me Remove wrong explanation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3bbdb18..6c30fe4 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Historical tables maintain __historical versions__ of data. Modifying operations - undo functionalities - track interactions (customer support) -Under the hood a history table with the same structure, but without constraints is created in the model (you will need to make sure the table is created in the database). +Under the hood a history table with the same structure, but without constraints is created in the model. The normal singular/plural naming scheme in Sequelize is used: From 7c5c2d5b9bc1126dd2759a167c8534c9a35c1a8b Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Mon, 15 Apr 2019 21:52:05 +0900 Subject: [PATCH 12/37] Fixed Read Me Remove wrong text again. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6c30fe4..9393065 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Historical tables maintain __historical versions__ of data. Modifying operations - undo functionalities - track interactions (customer support) -Under the hood a history table with the same structure, but without constraints is created in the model. +Under the hood a history table with the same structure, but without constraints is created. The normal singular/plural naming scheme in Sequelize is used: From 55aaa1ab39c46efb175bf909816c3f5ff4eefc88 Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Mon, 15 Apr 2019 22:57:15 +0900 Subject: [PATCH 13/37] Added modelSuffix to ts file --- index.d.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index fc74696..7c94418 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,7 +1,8 @@ declare module 'sequelize-temporal' { interface Options { blocking?:boolean, - full?:boolean + full?:boolean, + modelSuffix?:string, } function output(define:T, sequelize:any, options?:Options): T From 5d46282f442b9e0c3365961a1edd2742cd5e5cec Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Thu, 18 Apr 2019 23:56:26 +0900 Subject: [PATCH 14/37] Association Implementation of Association but not yet working. --- .gitignore | 2 + README.md | 2 +- index.d.ts | 7 +++ index.js | 76 ++++++++++++++++++++++++++++-- test/test.js | 128 +++++++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 205 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 6a1afe5..f77c400 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .test.sqlite node_modules package-lock.json +server.js +.vscode diff --git a/README.md b/README.md index 9393065..244a4a2 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ whereas the options are listed here (with default value). /* By default sequelize-historical persist only changes, and saves the previous state in the history table. The "full" option saves all transactions into the historical database (i.e. this includes the latest state.) - This allows to only query the hostory table to get the full history of an entity. + This allows to only query the history table to get the full history of an entity. */ full: false, /* By default sequelize-historical will add 'History' to the historical Model name and 'Histories' to the historical table. diff --git a/index.d.ts b/index.d.ts index 7c94418..027b827 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,8 +1,15 @@ declare module 'sequelize-temporal' { + enum relations { + DISABLED, + ORIGIN, + HISTORY + } + interface Options { blocking?:boolean, full?:boolean, modelSuffix?:string, + keepRelations?:relations, } function output(define:T, sequelize:any, options?:Options): T diff --git a/index.js b/index.js index 96b1802..8c0a6d8 100644 --- a/index.js +++ b/index.js @@ -1,11 +1,19 @@ var _ = require('lodash'); +const relations = { + DISABLED: 0, + ORIGIN: 1, + HISTORY: 2 +} + +//TODO add options to keep no foreign key associations to historical to get associated models (add secondary option to select keep linked to current or to historical) var temporalDefaultOptions = { // runs the insert within the sequelize hook chain, disable // for increased performance blocking: true, full: false, - modelSuffix: 'History' + modelSuffix: 'History', + keepRelations: relations.DISABLED }; var excludeAttributes = function(obj, attrsToExclude){ @@ -19,8 +27,6 @@ var Temporal = function(model, sequelize, temporalOptions){ var Sequelize = sequelize.Sequelize; var historyName = model.name + temporalOptions.modelSuffix; - //var historyName = model.getTableName() + 'History'; - //var historyName = model.options.name.singular + 'History'; var historyOwnAttrs = { hid: { @@ -37,7 +43,7 @@ var Temporal = function(model, sequelize, temporalOptions){ }; var excludedAttributes = ["Model","unique","primaryKey","autoIncrement", "set", "get", "_modelAttribute"]; - var historyAttributes = _(model.rawAttributes).mapValues(function(v){ + var historyAttributes = _(model.rawAttributes).mapValues(function(v){ v = excludeAttributes(v, excludedAttributes); // remove the "NOW" defaultValue for the default timestamps // we want to save them, but just a copy from our master record @@ -86,6 +92,59 @@ var Temporal = function(model, sequelize, temporalOptions){ } } + var afterBulkSyncHook = function(options){ + sequelize.removeHook('beforeBulkSync', 'TemporalBulkSyncHook'); + sequelize.removeHook('afterBulkSync', 'TemporalBulkSyncHook'); + return Promise.resolve('Temporal Hooks Removed'); + } + + + var beforeBulkSyncHook = function(options){ + const allModels = sequelize.models; + Object.keys(allModels).forEach(key => { + const model = allModels[key]; + if(!model.name.endsWith(temporalOptions.modelSuffix) && model.associations) { + Object.keys(model.associations).forEach(key => { + const source = model; + const association = model.associations[key]; + const target = association.target; + + const sourceHistName = source.name + temporalOptions.modelSuffix; + const sourceHist = allModels[sourceHistName]; + + const targetHistName = target.name + temporalOptions.modelSuffix; + const targetHist = allModels[targetHistName]; + const tableName = source.name + target.name; + + if(sourceHist.keepRelations == relations.HISTORY) { + //TODO FIX relations table not having entries + //TODO add options to belongsToMany + //TODO remove"through" from options since we're adding it + //TODO test with several associations + //TODO test with several associations to the same table i.e: addedBy, UpdatedBy + + if(!sourceHist.associations[targetHist.tableName]) + { + sourceHist.belongsToMany(targetHist, {through: tableName}); + targetHist.belongsToMany(sourceHist, {through: tableName}); + } + } + + if(sourceHist.keepRelations == relations.ORIGIN) { + const assocName = association.associationType.charAt(0).toLowerCase() + association.associationType.substr(1); + sourceHist[assocName].apply(sourceHist, [target, association.options]); + } + + sequelize.models[sourceHistName] = sourceHist; + sequelize.models[sourceHistName].sync(); + + }); + } + }); + + return Promise.resolve('Temporal associations established'); + } + // use `after` to be nonBlocking // all hooks just create a copy if (temporalOptions.full) { @@ -108,6 +167,15 @@ var Temporal = function(model, sequelize, temporalOptions){ modelHistory.addHook('beforeUpdate', readOnlyHook); modelHistory.addHook('beforeDestroy', readOnlyHook); + sequelize.removeHook('beforeBulkSync', 'TemporalBulkSyncHook');//remove first to avoid duplicating + sequelize.removeHook('afterBulkSync', 'TemporalBulkSyncHook');//remove first to avoid duplicating + sequelize.addHook('afterBulkSync', 'TemporalBulkSyncHook', afterBulkSyncHook); + sequelize.addHook('beforeBulkSync', 'TemporalBulkSyncHook', beforeBulkSyncHook); + +// sequelize.removeHook('afterBulkSync', 'TemporalBulkSyncHook');//remove first to avoid duplicating +// sequelize.addHook('afterBulkSync', 'TemporalBulkSyncHook', beforeBulkSyncHook); + + modelHistory.keepRelations = temporalOptions.keepRelations; return model; }; diff --git a/test/test.js b/test/test.js index 42a6a15..179e595 100644 --- a/test/test.js +++ b/test/test.js @@ -7,7 +7,7 @@ var assert = chai.assert; var eventually = assert.eventually; describe('Read-only API', function(){ - var sequelize, User, UserHistory; + var sequelize, User, UserHistory, Creation, CreationHistory; function newDB(paranoid, options){ suffix = options && options.modelSuffix ? options.modelSuffix : 'History'; @@ -18,11 +18,27 @@ describe('Read-only API', function(){ storage: __dirname + '/.test.sqlite' }); - User = Temporal(sequelize.define('User', { - name: Sequelize.TEXT - }, {paranoid: paranoid || false}), sequelize, options); - + //create 2 models and their historical models to test associations + User = sequelize.define('User', { + name: Sequelize.TEXT + }, {paranoid: paranoid || false}); + + Creation = sequelize.define('Creation', { + name: Sequelize.TEXT, + user: Sequelize.INTEGER, + }, {paranoid: paranoid || false}); + + //associate the 2 models together + User.hasMany(Creation, { foreignKey: 'user' }); + Creation.belongsTo(User, { foreignKey: 'user' }); + + //Temporalize + User = Temporal(User, sequelize, options); + Creation = Temporal(Creation, sequelize, options); + UserHistory = sequelize.models['User' + suffix]; + CreationHistory = sequelize.models['Creation' + suffix]; + return sequelize.sync({ force: true }); } @@ -30,6 +46,14 @@ describe('Read-only API', function(){ return newDB(); } + function freshDBWithOriginRelations(){ + return newDB(false, { keepRelations: 1}); + } + + function freshDBWithHistoricalRelations(){ + return newDB(false, { keepRelations: 2}); + } + function freshDBWithFullModeAndParanoid(){ return newDB(true,{ full: true }); } @@ -56,6 +80,100 @@ describe('Read-only API', function(){ } } + describe.only('Association Tests', function(){ + describe('test there are no historical association', function(){ + beforeEach(freshDB); + it('onCreate: should have relations for origin models but not for historical models' , function(){ + return User.create({ name: 'test' }).then( u => { + assert.exists(u.id); + u.name = 'test renamed'; + u.save(); + return Creation.create({ name: 'test', user: u.id }); + }).then(c => { + assert.exists(c.id); + c.name = 'creation renamed'; + c.save(); + return c.getUser(); + }).then( u => { + assert.exists(u.id); + assertCount(UserHistory, 1); + assertCount(CreationHistory, 1); + + return CreationHistory.findOne(); + }).then( c => { + assert.exists(c.id); + assert.isUndefined(c.getUser); + }); + }); + }); + + describe('test there are historical associations to origin models', function(){ + beforeEach(freshDBWithOriginRelations); + it('onCreate: should have relations for origin models and for historical models to origin' , function(){ + return User.create({ name: 'test' }).then( u => { + assert.exists(u.id); + u.name = 'test renamed'; + u.save(); + return Creation.create({ name: 'test', user: u.id }); + }).then(c => { + assert.exists(c.id); + c.name = 'creation renamed'; + c.save(); + return c.getUser(); + }).then( u => { + assert.exists(u.id); + assertCount(UserHistory, 1); + assertCount(CreationHistory, 1); + + return CreationHistory.findOne(); + }).then( c => { + assert.exists(c.id); + assert.exists(c.getUser); + assert.notExists(c.getUsers); + + return c.getUser(); + }).then( u => { + assert.exists(u.id); + }); + }); + }); + + describe('test there are historical associations to historical models', function(){ + beforeEach(freshDBWithHistoricalRelations); + it('onCreate: should have relations for origin models and for historical models to historical' , function(){ + return User.create({ name: 'test' }).then( u => { + assert.exists(u.id); + u.name = 'test renamed'; + u.save(); + u.name = 'test renamed twice'; + u.save(); + u.name = 'test renamed three times'; + u.save(); + return Creation.create({ name: 'test', user: u.id }); + }).then(c => { + assert.exists(c.id); + c.name = 'creation renamed'; + c.save(); + return c.getUser(); + }).then( u => { + assert.exists(u.id); + assertCount(UserHistory, 3); + assertCount(CreationHistory, 1); + + return CreationHistory.findOne(); + }).then( c => { + assert.exists(c.id); + assert.notExists(c.getUser); + assert.exists(c.getUsers); + + return c.getUsers(); + }).then( u => { + assert.equal(u.length, 3); + }); + }); + }); + }); + //these tests are the same as hooks since the results should not change, even with a different model name //Only added is to test for the model name describe('test suffix ending in T', function(){ From 96bd1b4aa2a2083301658d3af585ef3f332af717 Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Sun, 21 Apr 2019 01:13:37 +0900 Subject: [PATCH 15/37] Fix association --- index.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 8c0a6d8..30686d0 100644 --- a/index.js +++ b/index.js @@ -124,9 +124,9 @@ var Temporal = function(model, sequelize, temporalOptions){ //TODO test with several associations to the same table i.e: addedBy, UpdatedBy if(!sourceHist.associations[targetHist.tableName]) - { + { sourceHist.belongsToMany(targetHist, {through: tableName}); - targetHist.belongsToMany(sourceHist, {through: tableName}); + targetHist.belongsToMany(sourceHist, {through: tableName}); } } @@ -172,9 +172,6 @@ var Temporal = function(model, sequelize, temporalOptions){ sequelize.addHook('afterBulkSync', 'TemporalBulkSyncHook', afterBulkSyncHook); sequelize.addHook('beforeBulkSync', 'TemporalBulkSyncHook', beforeBulkSyncHook); -// sequelize.removeHook('afterBulkSync', 'TemporalBulkSyncHook');//remove first to avoid duplicating -// sequelize.addHook('afterBulkSync', 'TemporalBulkSyncHook', beforeBulkSyncHook); - modelHistory.keepRelations = temporalOptions.keepRelations; return model; }; From 07581c77c6d25cdaacc4fc044ce8763c66cdc48e Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Sun, 21 Apr 2019 02:19:46 +0900 Subject: [PATCH 16/37] Update association logic --- index.d.ts | 8 +------- index.js | 23 +++++++++-------------- 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/index.d.ts b/index.d.ts index 027b827..7281eba 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,15 +1,9 @@ declare module 'sequelize-temporal' { - enum relations { - DISABLED, - ORIGIN, - HISTORY - } - interface Options { blocking?:boolean, full?:boolean, modelSuffix?:string, - keepRelations?:relations, + addAssociations?:boolean, } function output(define:T, sequelize:any, options?:Options): T diff --git a/index.js b/index.js index 30686d0..857445d 100644 --- a/index.js +++ b/index.js @@ -13,7 +13,7 @@ var temporalDefaultOptions = { blocking: true, full: false, modelSuffix: 'History', - keepRelations: relations.DISABLED + addAssociations: false }; var excludeAttributes = function(obj, attrsToExclude){ @@ -116,23 +116,18 @@ var Temporal = function(model, sequelize, temporalOptions){ const targetHist = allModels[targetHistName]; const tableName = source.name + target.name; - if(sourceHist.keepRelations == relations.HISTORY) { - //TODO FIX relations table not having entries - //TODO add options to belongsToMany - //TODO remove"through" from options since we're adding it + if(sourceHist.addAssociations == true) { //TODO test with several associations //TODO test with several associations to the same table i.e: addedBy, UpdatedBy + //TODO test with all 1.*, 1.1, *.* - if(!sourceHist.associations[targetHist.tableName]) - { - sourceHist.belongsToMany(targetHist, {through: tableName}); - targetHist.belongsToMany(sourceHist, {through: tableName}); - } - } - - if(sourceHist.keepRelations == relations.ORIGIN) { + //adding associations from historical model to origin model's association const assocName = association.associationType.charAt(0).toLowerCase() + association.associationType.substr(1); sourceHist[assocName].apply(sourceHist, [target, association.options]); + + //adding associations between origin model and historical + source.hasMany(sourceHist, { foreignKey: source.primaryKeyField }); + sourceHist.belongsTo(source, { foreignKey: source.primaryKeyField }); } sequelize.models[sourceHistName] = sourceHist; @@ -172,7 +167,7 @@ var Temporal = function(model, sequelize, temporalOptions){ sequelize.addHook('afterBulkSync', 'TemporalBulkSyncHook', afterBulkSyncHook); sequelize.addHook('beforeBulkSync', 'TemporalBulkSyncHook', beforeBulkSyncHook); - modelHistory.keepRelations = temporalOptions.keepRelations; + modelHistory.addAssociations = temporalOptions.addAssociations; return model; }; From 2b85246a77337ca04896073b954854545e412647 Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Sun, 21 Apr 2019 13:59:38 +0900 Subject: [PATCH 17/37] Fixed logic --- index.js | 58 +++++++++++++++++++------------------------------------- 1 file changed, 20 insertions(+), 38 deletions(-) diff --git a/index.js b/index.js index 857445d..f853756 100644 --- a/index.js +++ b/index.js @@ -1,12 +1,5 @@ var _ = require('lodash'); -const relations = { - DISABLED: 0, - ORIGIN: 1, - HISTORY: 2 -} - -//TODO add options to keep no foreign key associations to historical to get associated models (add secondary option to select keep linked to current or to historical) var temporalDefaultOptions = { // runs the insert within the sequelize hook chain, disable // for increased performance @@ -102,38 +95,27 @@ var Temporal = function(model, sequelize, temporalOptions){ var beforeBulkSyncHook = function(options){ const allModels = sequelize.models; Object.keys(allModels).forEach(key => { - const model = allModels[key]; - if(!model.name.endsWith(temporalOptions.modelSuffix) && model.associations) { - Object.keys(model.associations).forEach(key => { - const source = model; - const association = model.associations[key]; + const source = allModels[key]; + const sourceHistName = source.name + temporalOptions.modelSuffix; + const sourceHist = allModels[sourceHistName]; + + if(!source.name.endsWith(temporalOptions.modelSuffix) && source.associations && temporalOptions.addAssociations == true && sourceHist) { + //adding associations from historical model to origin model's association + Object.keys(source.associations).forEach(key => { + const association = source.associations[key]; const target = association.target; - - const sourceHistName = source.name + temporalOptions.modelSuffix; - const sourceHist = allModels[sourceHistName]; - - const targetHistName = target.name + temporalOptions.modelSuffix; - const targetHist = allModels[targetHistName]; - const tableName = source.name + target.name; - - if(sourceHist.addAssociations == true) { - //TODO test with several associations - //TODO test with several associations to the same table i.e: addedBy, UpdatedBy - //TODO test with all 1.*, 1.1, *.* - - //adding associations from historical model to origin model's association - const assocName = association.associationType.charAt(0).toLowerCase() + association.associationType.substr(1); - sourceHist[assocName].apply(sourceHist, [target, association.options]); - - //adding associations between origin model and historical - source.hasMany(sourceHist, { foreignKey: source.primaryKeyField }); - sourceHist.belongsTo(source, { foreignKey: source.primaryKeyField }); - } - - sequelize.models[sourceHistName] = sourceHist; - sequelize.models[sourceHistName].sync(); - - }); + const assocName = association.associationType.charAt(0).toLowerCase() + association.associationType.substr(1); + sourceHist[assocName].apply(sourceHist, [target, association.options]); + + //TODO test with several associations to the same table i.e: addedBy, UpdatedBy + }); + + //adding associations between origin model and historical + source.hasMany(sourceHist, { foreignKey: source.primaryKeyField }); + sourceHist.belongsTo(source, { foreignKey: source.primaryKeyField }); + + sequelize.models[sourceHistName] = sourceHist; + sequelize.models[sourceHistName].sync(); } }); From 3e22b02deb700bc04e5a6980907a67df0536976f Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Mon, 22 Apr 2019 00:45:33 +0900 Subject: [PATCH 18/37] Updated tests --- test/test.js | 1465 +++++++++++++++++++++++++++++--------------------- 1 file changed, 867 insertions(+), 598 deletions(-) diff --git a/test/test.js b/test/test.js index 179e595..40b3892 100644 --- a/test/test.js +++ b/test/test.js @@ -1,627 +1,896 @@ -var Temporal = require('../'); -var Sequelize = require('sequelize'); -var chai = require("chai"); -var chaiAsPromised = require("chai-as-promised"); +const Temporal = require('../'); +const Sequelize = require('sequelize'); +const chai = require("chai"); +const chaiAsPromised = require("chai-as-promised"); +const fs = require('fs'); chai.use(chaiAsPromised); -var assert = chai.assert; -var eventually = assert.eventually; +const assert = chai.assert; +const eventually = assert.eventually; describe('Read-only API', function(){ - var sequelize, User, UserHistory, Creation, CreationHistory; + var sequelize; - function newDB(paranoid, options){ - suffix = options && options.modelSuffix ? options.modelSuffix : 'History'; - options = options || {}; - - sequelize = new Sequelize('', '', '', { - dialect: 'sqlite', - storage: __dirname + '/.test.sqlite' - }); - - //create 2 models and their historical models to test associations - User = sequelize.define('User', { - name: Sequelize.TEXT - }, {paranoid: paranoid || false}); - - Creation = sequelize.define('Creation', { - name: Sequelize.TEXT, - user: Sequelize.INTEGER, - }, {paranoid: paranoid || false}); - - //associate the 2 models together - User.hasMany(Creation, { foreignKey: 'user' }); - Creation.belongsTo(User, { foreignKey: 'user' }); - - //Temporalize - User = Temporal(User, sequelize, options); - Creation = Temporal(Creation, sequelize, options); + function newDB(paranoid, options){ + if(sequelize) { + sequelize.close(); + sequelize = null; + } + + const dbFile = __dirname + '/.test.sqlite'; + try { fs.unlinkSync(dbFile); } catch {}; + + sequelize = new Sequelize('', '', '', { + dialect: 'sqlite', + storage: dbFile, + logging: false//console.log + }); + + //Define origin models + const User = sequelize.define('User', { name: Sequelize.TEXT }, {paranoid: paranoid || false}); + const Creation = sequelize.define('Creation', { name: Sequelize.TEXT, user: Sequelize.INTEGER }, {paranoid: paranoid || false}); + const Tag = sequelize.define('Tag', { name: Sequelize.TEXT }, {paranoid: paranoid || false}); + const Event = sequelize.define('Event', { name: Sequelize.TEXT, creation: Sequelize.INTEGER }, {paranoid: paranoid || false}); + const CreationTag = sequelize.define('CreationTag', { creation: Sequelize.INTEGER, tag: Sequelize.INTEGER }, {paranoid: paranoid || false}); + + //Associate models + + //1.* + User.hasMany(Creation, { foreignKey: 'user' }); + Creation.belongsTo(User, { foreignKey: 'user' }); + + //1.1 + Event.belongsTo(Creation, { foreignKey: 'creation' }); + Creation.hasOne(Event, { foreignKey: 'creation' }); - UserHistory = sequelize.models['User' + suffix]; - CreationHistory = sequelize.models['Creation' + suffix]; - - return sequelize.sync({ force: true }); - } - - function freshDB(){ - return newDB(); - } - - function freshDBWithOriginRelations(){ - return newDB(false, { keepRelations: 1}); - } - - function freshDBWithHistoricalRelations(){ - return newDB(false, { keepRelations: 2}); - } - - function freshDBWithFullModeAndParanoid(){ - return newDB(true,{ full: true }); - } - - function freshDBWithSuffixEndingWithT(){ - return newDB(false, { modelSuffix: '_Hist'}); - } - - function freshDBWithSuffixEndingWithY(){ - return newDB(false, { modelSuffix: 'Memory'}); - } - - function freshDBWithSuffixEndingWithS(){ - return newDB(false, { modelSuffix: 'Pass'}); - } - - function assertCount(modelHistory, n, opts){ - // wrapped, chainable promise - return function(obj){ - return modelHistory.count(opts).then(function(count){ - assert.equal(n, count, "history entries") - return obj; - }); - } - } - - describe.only('Association Tests', function(){ + //*.* + Tag.belongsToMany(Creation, { through: CreationTag, foreignKey: 'creation', otherKey: 'tag' }); + Creation.belongsToMany(Tag, { through: CreationTag, foreignKey: 'tag', otherKey: 'creation' }); + + //Temporalize + Temporal(User, sequelize, options); + Temporal(Creation, sequelize, options); + Temporal(Tag, sequelize, options); + Temporal(Event, sequelize, options); + Temporal(CreationTag, sequelize, options); + + return sequelize.sync({force:true}); + } + + //Adding 3 tags, 2 creations, 2 events, 1 user + //each creation has 3 tags + //user has 2 creations + //creation has 1 event + //tags,crestions,user,events are renamed 3 times to generate 3 historical data + //1 tag is removed and re-added to a creation to create 1 historical entry in the CreationTags table + function dataCreate() + { + const tag = sequelize.models.Tag.create({ name: 'tag01' }).then( t => { + t.name = 'tag01 renamed'; + t.save(); + t.name = 'tag01 renamed twice'; + t.save(); + t.name = 'tag01 renamed three times'; + t.save(); + return t; + }); + + const tag2 = sequelize.models.Tag.create({ name: 'tag02' }).then( t => { + t.name = 'tag02 renamed'; + t.save(); + t.name = 'tag02 renamed twice'; + t.save(); + t.name = 'tag02 renamed three times'; + t.save(); + return t; + }); + + const tag3 = sequelize.models.Tag.create({ name: 'tag03' }).then( t => { + t.name = 'tag03 renamed'; + t.save(); + t.name = 'tag03 renamed twice'; + t.save(); + t.name = 'tag03 renamed three times'; + t.save(); + return t; + }); + + const user = sequelize.models.User.create({ name: 'user01' }).then( u => { + u.name = 'user01 renamed'; + u.save(); + u.name = 'user01 renamed twice'; + u.save(); + u.name = 'user01 renamed three times'; + u.save(); + return u; + }); + + const creation = user.then(u => sequelize.models.Creation.create({ name: 'creation01', user: u.id })) + .then( c => { + c.name = 'creation01 renamed'; + c.save(); + c.name = 'creation01 renamed twice'; + c.save(); + c.name = 'creation01 renamed three times'; + c.save(); + return c; + }); + + const creation2 = user.then(u => sequelize.models.Creation.create({ name: 'creation02', user: u.id })) + .then( c => { + c.name = 'creation02 renamed'; + c.save(); + c.name = 'creation02 renamed twice'; + c.save(); + c.name = 'creation02 renamed three times'; + c.save(); + return c; + }); + + const event = creation.then(c => sequelize.models.Event.create({ name: 'event01', creation: c.id })) + .then( e => { + e.name = 'event01 renamed'; + e.save(); + e.name = 'event01 renamed twice'; + e.save(); + e.name = 'event01 renamed three times'; + e.save(); + return e; + }); + + const event2 = creation2.then(c => sequelize.models.Event.create({ name: 'event02', creation: c.id })) + .then( e => { + e.name = 'event02 renamed'; + e.save(); + e.name = 'event02 renamed twice'; + e.save(); + e.name = 'event02 renamed three times'; + e.save(); + return e; + }); + + const creationTag1 = Promise.all([tag, creation]).then(models =>{ + const t = models[0]; + const c = models[1]; + + return c.addTag(t); + }); + + const creationTag1_rem = Promise.all([tag, creation,creationTag1]).then(models =>{ + const t = models[0]; + const c = models[1]; + + return c.removeTag(t); + }); + + const creationTag1_rea = Promise.all([tag, creation,creationTag1_rem]).then(models =>{ + const t = models[0]; + const c = models[1]; + + return c.addTag(t); + }); + + const creationTag2 = Promise.all([tag2, creation]).then(models =>{ + const t = models[0]; + const c = models[1]; + + return c.addTag(t); + }); + + const creationTag3 = Promise.all([tag3, creation]).then(models =>{ + const t = models[0]; + const c = models[1]; + + return c.addTag(t); + }); + + const creationTag4 = Promise.all([tag, creation2]).then(models =>{ + const t = models[0]; + const c = models[1]; + + return c.addTag(t); + }); + + const creationTag5 = Promise.all([tag2, creation2]).then(models =>{ + const t = models[0]; + const c = models[1]; + + return c.addTag(t); + }); + + const creationTag6 = Promise.all([tag3, creation2]).then(models =>{ + const t = models[0]; + const c = models[1]; + + return c.addTag(t); + }); + + return Promise.all([ + event, + event2, + tag, + tag2, + tag3, + user, + creation, + creation2, + creationTag1, + creationTag2, + creationTag3, + creationTag4, + creationTag5, + creationTag6, + creationTag1_rea, + creationTag1_rem + ]); + } + + function freshDB(){ + return newDB(); + } + + function freshDBWithAssociations(){ + return newDB(false, { addAssociations: true}); + } + + function freshDBWithFullModeAndParanoid(){ + return newDB(true,{ full: true }); + } + + function freshDBWithSuffixEndingWithT(){ + return newDB(false, { modelSuffix: '_Hist'}); + } + + function assertCount(modelHistory, n, opts) { + // wrapped, chainable promise + return function(obj) { + return modelHistory.count(opts).then((count) => { + //console.log('Asserting ', modelHistory.name, ' count: ', count, ' expected: ', n); + assert.equal(n, count, "history entries") + return obj; + }); + } + } + + describe.only('Association Tests', function() { describe('test there are no historical association', function(){ beforeEach(freshDB); - it('onCreate: should have relations for origin models but not for historical models' , function(){ - return User.create({ name: 'test' }).then( u => { - assert.exists(u.id); - u.name = 'test renamed'; - u.save(); - return Creation.create({ name: 'test', user: u.id }); - }).then(c => { - assert.exists(c.id); - c.name = 'creation renamed'; - c.save(); - return c.getUser(); - }).then( u => { - assert.exists(u.id); - assertCount(UserHistory, 1); - assertCount(CreationHistory, 1); - - return CreationHistory.findOne(); - }).then( c => { - assert.exists(c.id); - assert.isUndefined(c.getUser); + it('Should have relations for origin models but not for historical models' , function(){ + const init = dataCreate(); + + //Get User + const user = init.then(() => sequelize.models.User.findOne()); + + //User associations check + const userHistory = user.then(u =>{ + assert.notExists(u.getUserHistories, 'User: getUserHistories exists'); + return Promise.resolve('done'); + }); + + const creation = user.then(u =>{ + assert.exists(u.getCreations, 'User: getCreations does not exist'); + return u.getCreations(); + }); + + //Creation associations check + const creationHistory = creation.then(c =>{ + assert.equal(c.length, 2, 'User: should have found 2 creations'); + const first = c[0]; + assert.notExists(first.getCreationHistories, 'Creation: getCreationHistories exists'); + return Promise.resolve('done'); + }); + + const tag = creation.then(c =>{ + const first = c[0]; + assert.exists(first.getTags, 'Creation: getTags does not exist'); + return first.getTags(); + }); + + const event = creation.then(c =>{ + const first = c[0]; + assert.exists(first.getEvent, 'Creation: getEvent does not exist'); + return first.getEvent(); + }); + + const cUser = creation.then(c =>{ + const first = c[0]; + assert.exists(first.getUser, 'Creation: getUser does not exist'); + return first.getUser(); + }).then(cu => { + assert.exists(cu, 'Creation: did not find user'); + return Promise.resolve('done'); + }); + + //Tag associations check + const tagHistory = tag.then(t =>{ + assert.equal(t.length, 3, 'Creation: should have found 3 tags'); + const first = t[0]; + assert.notExists(first.getTagHistories, 'Tag: getTagHistories exists'); + return Promise.resolve('done'); }); - }); - }); - describe('test there are historical associations to origin models', function(){ - beforeEach(freshDBWithOriginRelations); - it('onCreate: should have relations for origin models and for historical models to origin' , function(){ - return User.create({ name: 'test' }).then( u => { - assert.exists(u.id); - u.name = 'test renamed'; - u.save(); - return Creation.create({ name: 'test', user: u.id }); - }).then(c => { - assert.exists(c.id); - c.name = 'creation renamed'; - c.save(); - return c.getUser(); - }).then( u => { - assert.exists(u.id); - assertCount(UserHistory, 1); - assertCount(CreationHistory, 1); - - return CreationHistory.findOne(); - }).then( c => { - assert.exists(c.id); - assert.exists(c.getUser); - assert.notExists(c.getUsers); - - return c.getUser(); - }).then( u => { - assert.exists(u.id); + const tCreation = tag.then(t =>{ + const first = t[0]; + assert.exists(first.getCreations, 'Tag: getCreations does not exist'); + return first.getCreations(); + }).then(tc => { + assert.equal(tc.length, 2, 'Tag: should have found 2 creations'); + return Promise.resolve('done'); + }); + + //Event associations check + const eventHistory = event.then(e =>{ + assert.exists(e, 'Creation: did not find event'); + assert.notExists(e.getEventHistories, 'Event: getEventHistories exist'); + return Promise.resolve('done'); }); + + const eCreation = event.then(e =>{ + assert.exists(e.getCreation); + return e.getCreation(); + }).then(ec => { + assert.exists(ec); + return Promise.resolve('done'); + }); + + //Check Historical data + const userHistories = init.then(assertCount(sequelize.models.UserHistory, 3)); + const creationHistories = init.then(assertCount(sequelize.models.CreationHistory, 6)); + const tagHistories = init.then(assertCount(sequelize.models.TagHistory, 9)); + const eventHistories = init.then(assertCount(sequelize.models.EventHistory, 6)); + const creationTagHistories = init.then(assertCount(sequelize.models.CreationTagHistory, 1)); + + return Promise.all([ + creation, + creationHistories, + creationHistory, + creationTagHistories, + cUser, + eCreation, + event, + eventHistories, + eventHistory, + init, + tag, + tagHistories, + tagHistory, + tCreation, + user, + userHistories, + userHistory + ]); }); }); - describe('test there are historical associations to historical models', function(){ - beforeEach(freshDBWithHistoricalRelations); - it('onCreate: should have relations for origin models and for historical models to historical' , function(){ - return User.create({ name: 'test' }).then( u => { - assert.exists(u.id); - u.name = 'test renamed'; - u.save(); - u.name = 'test renamed twice'; - u.save(); - u.name = 'test renamed three times'; - u.save(); - return Creation.create({ name: 'test', user: u.id }); - }).then(c => { - assert.exists(c.id); - c.name = 'creation renamed'; - c.save(); - return c.getUser(); - }).then( u => { - assert.exists(u.id); - assertCount(UserHistory, 3); - assertCount(CreationHistory, 1); - - return CreationHistory.findOne(); - }).then( c => { - assert.exists(c.id); - assert.notExists(c.getUser); - assert.exists(c.getUsers); - - return c.getUsers(); - }).then( u => { - assert.equal(u.length, 3); + describe.only('test there are associations are created between origin and historical', function(){ + beforeEach(freshDBWithAssociations); + it('Should have relations for origin models and for historical models to origin' , function(){ + const init = dataCreate(); + + //Get User + const user = init.then(() => sequelize.models.User.findOne()); + + //User associations check + const userHistory = user.then(u =>{ + assert.exists(u.getUserHistories, 'User: getUserHistories does not exist'); + return u.getUserHistories(); + }); + + const creation = user.then(u =>{ + assert.exists(u.getCreations, 'User: getCreations does not exist'); + return u.getCreations(); + }); + + //UserHistories associations check + const uhCreation = userHistory.then(uh =>{ + assert.equal(uh.length, 3, 'User: should have found 3 UserHistories'); + const first = uh[0]; + assert.exists(first.getCreations, 'UserHistory: getCreations does not exist'); + return first.getCreations(); + }).then(uhc => { + assert.equal(uhc.length, 2, 'UserHistory: should have found 2 creations'); + return Promise.resolve('done'); + }); + + const uhUser = userHistory.then(uh =>{ + const first = uh[0]; + assert.exists(first.getUser, 'UserHistory: getUser does not exist'); + return first.getUser(); + }).then(uhu => { + assert.exists(uhu, 'UserHistory: did not find a user'); + return Promise.resolve('done'); + }); + + //Creation associations check + const creationHistory = creation.then(c =>{ + assert.equal(c.length, 2, 'User: should have found 2 creations'); + const first = c[0]; + assert.exists(first.getCreationHistories, 'Creation: getCreationHistories does not exist'); + return first.getCreationHistories(); + }); + + const tag = creation.then(c =>{ + const first = c[0]; + assert.exists(first.getTags, 'Creation: getTags does not exist'); + return first.getTags(); }); + + const event = creation.then(c =>{ + const first = c[0]; + assert.exists(first.getEvent, 'Creation: getEvent does not exist'); + return first.getEvent(); + }); + + const cUser = creation.then(c =>{ + const first = c[0]; + assert.exists(first.getUser, 'Creation: getUser does not exist'); + return first.getUser(); + }).then(cu => { + assert.exists(cu, 'Creation: did not find a user'); + return Promise.resolve('done'); + }); + + //CreationHistories association check + const chCreation = creationHistory.then(ch =>{ + assert.equal(ch.length, 3, 'Creation: should have found 3 CreationHistories'); + const first = ch[0]; + assert.exists(first.getCreation, 'CreationHistory: getCreation does not exist'); + return first.getCreation(); + }).then(chc => { + assert.exists(chc, 'CreationHistory: did noy find a creation'); + return Promise.resolve('done'); + }); + + const chTag = creationHistory.then(ch =>{ + const first = ch[0]; + assert.exists(first.getTags, 'CreationHistory: getTags does not exist'); + return first.getTags(); + }).then(uht => { + assert.equal(uht.length, 3); + return Promise.resolve('done'); + }); + + const chUser = creationHistory.then(ch =>{ + const first = ch[0]; + assert.exists(first.getUser, 'CreationHistory: getUser does not exist'); + return first.getUser(); + }).then(chu => { + assert.exists(chu, 'CreationHistory: did not find a user'); + return Promise.resolve('done'); + }); + + const chEvent = creationHistory.then(ch =>{ + const first = ch[0]; + assert.exists(first.getEvent, 'CreationHistory: getEvent does not exist'); + return first.getEvent(); + }).then(che => { + assert.exists(che, 'CreationHistory: did not find an event'); + return Promise.resolve('done'); + }); + + + //Tag associations check + const tagHistory = tag.then(t =>{ + assert.equal(t.length, 3, 'Creation: should have found 3 tags'); + const first = t[0]; + assert.exists(first.getTagHistories, 'Tag: getTagHistories does not exist'); + return first.getTagHistories(); + }); + + const tCreation = tag.then(t =>{ + const first = t[0]; + assert.exists(first.getCreations, 'Tag: getCreations does not exist'); + return first.getCreations(); + }).then(tc => { + assert.equal(tc.length, 2, 'Tag: should have found 2 creations'); + return Promise.resolve('done'); + }); + + //TagHistories associations check + const thTag = tagHistory.then(th =>{ + assert.equal(th.length, 3, 'TagHistory: should have found 3 TagHistories'); + const first = th[0]; + assert.exists(first.getTag, 'TagHistory: getTag does not exist'); + return first.getTag(); + }).then(tht => { + assert.exists(tht, 'TagHistory: did not find a tag'); + return Promise.resolve('done'); + }); + + const thCreation = tagHistory.then(th =>{ + const first = th[0]; + assert.exists(first.getCreations, 'TagHistory: getCreations does not exist'); + return first.getCreations(); + }).then(thc => { + assert.equal(thc.length, 2, 'TagHistory: should have found 2 creations'); + return Promise.resolve('done'); + }); + + //Event associations check + const eventHistory = event.then(e =>{ + assert.exists(e, 'Creation: did not find an event'); + assert.exists(e.getEventHistories, 'Event: getEventHistories does not exist'); + return e.getEventHistories(); + }); + + const eCreation = event.then(e =>{ + assert.exists(e.getCreation, 'Event: getCreation does not exist'); + return e.getCreation(); + }).then(ec => { + assert.exists(ec, 'Event: did not find a creation'); + return Promise.resolve('done'); + }); + + //EventHistories associations check + const ehEvent = eventHistory.then(eh =>{ + assert.equal(eh.length, 3, 'Event: should have found 3 EventHistories'); + const first = eh[0]; + assert.exists(first.getEvent, 'EventHistories: getEvent does not exist'); + return first.getEvent(); + }).then(ehe => { + assert.exists(ehe, 'EventHistories: did not find an event'); + return Promise.resolve('done'); + }); + + const ehCreation = eventHistory.then(eh =>{ + const first = eh[0]; + assert.exists(first.getCreation, 'EventHistories: getCreation does not exist'); + return first.getCreation(); + }).then(ehc => { + assert.exists(ehc, 'EventHistories: did not find a creation'); + return Promise.resolve('done'); + }); + + //Check Historical data + const userHistories = init.then(assertCount(sequelize.models.UserHistory, 3)); + const creationHistories = init.then(assertCount(sequelize.models.CreationHistory, 6)); + const tagHistories = init.then(assertCount(sequelize.models.TagHistory, 9)); + const eventHistories = init.then(assertCount(sequelize.models.EventHistory, 6)); + const creationTagHistories = init.then(assertCount(sequelize.models.CreationTagHistory, 1)); + + + return Promise.all([ + chCreation, + chEvent, + chTag, + chUser, + creation, + creationHistories, + creationHistory, + creationTagHistories, + cUser, + eCreation, + event, + eventHistories, + eventHistory, + init, + tag, + tagHistories, + tagHistory, + tCreation, + thCreation, + thTag, + uhCreation, + uhUser, + user, + userHistories, + userHistory, + ehEvent, + ehCreation + ]); }); + }); + }); + + //these tests are the same as hooks since the results should not change, even with a different model name + //Only added is to test for the model name + describe('test suffix ending in T', function() { + beforeEach(freshDBWithSuffixEndingWithT); + it('onCreate: should not store the new version in history db' , function() { + return sequelize.models.User.create({ name: 'test' }) + .then(assertCount(sequelize.models.User_Hist, 0)); + }); + it('onUpdate/onDestroy: should save to the historyDB' , function() { + return sequelize.models.User.create() + .then(assertCount(sequelize.models.User_Hist,0)) + .then((user) => { + user.name = "foo"; + return user.save(); + }) + .then(assertCount(sequelize.models.User_Hist,1)) + .then(user => user.destroy()) + .then(assertCount(sequelize.models.User_Hist,2)); + }); + it('onUpdate: should store the previous version to the historyDB' , function() { + return sequelize.models.User.create({name: "foo"}) + .then(assertCount(sequelize.models.User_Hist,0)) + .then((user) => { + user.name = "bar"; + return user.save(); + }) + .then(assertCount(sequelize.models.User_Hist,1)) + .then(() => sequelize.models.User_Hist.findAll()) + .then((users) => { + assert.equal(users.length,1, "only one entry in DB"); + assert.equal(users[0].name, "foo", "previous entry saved"); + }) + .then(() => sequelize.models.User.findOne()) + .then((user) => user.destroy()) + .then(assertCount(sequelize.models.User_Hist,2)) + }); + it('onDelete: should store the previous version to the historyDB' , function() { + return sequelize.models.User.create({name: "foo"}) + .then(assertCount(sequelize.models.User_Hist,0)) + .then(user => user.destroy()) + .then(assertCount(sequelize.models.User_Hist,1)) + .then(() => sequelize.models.User_Hist.findAll()) + .then((users) => { + assert.equal(users.length,1, "only one entry in DB"); + assert.equal(users[0].name, "foo", "previous entry saved"); + }); }); }); - //these tests are the same as hooks since the results should not change, even with a different model name - //Only added is to test for the model name - describe('test suffix ending in T', function(){ - beforeEach(freshDBWithSuffixEndingWithT); - it('onCreate: should not store the new version in history db' , function(){ - return User.create({ name: 'test' }).then(assertCount(UserHistory, 0)); - }); - it('onUpdate/onDestroy: should save to the historyDB' , function(){ - return User.create() - .then(assertCount(UserHistory,0)) - .then(function(user){ - user.name = "foo"; - return user.save(); - }).then(assertCount(UserHistory,1)) - .then(function(user){ - return user.destroy(); - }).then(assertCount(UserHistory,2)) - }); - it('onUpdate: should store the previous version to the historyDB' , function(){ - return User.create({name: "foo"}) - .then(assertCount(UserHistory,0)) - .then(function(user){ - user.name = "bar"; - return user.save(); - }).then(assertCount(UserHistory,1)) - .then(function(){ - return UserHistory.findAll(); - }).then(function(users){ - assert.equal(users.length,1, "only one entry in DB"); - assert.equal(users[0].name, "foo", "previous entry saved"); - }).then(function(user){ - return User.findOne(); - }).then(function(user){ - return user.destroy(); - }).then(assertCount(UserHistory,2)) - }); - it('onDelete: should store the previous version to the historyDB' , function(){ - return User.create({name: "foo"}) - .then(assertCount(UserHistory,0)) - .then(function(user){ - return user.destroy(); - }).then(assertCount(UserHistory,1)) - .then(function(){ - return UserHistory.findAll(); - }).then(function(users){ - assert.equal(users.length,1, "only one entry in DB"); - assert.equal(users[0].name, "foo", "previous entry saved"); - }); + describe('hooks', function() { + beforeEach(freshDB); + it('onCreate: should not store the new version in history db' , function() { + return sequelize.models.User.create({ name: 'test' }) + .then(assertCount(sequelize.models.UserHistory, 0)); + }); + it('onUpdate/onDestroy: should save to the historyDB' , function() { + return sequelize.models.User.create() + .then(assertCount(sequelize.models.UserHistory,0)) + .then((user) => { + user.name = "foo"; + return user.save(); + }) + .then(assertCount(sequelize.models.UserHistory,1)) + .then(user => user.destroy()) + .then(assertCount(sequelize.models.UserHistory,2)) + }); + it('onUpdate: should store the previous version to the historyDB' , function() { + return sequelize.models.User.create({name: "foo"}) + .then(assertCount(sequelize.models.UserHistory,0)) + .then((user) => { + user.name = "bar"; + return user.save(); + }) + .then(assertCount(sequelize.models.UserHistory,1)) + .then(() => sequelize.models.UserHistory.findAll()) + .then((users) => { + assert.equal(users.length,1, "only one entry in DB"); + assert.equal(users[0].name, "foo", "previous entry saved"); + }).then(user => sequelize.models.User.findOne()) + .then(user => user.destroy()) + .then(assertCount(sequelize.models.UserHistory,2)) + }); + it('onDelete: should store the previous version to the historyDB' , function() { + return sequelize.models.User.create({name: "foo"}) + .then(assertCount(sequelize.models.UserHistory,0)) + .then(user => user.destroy()) + .then(assertCount(sequelize.models.UserHistory,1)) + .then(() => sequelize.models.UserHistory.findAll()) + .then((users) => { + assert.equal(users.length,1, "only one entry in DB"); + assert.equal(users[0].name, "foo", "previous entry saved"); + }); + }); }); - it('onCreate: check the model is using the custom suffix' , function(){ - return User.create({ name: 'test' }).then(function(){ - assert.equal(UserHistory.name, User.name + '_Hist'); + + describe('transactions', function() { + beforeEach(freshDB); + it('revert on failed transactions' , function() { + return sequelize.transaction() + .then((t) => { + var opts = {transaction: t}; + return sequelize.models.User.create({name: "not foo"},opts) + .then(assertCount(sequelize.models.UserHistory,0, opts)) + .then((user) => { + user.name = "foo"; + user.save(opts); + }) + .then(assertCount(sequelize.models.UserHistory,1, opts)) + .then(() => t.rollback()); + }) + .then(assertCount(sequelize.models.UserHistory,0)); }); }); - }); - - describe('test suffix ending in Y', function(){ - beforeEach(freshDBWithSuffixEndingWithY); - it('onCreate: should not store the new version in history db' , function(){ - return User.create({ name: 'test' }).then(assertCount(UserHistory, 0)); - }); - it('onUpdate/onDestroy: should save to the historyDB' , function(){ - return User.create() - .then(assertCount(UserHistory,0)) - .then(function(user){ - user.name = "foo"; - return user.save(); - }).then(assertCount(UserHistory,1)) - .then(function(user){ - return user.destroy(); - }).then(assertCount(UserHistory,2)) - }); - it('onUpdate: should store the previous version to the historyDB' , function(){ - return User.create({name: "foo"}) - .then(assertCount(UserHistory,0)) - .then(function(user){ - user.name = "bar"; - return user.save(); - }).then(assertCount(UserHistory,1)) - .then(function(){ - return UserHistory.findAll(); - }).then(function(users){ - assert.equal(users.length,1, "only one entry in DB"); - assert.equal(users[0].name, "foo", "previous entry saved"); - }).then(function(user){ - return User.findOne(); - }).then(function(user){ - return user.destroy(); - }).then(assertCount(UserHistory,2)) - }); - it('onDelete: should store the previous version to the historyDB' , function(){ - return User.create({name: "foo"}) - .then(assertCount(UserHistory,0)) - .then(function(user){ - return user.destroy(); - }).then(assertCount(UserHistory,1)) - .then(function(){ - return UserHistory.findAll(); - }).then(function(users){ - assert.equal(users.length,1, "only one entry in DB"); - assert.equal(users[0].name, "foo", "previous entry saved"); - }); + + describe('bulk update', function() { + beforeEach(freshDB); + it('should archive every entry', function() { + return sequelize.models.User.bulkCreate([{name: "foo1"},{name: "foo2"}]) + .then(assertCount(sequelize.models.UserHistory,0)) + .then(() => sequelize.models.User.update({ name: 'updated-foo' }, {where: {}})) + .then(assertCount(sequelize.models.UserHistory,2)) + }); + it('should revert under transactions', function() { + return sequelize.transaction() + .then(function(t) { + var opts = {transaction: t}; + return sequelize.models.User.bulkCreate([{name: "foo1"},{name: "foo2"}], opts) + .then(assertCount(sequelize.models.UserHistory,0,opts)) + .then(() => sequelize.models.User.update({ name: 'updated-foo' }, {where: {}, transaction: t})) + .then(assertCount(sequelize.models.UserHistory,2, opts)) + .then(() => t.rollback()); + }) + .then(assertCount(sequelize.models.UserHistory,0)); + }); }); - it('onCreate: check the model is using the custom suffix' , function(){ - return User.create({ name: 'test' }).then(function(){ - assert.equal(UserHistory.name, User.name + 'Memory'); + + describe('bulk destroy/truncate', function() { + beforeEach(freshDB); + it('should archive every entry', function() { + return sequelize.models.User.bulkCreate([{name: "foo1"},{name: "foo2"}]) + .then(assertCount(sequelize.models.UserHistory,0)) + .then(() => sequelize.models.User.destroy({ + where: {}, + truncate: true // truncate the entire table + })) + .then(assertCount(sequelize.models.UserHistory,2)) + }); + it('should revert under transactions', function() { + return sequelize.transaction() + .then((t) => { + var opts = {transaction: t}; + return sequelize.models.User.bulkCreate([{name: "foo1"},{name: "foo2"}], opts) + .then(assertCount(sequelize.models.UserHistory,0,opts)) + .then(() => sequelize.models.User.destroy({ + where: {}, + truncate: true, // truncate the entire table + transaction: t + })) + .then(assertCount(sequelize.models.UserHistory,2, opts)) + .then(() => t.rollback()); + }) + .then(assertCount(sequelize.models.UserHistory,0)); + }); + }); + + describe('bulk destroy/truncate with associations', function() { + beforeEach(freshDBWithAssociations); + it('should archive every entry', function() { + return dataCreate() + .then(assertCount(sequelize.models.UserHistory,3)) + .then(() => sequelize.models.User.destroy({ + where: {}, + truncate: true // truncate the entire table + })) + .then(assertCount(sequelize.models.UserHistory,6)) + .then(() => sequelize.models.User.findOne()) + .then(u => u.getUserHistories()) + .then(uh => assert.exists(uh, 'The truncation did not break the associations')) + .catch(err => assert.exists(err,'The truncation broke the associations')); + }); + it('should fail to truncate', function() { + return dataCreate() + .then(() => sequelize.transaction()) + .then((t) => { + var opts = {transaction: t}; + assertCount(sequelize.models.UserHistory,3,opts); + return sequelize.models.User.destroy({ + where: {}, + truncate: true, // truncate the entire table + transaction: t + }) + .then(assertCount(sequelize.models.UserHistory,6, opts)) + .then(() => t.rollback()) + .catch(err => assert.exists(err)); + }) + .then(assertCount(sequelize.models.UserHistory,3)); }); }); - }); - - describe('test suffix ending in S', function(){ - beforeEach(freshDBWithSuffixEndingWithS); - it('onCreate: should not store the new version in history db' , function(){ - return User.create({ name: 'test' }).then(assertCount(UserHistory, 0)); - }); - it('onUpdate/onDestroy: should save to the historyDB' , function(){ - return User.create() - .then(assertCount(UserHistory,0)) - .then(function(user){ - user.name = "foo"; - return user.save(); - }).then(assertCount(UserHistory,1)) - .then(function(user){ - return user.destroy(); - }).then(assertCount(UserHistory,2)) - }); - it('onUpdate: should store the previous version to the historyDB' , function(){ - return User.create({name: "foo"}) - .then(assertCount(UserHistory,0)) - .then(function(user){ - user.name = "bar"; - return user.save(); - }).then(assertCount(UserHistory,1)) - .then(function(){ - return UserHistory.findAll(); - }).then(function(users){ - assert.equal(users.length,1, "only one entry in DB"); - assert.equal(users[0].name, "foo", "previous entry saved"); - }).then(function(user){ - return User.findOne(); - }).then(function(user){ - return user.destroy(); - }).then(assertCount(UserHistory,2)) - }); - it('onDelete: should store the previous version to the historyDB' , function(){ - return User.create({name: "foo"}) - .then(assertCount(UserHistory,0)) - .then(function(user){ - return user.destroy(); - }).then(assertCount(UserHistory,1)) - .then(function(){ - return UserHistory.findAll(); - }).then(function(users){ - assert.equal(users.length,1, "only one entry in DB"); - assert.equal(users[0].name, "foo", "previous entry saved"); - }); + + describe('read-only ', function() { + beforeEach(freshDB); + it('should forbid updates' , function() { + var userUpdate = sequelize.models.UserHistory.create({name: 'bla00'}) + .then((uh) => uh.update({name: 'bla'})); + + return assert.isRejected(userUpdate, Error, "Validation error"); + }); + it('should forbid deletes' , function() { + var userUpdate = sequelize.models.UserHistory.create({name: 'bla00'}) + .then(uh => uh.destroy()); + + return assert.isRejected(userUpdate, Error, "Validation error"); + }); }); - it('onCreate: check the model is using the custom suffix' , function(){ - return User.create({ name: 'test' }).then(function(){ - assert.equal(UserHistory.name, User.name + 'Pass'); + + describe('interference with the original model', function() { + beforeEach(freshDB); + it('shouldn\'t delete instance methods' , function() { + Fruit = Temporal(sequelize.define('Fruit', { name: Sequelize.TEXT }), sequelize); + Fruit.prototype.sayHi = () => { return 2; } + + return sequelize.sync() + .then(() => Fruit.create()) + .then((f) => { + assert.isFunction(f.sayHi); + assert.equal(f.sayHi(), 2); + }); + }); + + it('shouldn\'t interfere with hooks of the model' , function() { + var triggered = 0; + Fruit = Temporal(sequelize.define('Fruit', { name: Sequelize.TEXT }, { hooks:{ beforeCreate: function(){ triggered++; }}}), sequelize); + return sequelize.sync() + .then(() => Fruit.create()) + .then((f) => assert.equal(triggered, 1,"hook trigger count")); + }); + + it('shouldn\'t interfere with setters' , function() { + var triggered = 0; + Fruit = Temporal(sequelize.define('Fruit', { + name: { + type: Sequelize.TEXT, + set: function() { triggered++; } + } + }), sequelize); + return sequelize.sync() + .then(() => Fruit.create({name: "apple"})) + .then((f) => assert.equal(triggered, 1,"hook trigger count")); }); }); - }); - - describe('hooks', function(){ - beforeEach(freshDB); - it('onCreate: should not store the new version in history db' , function(){ - return User.create({ name: 'test' }).then(assertCount(UserHistory, 0)); - }); - it('onUpdate/onDestroy: should save to the historyDB' , function(){ - return User.create() - .then(assertCount(UserHistory,0)) - .then(function(user){ - user.name = "foo"; - return user.save(); - }).then(assertCount(UserHistory,1)) - .then(function(user){ - return user.destroy(); - }).then(assertCount(UserHistory,2)) - }); - it('onUpdate: should store the previous version to the historyDB' , function(){ - return User.create({name: "foo"}) - .then(assertCount(UserHistory,0)) - .then(function(user){ - user.name = "bar"; - return user.save(); - }).then(assertCount(UserHistory,1)) - .then(function(){ - return UserHistory.findAll(); - }).then(function(users){ - assert.equal(users.length,1, "only one entry in DB"); - assert.equal(users[0].name, "foo", "previous entry saved"); - }).then(function(user){ - return User.findOne(); - }).then(function(user){ - return user.destroy(); - }).then(assertCount(UserHistory,2)) - }); - it('onDelete: should store the previous version to the historyDB' , function(){ - return User.create({name: "foo"}) - .then(assertCount(UserHistory,0)) - .then(function(user){ - return user.destroy(); - }).then(assertCount(UserHistory,1)) - .then(function(){ - return UserHistory.findAll(); - }).then(function(users){ - assert.equal(users.length,1, "only one entry in DB"); - assert.equal(users[0].name, "foo", "previous entry saved"); - }); - }); - }); - - describe('transactions', function(){ - beforeEach(freshDB); - it('revert on failed transactions' , function(){ - return sequelize.transaction().then(function(t){ - var opts = {transaction: t}; - return User.create(opts) - .then(assertCount(UserHistory,0, opts)) - .then(function(user){ - user.name = "foo"; - return user.save(opts); - }).then(assertCount(UserHistory,1, opts)) - .then(function(){ - t.rollback(); - }); - }).then(assertCount(UserHistory,0)); - }); - }); - - describe('bulk update', function(){ - beforeEach(freshDB); - it('should archive every entry' , function(){ - return User.bulkCreate([ - {name: "foo1"}, - {name: "foo2"}, - ]).then(assertCount(UserHistory,0)) - .then(function(){ - return User.update({ name: 'updated-foo' }, {where: {}}); - }).then(assertCount(UserHistory,2)) - }); - it('should revert under transactions' , function(){ - return sequelize.transaction().then(function(t){ - var opts = {transaction: t}; - return User.bulkCreate([ - {name: "foo1"}, - {name: "foo2"}, - ], opts).then(assertCount(UserHistory,0,opts)) - .then(function(){ - return User.update({ name: 'updated-foo' }, {where: {}, transaction: t}); - }).then(assertCount(UserHistory,2, opts)) - .then(function(){ - t.rollback(); - }); - }).then(assertCount(UserHistory,0)); - }); - - }); - - describe('bulk destroy/truncate', function(){ - beforeEach(freshDB); - it('should archive every entry' , function(){ - return User.bulkCreate([ - {name: "foo1"}, - {name: "foo2"}, - ]).then(assertCount(UserHistory,0)) - .then(function(){ - return User.destroy({ - where: {}, - truncate: true // truncate the entire table - }); - }).then(assertCount(UserHistory,2)) - }); - it('should revert under transactions' , function(){ - return sequelize.transaction().then(function(t){ - var opts = {transaction: t}; - return User.bulkCreate([ - {name: "foo1"}, - {name: "foo2"}, - ], opts).then(assertCount(UserHistory,0,opts)) - .then(function(){ - return User.destroy({ - where: {}, - truncate: true, // truncate the entire table - transaction: t - }); - }).then(assertCount(UserHistory,2, opts)) - .then(function(){ - t.rollback(); - }); - }).then(assertCount(UserHistory,0)); - }); - - - }); - - describe('read-only ', function(){ - it('should forbid updates' , function(){ - var userUpdate = UserHistory.create().then(function(uh){ - uh.update({name: 'bla'}); - }); - return assert.isRejected(userUpdate, Error, "Validation error"); - }); - it('should forbid deletes' , function(){ - var userUpdate = UserHistory.create().then(function(uh){ - uh.destroy(); - }); - return assert.isRejected(userUpdate, Error, "Validation error"); - }); - }); - - describe('interference with the original model', function(){ - - beforeEach(freshDB); - - it('shouldn\'t delete instance methods' , function(){ - Fruit = Temporal(sequelize.define('Fruit', { - name: Sequelize.TEXT - }), sequelize); - Fruit.prototype.sayHi = function(){ return 2;} - return sequelize.sync().then(function(){ - return Fruit.create(); - }).then(function(f){ - assert.isFunction(f.sayHi); - assert.equal(f.sayHi(), 2); - }); - }); - - it('shouldn\'t interfere with hooks of the model' , function(){ - var triggered = 0; - Fruit = Temporal(sequelize.define('Fruit', { - name: Sequelize.TEXT - }, { - hooks:{ - beforeCreate: function(){ triggered++;} - } - }), sequelize); - return sequelize.sync().then(function(){ - return Fruit.create(); - }).then(function(f){ - assert.equal(triggered, 1,"hook trigger count"); - }); - }); - - it('shouldn\'t interfere with setters' , function(){ - var triggered = 0; - Fruit = Temporal(sequelize.define('Fruit', { - name: { - type: Sequelize.TEXT, - set: function(){ - triggered++; - } - } - }), sequelize); - return sequelize.sync().then(function(){ - return Fruit.create({name: "apple"}); - }).then(function(f){ - assert.equal(triggered, 1,"hook trigger count"); - }); - }); - - }); - - describe('full mode', function() { - - beforeEach(freshDBWithFullModeAndParanoid); - - it('onCreate: should store the new version in history db' , function(){ - return User.create({ name: 'test' }) - .then(function() { - return UserHistory.findAll(); - }) - .then(function(histories) { - assert.equal(1, histories.length); - assert.equal('test', histories[0].name); - }); - }); - - it('onUpdate: should store the new version to the historyDB' , function(){ - return User.create({ name: 'test' }) - .then(function(user) { - return user.update({ name: 'renamed' }); - }) - .then(function() { - return UserHistory.findAll(); - }) - .then(function(histories) { - assert.equal(histories.length, 2, 'two entries in DB'); - assert.equal(histories[0].name, 'test', 'first version saved'); - assert.equal(histories[1].name, 'renamed', 'second version saved'); - }); - }); - - it('onDelete: should store the previous version to the historyDB' , function(){ - return User.create({ name: 'test' }) - .then(function(user) { - return user.update({ name: 'renamed' }); - }) - .then(function(user) { - return user.destroy(); - }) - .then(function() { - return UserHistory.findAll(); - }) - .then(function(histories) { - assert.equal(histories.length, 3, 'three entries in DB'); - assert.equal(histories[0].name, 'test', 'first version saved'); - assert.equal(histories[1].name, 'renamed', 'second version saved'); - assert.notEqual(histories[2].deletedAt, null, 'deleted version saved'); - }); - }); - - it('onRestore: should store the new version to the historyDB' , function(){ - return User.create({ name: 'test' }) - .then(function(user) { - return user.destroy(); - }) - .then(function(user) { - return user.restore(); - }) - .then(function() { - return UserHistory.findAll(); - }) - .then(function(histories) { - assert.equal(histories.length, 3, 'three entries in DB'); - assert.equal(histories[0].name, 'test', 'first version saved'); - assert.notEqual(histories[1].deletedAt, null, 'deleted version saved'); - assert.equal(histories[2].deletedAt, null, 'restored version saved'); - }); - }); - - it('should revert on failed transactions, even when using after hooks' , function(){ - return sequelize.transaction() - .then(function(transaction) { - var options = { transaction: transaction }; - - return User.create({ name: 'test' }, options) - .then(function(user) { - return user.destroy(options); - }) - .then(assertCount(UserHistory, 2, options)) - .then(function() { - return transaction.rollback() - }); - }) - .then(assertCount(UserHistory,0)); - }); - }); + describe('full mode', function() { + beforeEach(freshDBWithFullModeAndParanoid); + it('onCreate: should store the new version in history db' , function() { + return sequelize.models.User.create({ name: 'test' }) + .then(() => sequelize.models.UserHistory.findAll()) + .then((histories) => { + assert.equal(1, histories.length); + assert.equal('test', histories[0].name); + }); + }); + + it('onUpdate: should store the new version to the historyDB' , function() { + return sequelize.models.User.create({ name: 'test' }) + .then(user => user.update({ name: 'renamed' })) + .then(() => sequelize.models.UserHistory.findAll()) + .then((histories) => { + assert.equal(histories.length, 2, 'two entries in DB'); + assert.equal(histories[0].name, 'test', 'first version saved'); + assert.equal(histories[1].name, 'renamed', 'second version saved'); + }); + }); + + it('onDelete: should store the previous version to the historyDB' , function() { + return sequelize.models.User.create({ name: 'test' }) + .then(user => user.update({ name: 'renamed' })) + .then(user=> user.destroy()) + .then(() => sequelize.models.UserHistory.findAll()) + .then((histories) => { + assert.equal(histories.length, 3, 'three entries in DB'); + assert.equal(histories[0].name, 'test', 'first version saved'); + assert.equal(histories[1].name, 'renamed', 'second version saved'); + assert.notEqual(histories[2].deletedAt, null, 'deleted version saved'); + }); + }); + + it('onRestore: should store the new version to the historyDB' , function() { + return sequelize.models.User.create({ name: 'test' }) + .then(user => user.destroy()) + .then(user => user.restore()) + .then(() => sequelize.models.UserHistory.findAll()) + .then((histories) => { + assert.equal(histories.length, 3, 'three entries in DB'); + assert.equal(histories[0].name, 'test', 'first version saved'); + assert.notEqual(histories[1].deletedAt, null, 'deleted version saved'); + assert.equal(histories[2].deletedAt, null, 'restored version saved'); + }); + }); + + it('should revert on failed transactions, even when using after hooks' , function(){ + return sequelize.transaction() + .then((transaction) => { + var options = { transaction: transaction }; + + return sequelize.models.User.create({ name: 'test' }, options) + .then(user => user.destroy(options)) + .then(assertCount(sequelize.models.UserHistory, 2, options)) + .then(() => transaction.rollback()); + }) + .then(assertCount(sequelize.models.UserHistory,0)); + }); + }); }); From 4cefc56e2c2555be02110ab74b19c3a2e4f821a2 Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Mon, 22 Apr 2019 22:45:19 +0900 Subject: [PATCH 19/37] Before Lodash Upgrade --- index.js | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index f853756..7c00afe 100644 --- a/index.js +++ b/index.js @@ -14,6 +14,8 @@ var excludeAttributes = function(obj, attrsToExclude){ return _.omit(obj, _.partial(_.rearg(_.contains,0,2,1), attrsToExclude)); } + + var Temporal = function(model, sequelize, temporalOptions){ temporalOptions = _.extend({},temporalDefaultOptions, temporalOptions); @@ -75,7 +77,7 @@ var Temporal = function(model, sequelize, temporalOptions){ if(!options.individualHooks){ var queryAll = model.findAll({where: options.where, transaction: options.transaction}).then(function(hits){ if(hits){ - hits = _.pluck(hits, 'dataValues'); + hits = _.map(hits, 'dataValues'); return modelHistory.bulkCreate(hits, {transaction: options.transaction}); } }); @@ -93,26 +95,46 @@ var Temporal = function(model, sequelize, temporalOptions){ var beforeBulkSyncHook = function(options){ - const allModels = sequelize.models; + const customizer = function (objValue, srcValue, key, obj, src) { + if(key == 'hid') + obj.primaryKey = false; + else + obj.autoIncrement = false; + + return obj; + } + + const allModels = sequelize.models; + const mergePKs = _.partialRight(_.assignWith, customizer); + Object.keys(allModels).forEach(key => { const source = allModels[key]; const sourceHistName = source.name + temporalOptions.modelSuffix; const sourceHist = allModels[sourceHistName]; if(!source.name.endsWith(temporalOptions.modelSuffix) && source.associations && temporalOptions.addAssociations == true && sourceHist) { + const pkfield = source.primaryKeyField; //adding associations from historical model to origin model's association Object.keys(source.associations).forEach(key => { const association = source.associations[key]; const target = association.target; - const assocName = association.associationType.charAt(0).toLowerCase() + association.associationType.substr(1); + const assocName = association.associationType.charAt(0).toLowerCase() + association.associationType.substr(1); + + //handle premary keys for belongsToMany + if(assocName == 'belongsToMany') { + sourceHist.primaryKeys = mergePKs({}, sourceHist.primaryKeys, source.primaryKeys); + sourceHist.primaryKeys.hid.primaryKey = false; + sourceHist.primaryKeys[pkfield].autoIncrement = false; + } + sourceHist[assocName].apply(sourceHist, [target, association.options]); //TODO test with several associations to the same table i.e: addedBy, UpdatedBy }); //adding associations between origin model and historical - source.hasMany(sourceHist, { foreignKey: source.primaryKeyField }); - sourceHist.belongsTo(source, { foreignKey: source.primaryKeyField }); + source.hasMany(sourceHist, { foreignKey: pkfield }); + sourceHist.belongsTo(source, { foreignKey: pkfield }); sequelize.models[sourceHistName] = sourceHist; sequelize.models[sourceHistName].sync(); From 9e5f15b4baf162df3bc246e3fd82ebf8d4708365 Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Tue, 23 Apr 2019 00:58:51 +0900 Subject: [PATCH 20/37] All test + upgrades + All tests passing + Updated Lodash + Updated Mocha --- index.js | 34 ++++++++-------------------------- package.json | 4 ++-- test/test.js | 8 ++++---- 3 files changed, 14 insertions(+), 32 deletions(-) diff --git a/index.js b/index.js index 7c00afe..5aa1765 100644 --- a/index.js +++ b/index.js @@ -9,14 +9,7 @@ var temporalDefaultOptions = { addAssociations: false }; -var excludeAttributes = function(obj, attrsToExclude){ - // fancy way to exclude attributes - return _.omit(obj, _.partial(_.rearg(_.contains,0,2,1), attrsToExclude)); -} - - - -var Temporal = function(model, sequelize, temporalOptions){ +var Temporal = function(model, sequelize, temporalOptions) { temporalOptions = _.extend({},temporalDefaultOptions, temporalOptions); var Sequelize = sequelize.Sequelize; @@ -39,7 +32,7 @@ var Temporal = function(model, sequelize, temporalOptions){ var excludedAttributes = ["Model","unique","primaryKey","autoIncrement", "set", "get", "_modelAttribute"]; var historyAttributes = _(model.rawAttributes).mapValues(function(v){ - v = excludeAttributes(v, excludedAttributes); + v = _.omit(v, excludedAttributes); // remove the "NOW" defaultValue for the default timestamps // we want to save them, but just a copy from our master record if(v.fieldName == "createdAt" || v.fieldName == "updatedAt"){ @@ -54,7 +47,7 @@ var Temporal = function(model, sequelize, temporalOptions){ timestamps: false }; var excludedNames = ["name", "tableName", "sequelize", "uniqueKeys", "hasPrimaryKey", "hooks", "scopes", "instanceMethods", "defaultScope"]; - var modelOptions = excludeAttributes(model.options, excludedNames); + var modelOptions = _.omit(model.options, excludedNames); var historyOptions = _.assign({}, modelOptions, historyOwnOptions); // We want to delete indexes that have unique constraint @@ -94,18 +87,8 @@ var Temporal = function(model, sequelize, temporalOptions){ } - var beforeBulkSyncHook = function(options){ - const customizer = function (objValue, srcValue, key, obj, src) { - if(key == 'hid') - obj.primaryKey = false; - else - obj.autoIncrement = false; - - return obj; - } - + var beforeBulkSyncHook = function(options){ const allModels = sequelize.models; - const mergePKs = _.partialRight(_.assignWith, customizer); Object.keys(allModels).forEach(key => { const source = allModels[key]; @@ -121,12 +104,11 @@ var Temporal = function(model, sequelize, temporalOptions){ const assocName = association.associationType.charAt(0).toLowerCase() + association.associationType.substr(1); //handle premary keys for belongsToMany - if(assocName == 'belongsToMany') { - sourceHist.primaryKeys = mergePKs({}, sourceHist.primaryKeys, source.primaryKeys); - sourceHist.primaryKeys.hid.primaryKey = false; - sourceHist.primaryKeys[pkfield].autoIncrement = false; + if(assocName == 'belongsToMany') { + sourceHist.primaryKeys = _.forEach(source.primaryKeys, (x) => x.autoIncrement = false); + sourceHist.primaryKeyField = Object.keys(sourceHist.primaryKeys)[0]; } - + sourceHist[assocName].apply(sourceHist, [target, association.options]); //TODO test with several associations to the same table i.e: addedBy, UpdatedBy diff --git a/package.json b/package.json index 8eb4687..139edb7 100644 --- a/package.json +++ b/package.json @@ -29,12 +29,12 @@ }, "homepage": "https://github.com/opencollective/sequelize-historical#readme", "dependencies": { - "lodash": "^3.10.1" + "lodash": "^4.17.11" }, "devDependencies": { "chai": "^4.2.0", "chai-as-promised": "^7.1.1", - "mocha": "^6.1.3", + "mocha": "^6.1.4", "sequelize": "^5.4.0", "sqlite3": "^4.0.6" } diff --git a/test/test.js b/test/test.js index 40b3892..f1cc9d6 100644 --- a/test/test.js +++ b/test/test.js @@ -43,8 +43,8 @@ describe('Read-only API', function(){ Creation.hasOne(Event, { foreignKey: 'creation' }); //*.* - Tag.belongsToMany(Creation, { through: CreationTag, foreignKey: 'creation', otherKey: 'tag' }); - Creation.belongsToMany(Tag, { through: CreationTag, foreignKey: 'tag', otherKey: 'creation' }); + Tag.belongsToMany(Creation, { through: CreationTag, foreignKey: 'tag', otherKey: 'creation' }); + Creation.belongsToMany(Tag, { through: CreationTag, foreignKey: 'creation', otherKey: 'tag' }); //Temporalize Temporal(User, sequelize, options); @@ -251,7 +251,7 @@ describe('Read-only API', function(){ } } - describe.only('Association Tests', function() { + describe('Association Tests', function() { describe('test there are no historical association', function(){ beforeEach(freshDB); it('Should have relations for origin models but not for historical models' , function(){ @@ -361,7 +361,7 @@ describe('Read-only API', function(){ }); }); - describe.only('test there are associations are created between origin and historical', function(){ + describe('test there are associations are created between origin and historical', function(){ beforeEach(freshDBWithAssociations); it('Should have relations for origin models and for historical models to origin' , function(){ const init = dataCreate(); From e7ba226d9d1bf47407cd1d49d2517d3727e5049b Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Wed, 24 Apr 2019 01:25:29 +0900 Subject: [PATCH 21/37] Updated MD file --- README.md | 117 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 85 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 244a4a2..a4fabd8 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,42 @@ -Historical tables for Sequelize +Record tables for Sequelize =============================== -Warning: this is a fork of [sequelize-temporal](https://github.com/bonaval/sequelize-temporal) that adds support for Sequelize 5. +Warning: this is a fork of [sequelize-temporal](https://github.com/opencollective/sequelize-historical) that adds the ability to associate data history table to origin tables (table a Record table is based on) and to specify a different name for the __Record__ tables. -[![Build Status](https://travis-ci.org/opencollective/sequelize-historical.svg?branch=master)](https://travis-ci.org/opencollective/sequelize-historical) [![Dependency Status](https://david-dm.org/opencollective/sequelize-historical.svg)](https://david-dm.org/opencollective/sequelize-historical) [![NPM version](https://img.shields.io/npm/v/sequelize-historical.svg)](https://www.npmjs.com/package/sequelize-historical) [![Greenkeeper badge](https://badges.greenkeeper.io/opencollective/sequelize-historical.svg)](https://greenkeeper.io/) +[![Build Status](https://travis-ci.org/kurisutofu/sequelize-record.svg?branch=master)](https://travis-ci.org/opencollective/sequelize-record) [![Dependency Status](https://david-dm.org/kurisutofu/sequelize-record.svg)](https://david-dm.org/opencollective/sequelize-record) [![NPM version](https://img.shields.io/npm/v/sequelize-record.svg)](https://www.npmjs.com/package/sequelize-record) [![Greenkeeper badge](https://badges.greenkeeper.io/kurisutofu/sequelize-record.svg)](https://greenkeeper.io/) What is it? ----------- -Historical tables maintain __historical versions__ of data. Modifying operations (UPDATE, DELETE) on these tables don't cause permanent changes to entries, but create new versions of them. Hence this might be used to: +___Record__ tables maintain __previous values__ of data. Modifying operations (UPDATE, DELETE) on these tables don't cause permanent changes to entries, but create new versions of them. Hence this might be used to: - log changes (security/auditing) - undo functionalities - track interactions (customer support) -Under the hood a history table with the same structure, but without constraints is created. +Under the hood a record table with the same structure, but without constraints is created (unless option __addAssociation__ is set to __true__). The normal singular/plural naming scheme in Sequelize is used: -- model name: `modelName + History` -- table name: `modelName + Histories` +- model name: `modelName + Record` +- table name: `modelName + Records` Installation ------------ ``` -npm install sequelize-historical +npm install sequelize-record ``` How to use ---------- -### 1) Import `sequelize-historical` +### 1) Import `sequelize-record` ``` var Sequelize = require('sequelize'); -var Historical = require('sequelize-historical'); +var Record = require('sequelize-record'); ``` Create a sequelize instance and your models, e.g. @@ -48,68 +48,121 @@ var sequelize = new Sequelize('', '', '', { }); ``` -### 2) Add the *historical* feature to your models +### 2) Add the *record* feature to your models ``` -var User = Historical(sequelize.define('User'), sequelize); +var User = Record(sequelize.define('User'), sequelize); ``` -The output of `historical` is its input model, so assigning it's output to your +The output of `Record` is its input model, so assigning it's output to your Model is not necessary, hence it's just the lazy version of: ``` -var User = sequelize.define('User', {.types.}, {.options.}); // Sequelize Docu -Historical(User, sequelize); +var User = sequelize.define('User', {.types.}, {.options.}); //Vanilla Sequelize +Record(User, sequelize); ``` Options ------- -The default syntax for `Historical` is: +The default syntax for `Record` is: -`Historical(model, sequelizeInstance, options)` +`Record(model, sequelizeInstance, options)` whereas the options are listed here (with default value). ```js { - /* runs the insert within the sequelize hook chain, disable + /* + Runs the insert within the sequelize hook chain, disable for increased performance without warranties */ blocking: true, - /* By default sequelize-historical persist only changes, and saves the previous state in the history table. - The "full" option saves all transactions into the historical database + /* + By default sequelize-record persist only changes, and saves the previous state in the record table. + The "full" option saves all transactions into the record database (i.e. this includes the latest state.) - This allows to only query the history table to get the full history of an entity. + This allows to only query the record table to get the full record of an entity. */ full: false, - /* By default sequelize-historical will add 'History' to the historical Model name and 'Histories' to the historical table. + /* + By default sequelize-record will add 'Record' to the record Model name and 'Records' to the record table. By updating the modelSuffix value, you can decide what the naming will be. - The value will be appended to the historical Model name and its plural will be appended to the historical tablename. + The value will be appended to the record Model name and its plural will be appended to the record tablename. examples for table User: - modelSuffix: '_Hist' --> Historical Model Name: User_Hist --> Historical Table Name: User_Hists - modelSuffix: 'Memory' --> Historical Model Name: UserMemory --> Historical Table Name: UserMemories - modelSuffix: 'Pass' --> Historical Model Name: UserPass --> Historical Table Name: UserPasses + modelSuffix: '_Hist' --> Record Model Name: User_Hist --> Record Table Name: User_Hists + modelSuffix: 'Memory' --> Record Model Name: UserMemory --> Record Table Name: UserMemories + modelSuffix: 'Pass' --> Record Model Name: UserPass --> Record Table Name: UserPasses */ - modelSuffix: 'History' + modelSuffix: 'Record', + /* + By default sequelize-record will create the record table without associations. + However, setting this flag to true, you can keep association between the record table and the table with the latest value (origin). + + example for table User: + model: 'User' + record model: 'UserRecords' + --> This would add function User.getUserRecords() to return all record entries for that user entry. + --> This would add function UserRecords.getUser() to get the original user from an record. + + If a model has associations, those would be mirrored to the record table. + Origin model can only get its own records. + Even if a record table is associated to another origin table thought a foreign key field, the record table is not accessible from that origin table + + Basically, what you can access in the origin table can be accessed from the record table. + + example: + model: User + record model: UserRecords + + model: Creation + record model: CreationRecords + + User <-> Creation: 1 to many + + User.getCreations() exists (1 to many) + Creation.getUser() exists (1 to 1) + + User <-> UserRecords: 1 to many + + User.getUserRecords() exists (1 to many) + UserRecords.getUser() exists (1 to 1) + + Creation <-> CreationRecords: 1 to many + + Creation.getCreationRecords() exists (1 to many) + CreationRecords.getCreation() exists (1 to 1) + + CreationRecords -> User: many to 1 + + CreationRecords.getUser() exists (1 to 1) (same as Creation.getUser()) + User.GetCreationRecords DOES NOT EXIST. THE ORIGIN TABLE IS NOT MODIFIED. + + UserRecords -> Creation: many to many + + UserRecords.getCreations() exists (1 to many) (same as User.getCreations()) + CreationRecords.getUser() DOES NOT EXIST. THE ORIGIN TABLE IS NOT MODIFIED. + + */ + addAssociations: false ``` Details -------- -@See: https://wiki.postgresql.org/wiki/SQL2011Temporal +@See: https://wiki.postgresql.org/wiki/SQL2011Record -### History table +### Record table -History table stores historical versions of rows, which are inserted by triggers on every modifying operation executed on current table. It has the same structure and indexes as current table, but it doesn’t have any constraints. History tables are insert only and creator should prevent other users from executing updates or deletes by correct user rights settings. Otherwise the history can be violated. +Record table stores record versions of rows, which are inserted by triggers on every modifying operation executed on current table. It has the same structure and indexes as current table, but it doesn’t have any constraints. Record tables are insert only and creator should prevent other users from executing updates or deletes by correct user rights settings. Otherwise the record can be violated. ### Hooks -Triggers for storing old versions of rows to history table are inspired by referential integrity triggers. They are fired for each row before UPDATE and DELETE (within the same transaction) +Triggers for storing old versions of rows to record table are inspired by referential integrity triggers. They are fired for each row before UPDATE and DELETE (within the same transaction) ### Notes -If you only use Postgres, you might want to have a look at the [Temporal Table](https://github.com/arkhipov/temporal_tables) extension. +If you only use Postgres, you might want to have a look at the [Record Table](https://github.com/arkhipov/record_tables) extension. License ------- From c2f8cae11a42ba35a15e5ffa139b2e6cb7d5235a Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Mon, 29 Apr 2019 20:03:28 +0900 Subject: [PATCH 22/37] Renamed --- README.md | 2 +- index.d.ts | 2 +- index.js | 76 +++++------ package.json | 14 ++- test/test.js | 348 +++++++++++++++++++++++++-------------------------- 5 files changed, 222 insertions(+), 220 deletions(-) diff --git a/README.md b/README.md index a4fabd8..7b79889 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Record tables for Sequelize =============================== -Warning: this is a fork of [sequelize-temporal](https://github.com/opencollective/sequelize-historical) that adds the ability to associate data history table to origin tables (table a Record table is based on) and to specify a different name for the __Record__ tables. +Warning: this is a fork of [sequelize-temporal](https://github.com/bonaval/sequelize-temporal) that adds the ability to associate data history table to origin tables (table a record is based on) and to specify a different name for the __Record__ tables. [![Build Status](https://travis-ci.org/kurisutofu/sequelize-record.svg?branch=master)](https://travis-ci.org/opencollective/sequelize-record) [![Dependency Status](https://david-dm.org/kurisutofu/sequelize-record.svg)](https://david-dm.org/opencollective/sequelize-record) [![NPM version](https://img.shields.io/npm/v/sequelize-record.svg)](https://www.npmjs.com/package/sequelize-record) [![Greenkeeper badge](https://badges.greenkeeper.io/kurisutofu/sequelize-record.svg)](https://greenkeeper.io/) diff --git a/index.d.ts b/index.d.ts index 7281eba..c29b3b6 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,4 +1,4 @@ -declare module 'sequelize-temporal' { +declare module 'sequelize-record' { interface Options { blocking?:boolean, full?:boolean, diff --git a/index.js b/index.js index 5aa1765..9d87654 100644 --- a/index.js +++ b/index.js @@ -1,22 +1,22 @@ var _ = require('lodash'); -var temporalDefaultOptions = { +var recordDefaultOptions = { // runs the insert within the sequelize hook chain, disable // for increased performance blocking: true, full: false, - modelSuffix: 'History', + modelSuffix: 'Record', addAssociations: false }; -var Temporal = function(model, sequelize, temporalOptions) { - temporalOptions = _.extend({},temporalDefaultOptions, temporalOptions); +var Record = function(model, sequelize, recordOptions) { + recordOptions = _.extend({},recordDefaultOptions, recordOptions); var Sequelize = sequelize.Sequelize; - var historyName = model.name + temporalOptions.modelSuffix; + var recordName = model.name + recordOptions.modelSuffix; - var historyOwnAttrs = { + var recordOwnAttrs = { hid: { type: Sequelize.BIGINT, primaryKey: true, @@ -31,7 +31,7 @@ var Temporal = function(model, sequelize, temporalOptions) { }; var excludedAttributes = ["Model","unique","primaryKey","autoIncrement", "set", "get", "_modelAttribute"]; - var historyAttributes = _(model.rawAttributes).mapValues(function(v){ + var recordAttributes = _(model.rawAttributes).mapValues(function(v){ v = _.omit(v, excludedAttributes); // remove the "NOW" defaultValue for the default timestamps // we want to save them, but just a copy from our master record @@ -39,31 +39,31 @@ var Temporal = function(model, sequelize, temporalOptions) { v.type = Sequelize.DATE; } return v; - }).assign(historyOwnAttrs).value(); + }).assign(recordOwnAttrs).value(); // If the order matters, use this: - //historyAttributes = _.assign({}, historyOwnAttrs, historyAttributes); + //recordAttributes = _.assign({}, recordOwnAttrs, recordAttributes); - var historyOwnOptions = { + var recordOwnOptions = { timestamps: false }; var excludedNames = ["name", "tableName", "sequelize", "uniqueKeys", "hasPrimaryKey", "hooks", "scopes", "instanceMethods", "defaultScope"]; var modelOptions = _.omit(model.options, excludedNames); - var historyOptions = _.assign({}, modelOptions, historyOwnOptions); + var recordModelOptions = _.assign({}, modelOptions, recordOwnOptions); // We want to delete indexes that have unique constraint - var indexes = historyOptions.indexes; + var indexes = recordModelOptions.indexes; if(Array.isArray(indexes)){ - historyOptions.indexes = indexes.filter(function(index){return !index.unique && index.type != 'UNIQUE';}); + recordModelOptions.indexes = indexes.filter(function(index){return !index.unique && index.type != 'UNIQUE';}); } - var modelHistory = sequelize.define(historyName, historyAttributes, historyOptions); + var modelRecord = sequelize.define(recordName, recordAttributes, recordModelOptions); // we already get the updatedAt timestamp from our models var insertHook = function(obj, options){ - var dataValues = (!temporalOptions.full && obj._previousDataValues) || obj.dataValues; - var historyRecord = modelHistory.create(dataValues, {transaction: options.transaction}); - if(temporalOptions.blocking){ - return historyRecord; + var dataValues = (!recordOptions.full && obj._previousDataValues) || obj.dataValues; + var recordRecord = modelRecord.create(dataValues, {transaction: options.transaction}); + if(recordOptions.blocking){ + return recordRecord; } } var insertBulkHook = function(options){ @@ -71,19 +71,19 @@ var Temporal = function(model, sequelize, temporalOptions) { var queryAll = model.findAll({where: options.where, transaction: options.transaction}).then(function(hits){ if(hits){ hits = _.map(hits, 'dataValues'); - return modelHistory.bulkCreate(hits, {transaction: options.transaction}); + return modelRecord.bulkCreate(hits, {transaction: options.transaction}); } }); - if(temporalOptions.blocking){ + if(recordOptions.blocking){ return queryAll; } } } var afterBulkSyncHook = function(options){ - sequelize.removeHook('beforeBulkSync', 'TemporalBulkSyncHook'); - sequelize.removeHook('afterBulkSync', 'TemporalBulkSyncHook'); - return Promise.resolve('Temporal Hooks Removed'); + sequelize.removeHook('beforeBulkSync', 'RecordBulkSyncHook'); + sequelize.removeHook('afterBulkSync', 'RecordBulkSyncHook'); + return Promise.resolve('Record Hooks Removed'); } @@ -92,12 +92,12 @@ var Temporal = function(model, sequelize, temporalOptions) { Object.keys(allModels).forEach(key => { const source = allModels[key]; - const sourceHistName = source.name + temporalOptions.modelSuffix; + const sourceHistName = source.name + recordOptions.modelSuffix; const sourceHist = allModels[sourceHistName]; - if(!source.name.endsWith(temporalOptions.modelSuffix) && source.associations && temporalOptions.addAssociations == true && sourceHist) { + if(!source.name.endsWith(recordOptions.modelSuffix) && source.associations && recordOptions.addAssociations == true && sourceHist) { const pkfield = source.primaryKeyField; - //adding associations from historical model to origin model's association + //adding associations from record model to origin model's association Object.keys(source.associations).forEach(key => { const association = source.associations[key]; const target = association.target; @@ -114,7 +114,7 @@ var Temporal = function(model, sequelize, temporalOptions) { //TODO test with several associations to the same table i.e: addedBy, UpdatedBy }); - //adding associations between origin model and historical + //adding associations between origin model and record source.hasMany(sourceHist, { foreignKey: pkfield }); sourceHist.belongsTo(source, { foreignKey: pkfield }); @@ -123,12 +123,12 @@ var Temporal = function(model, sequelize, temporalOptions) { } }); - return Promise.resolve('Temporal associations established'); + return Promise.resolve('Record associations established'); } // use `after` to be nonBlocking // all hooks just create a copy - if (temporalOptions.full) { + if (recordOptions.full) { model.addHook('afterCreate', insertHook); model.addHook('afterUpdate', insertHook); model.addHook('afterDestroy', insertHook); @@ -142,19 +142,19 @@ var Temporal = function(model, sequelize, temporalOptions) { model.addHook('beforeBulkDestroy', insertBulkHook); var readOnlyHook = function(){ - throw new Error("This is a read-only history database. You aren't allowed to modify it."); + throw new Error("This is a read-only record database. You aren't allowed to modify it."); }; - modelHistory.addHook('beforeUpdate', readOnlyHook); - modelHistory.addHook('beforeDestroy', readOnlyHook); + modelRecord.addHook('beforeUpdate', readOnlyHook); + modelRecord.addHook('beforeDestroy', readOnlyHook); - sequelize.removeHook('beforeBulkSync', 'TemporalBulkSyncHook');//remove first to avoid duplicating - sequelize.removeHook('afterBulkSync', 'TemporalBulkSyncHook');//remove first to avoid duplicating - sequelize.addHook('afterBulkSync', 'TemporalBulkSyncHook', afterBulkSyncHook); - sequelize.addHook('beforeBulkSync', 'TemporalBulkSyncHook', beforeBulkSyncHook); + sequelize.removeHook('beforeBulkSync', 'RecordBulkSyncHook');//remove first to avoid duplicating + sequelize.removeHook('afterBulkSync', 'RecordBulkSyncHook');//remove first to avoid duplicating + sequelize.addHook('afterBulkSync', 'RecordBulkSyncHook', afterBulkSyncHook); + sequelize.addHook('beforeBulkSync', 'RecordBulkSyncHook', beforeBulkSyncHook); - modelHistory.addAssociations = temporalOptions.addAssociations; + modelRecord.addAssociations = recordOptions.addAssociations; return model; }; -module.exports = Temporal; +module.exports = Record; diff --git a/package.json b/package.json index 139edb7..38045e0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "sequelize-historical", + "name": "sequelize-record", "version": "5.1.0", - "description": "Historical tables for Sequelize", + "description": "Record tables for Sequelize", "main": "index.js", "directories": { "test": "test" @@ -11,7 +11,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/opencollective/sequelize-historical.git" + "url": "git+https://github.com/kurisutofu/sequelize-record.git" }, "keywords": [ "sequelize", @@ -20,14 +20,16 @@ "sql11", "undo", "paranoid", - "historical" + "historical", + "record", + "history" ], "author": "greenify (https://github.com/greenify)", "license": "MIT", "bugs": { - "url": "https://github.com/opencollective/sequelize-historical/issues" + "url": "https://github.com/kurisutofu/sequelize-record/issues" }, - "homepage": "https://github.com/opencollective/sequelize-historical#readme", + "homepage": "https://github.com/kurisutofu/sequelize-record#readme", "dependencies": { "lodash": "^4.17.11" }, diff --git a/test/test.js b/test/test.js index f1cc9d6..eadd9b6 100644 --- a/test/test.js +++ b/test/test.js @@ -1,4 +1,4 @@ -const Temporal = require('../'); +const Record = require('../'); const Sequelize = require('sequelize'); const chai = require("chai"); const chaiAsPromised = require("chai-as-promised"); @@ -46,12 +46,12 @@ describe('Read-only API', function(){ Tag.belongsToMany(Creation, { through: CreationTag, foreignKey: 'tag', otherKey: 'creation' }); Creation.belongsToMany(Tag, { through: CreationTag, foreignKey: 'creation', otherKey: 'tag' }); - //Temporalize - Temporal(User, sequelize, options); - Temporal(Creation, sequelize, options); - Temporal(Tag, sequelize, options); - Temporal(Event, sequelize, options); - Temporal(CreationTag, sequelize, options); + //Recordize + Record(User, sequelize, options); + Record(Creation, sequelize, options); + Record(Tag, sequelize, options); + Record(Event, sequelize, options); + Record(CreationTag, sequelize, options); return sequelize.sync({force:true}); } @@ -60,8 +60,8 @@ describe('Read-only API', function(){ //each creation has 3 tags //user has 2 creations //creation has 1 event - //tags,crestions,user,events are renamed 3 times to generate 3 historical data - //1 tag is removed and re-added to a creation to create 1 historical entry in the CreationTags table + //tags,crestions,user,events are renamed 3 times to generate 3 record data + //1 tag is removed and re-added to a creation to create 1 record entry in the CreationTags table function dataCreate() { const tag = sequelize.models.Tag.create({ name: 'tag01' }).then( t => { @@ -240,29 +240,29 @@ describe('Read-only API', function(){ return newDB(false, { modelSuffix: '_Hist'}); } - function assertCount(modelHistory, n, opts) { + function assertCount(modelRecord, n, opts) { // wrapped, chainable promise return function(obj) { - return modelHistory.count(opts).then((count) => { - //console.log('Asserting ', modelHistory.name, ' count: ', count, ' expected: ', n); - assert.equal(n, count, "history entries") + return modelRecord.count(opts).then((count) => { + //console.log('Asserting ', modelRecord.name, ' count: ', count, ' expected: ', n); + assert.equal(n, count, "record entries") return obj; }); } } describe('Association Tests', function() { - describe('test there are no historical association', function(){ + describe('test there are no record association', function(){ beforeEach(freshDB); - it('Should have relations for origin models but not for historical models' , function(){ + it('Should have relations for origin models but not for record models' , function(){ const init = dataCreate(); //Get User const user = init.then(() => sequelize.models.User.findOne()); //User associations check - const userHistory = user.then(u =>{ - assert.notExists(u.getUserHistories, 'User: getUserHistories exists'); + const userRecord = user.then(u =>{ + assert.notExists(u.getUserRecords, 'User: getUserRecords exists'); return Promise.resolve('done'); }); @@ -272,10 +272,10 @@ describe('Read-only API', function(){ }); //Creation associations check - const creationHistory = creation.then(c =>{ + const creationRecord = creation.then(c =>{ assert.equal(c.length, 2, 'User: should have found 2 creations'); const first = c[0]; - assert.notExists(first.getCreationHistories, 'Creation: getCreationHistories exists'); + assert.notExists(first.getCreationRecords, 'Creation: getCreationRecords exists'); return Promise.resolve('done'); }); @@ -301,10 +301,10 @@ describe('Read-only API', function(){ }); //Tag associations check - const tagHistory = tag.then(t =>{ + const tagRecord = tag.then(t =>{ assert.equal(t.length, 3, 'Creation: should have found 3 tags'); const first = t[0]; - assert.notExists(first.getTagHistories, 'Tag: getTagHistories exists'); + assert.notExists(first.getTagRecords, 'Tag: getTagRecords exists'); return Promise.resolve('done'); }); @@ -318,9 +318,9 @@ describe('Read-only API', function(){ }); //Event associations check - const eventHistory = event.then(e =>{ + const eventRecord = event.then(e =>{ assert.exists(e, 'Creation: did not find event'); - assert.notExists(e.getEventHistories, 'Event: getEventHistories exist'); + assert.notExists(e.getEventRecords, 'Event: getEventRecords exist'); return Promise.resolve('done'); }); @@ -332,47 +332,47 @@ describe('Read-only API', function(){ return Promise.resolve('done'); }); - //Check Historical data - const userHistories = init.then(assertCount(sequelize.models.UserHistory, 3)); - const creationHistories = init.then(assertCount(sequelize.models.CreationHistory, 6)); - const tagHistories = init.then(assertCount(sequelize.models.TagHistory, 9)); - const eventHistories = init.then(assertCount(sequelize.models.EventHistory, 6)); - const creationTagHistories = init.then(assertCount(sequelize.models.CreationTagHistory, 1)); + //Check record data + const userRecords = init.then(assertCount(sequelize.models.UserRecord, 3)); + const creationRecords = init.then(assertCount(sequelize.models.CreationRecord, 6)); + const tagRecords = init.then(assertCount(sequelize.models.TagRecord, 9)); + const eventRecords = init.then(assertCount(sequelize.models.EventRecord, 6)); + const creationTagRecords = init.then(assertCount(sequelize.models.CreationTagRecord, 1)); return Promise.all([ creation, - creationHistories, - creationHistory, - creationTagHistories, + creationRecords, + creationRecord, + creationTagRecords, cUser, eCreation, event, - eventHistories, - eventHistory, + eventRecords, + eventRecord, init, tag, - tagHistories, - tagHistory, + tagRecords, + tagRecord, tCreation, user, - userHistories, - userHistory + userRecords, + userRecord ]); }); }); - describe('test there are associations are created between origin and historical', function(){ + describe('test there are associations are created between origin and record', function(){ beforeEach(freshDBWithAssociations); - it('Should have relations for origin models and for historical models to origin' , function(){ + it('Should have relations for origin models and for record models to origin' , function(){ const init = dataCreate(); //Get User const user = init.then(() => sequelize.models.User.findOne()); //User associations check - const userHistory = user.then(u =>{ - assert.exists(u.getUserHistories, 'User: getUserHistories does not exist'); - return u.getUserHistories(); + const userRecord = user.then(u =>{ + assert.exists(u.getUserRecords, 'User: getUserRecords does not exist'); + return u.getUserRecords(); }); const creation = user.then(u =>{ @@ -380,32 +380,32 @@ describe('Read-only API', function(){ return u.getCreations(); }); - //UserHistories associations check - const uhCreation = userHistory.then(uh =>{ - assert.equal(uh.length, 3, 'User: should have found 3 UserHistories'); + //UserRecords associations check + const uhCreation = userRecord.then(uh =>{ + assert.equal(uh.length, 3, 'User: should have found 3 UserRecords'); const first = uh[0]; - assert.exists(first.getCreations, 'UserHistory: getCreations does not exist'); + assert.exists(first.getCreations, 'UserRecord: getCreations does not exist'); return first.getCreations(); }).then(uhc => { - assert.equal(uhc.length, 2, 'UserHistory: should have found 2 creations'); + assert.equal(uhc.length, 2, 'UserRecord: should have found 2 creations'); return Promise.resolve('done'); }); - const uhUser = userHistory.then(uh =>{ + const uhUser = userRecord.then(uh =>{ const first = uh[0]; - assert.exists(first.getUser, 'UserHistory: getUser does not exist'); + assert.exists(first.getUser, 'UserRecord: getUser does not exist'); return first.getUser(); }).then(uhu => { - assert.exists(uhu, 'UserHistory: did not find a user'); + assert.exists(uhu, 'UserRecord: did not find a user'); return Promise.resolve('done'); }); //Creation associations check - const creationHistory = creation.then(c =>{ + const creationRecord = creation.then(c =>{ assert.equal(c.length, 2, 'User: should have found 2 creations'); const first = c[0]; - assert.exists(first.getCreationHistories, 'Creation: getCreationHistories does not exist'); - return first.getCreationHistories(); + assert.exists(first.getCreationRecords, 'Creation: getCreationRecords does not exist'); + return first.getCreationRecords(); }); const tag = creation.then(c =>{ @@ -429,51 +429,51 @@ describe('Read-only API', function(){ return Promise.resolve('done'); }); - //CreationHistories association check - const chCreation = creationHistory.then(ch =>{ - assert.equal(ch.length, 3, 'Creation: should have found 3 CreationHistories'); + //CreationRecords association check + const chCreation = creationRecord.then(ch =>{ + assert.equal(ch.length, 3, 'Creation: should have found 3 CreationRecords'); const first = ch[0]; - assert.exists(first.getCreation, 'CreationHistory: getCreation does not exist'); + assert.exists(first.getCreation, 'CreationRecord: getCreation does not exist'); return first.getCreation(); }).then(chc => { - assert.exists(chc, 'CreationHistory: did noy find a creation'); + assert.exists(chc, 'CreationRecord: did noy find a creation'); return Promise.resolve('done'); }); - const chTag = creationHistory.then(ch =>{ + const chTag = creationRecord.then(ch =>{ const first = ch[0]; - assert.exists(first.getTags, 'CreationHistory: getTags does not exist'); + assert.exists(first.getTags, 'CreationRecord: getTags does not exist'); return first.getTags(); }).then(uht => { assert.equal(uht.length, 3); return Promise.resolve('done'); }); - const chUser = creationHistory.then(ch =>{ + const chUser = creationRecord.then(ch =>{ const first = ch[0]; - assert.exists(first.getUser, 'CreationHistory: getUser does not exist'); + assert.exists(first.getUser, 'CreationRecord: getUser does not exist'); return first.getUser(); }).then(chu => { - assert.exists(chu, 'CreationHistory: did not find a user'); + assert.exists(chu, 'CreationRecord: did not find a user'); return Promise.resolve('done'); }); - const chEvent = creationHistory.then(ch =>{ + const chEvent = creationRecord.then(ch =>{ const first = ch[0]; - assert.exists(first.getEvent, 'CreationHistory: getEvent does not exist'); + assert.exists(first.getEvent, 'CreationRecord: getEvent does not exist'); return first.getEvent(); }).then(che => { - assert.exists(che, 'CreationHistory: did not find an event'); + assert.exists(che, 'CreationRecord: did not find an event'); return Promise.resolve('done'); }); //Tag associations check - const tagHistory = tag.then(t =>{ + const tagRecord = tag.then(t =>{ assert.equal(t.length, 3, 'Creation: should have found 3 tags'); const first = t[0]; - assert.exists(first.getTagHistories, 'Tag: getTagHistories does not exist'); - return first.getTagHistories(); + assert.exists(first.getTagRecords, 'Tag: getTagRecords does not exist'); + return first.getTagRecords(); }); const tCreation = tag.then(t =>{ @@ -485,31 +485,31 @@ describe('Read-only API', function(){ return Promise.resolve('done'); }); - //TagHistories associations check - const thTag = tagHistory.then(th =>{ - assert.equal(th.length, 3, 'TagHistory: should have found 3 TagHistories'); + //TagRecords associations check + const thTag = tagRecord.then(th =>{ + assert.equal(th.length, 3, 'TagRecord: should have found 3 TagRecords'); const first = th[0]; - assert.exists(first.getTag, 'TagHistory: getTag does not exist'); + assert.exists(first.getTag, 'TagRecord: getTag does not exist'); return first.getTag(); }).then(tht => { - assert.exists(tht, 'TagHistory: did not find a tag'); + assert.exists(tht, 'TagRecord: did not find a tag'); return Promise.resolve('done'); }); - const thCreation = tagHistory.then(th =>{ + const thCreation = tagRecord.then(th =>{ const first = th[0]; - assert.exists(first.getCreations, 'TagHistory: getCreations does not exist'); + assert.exists(first.getCreations, 'TagRecord: getCreations does not exist'); return first.getCreations(); }).then(thc => { - assert.equal(thc.length, 2, 'TagHistory: should have found 2 creations'); + assert.equal(thc.length, 2, 'TagRecord: should have found 2 creations'); return Promise.resolve('done'); }); //Event associations check - const eventHistory = event.then(e =>{ + const eventRecord = event.then(e =>{ assert.exists(e, 'Creation: did not find an event'); - assert.exists(e.getEventHistories, 'Event: getEventHistories does not exist'); - return e.getEventHistories(); + assert.exists(e.getEventRecords, 'Event: getEventRecords does not exist'); + return e.getEventRecords(); }); const eCreation = event.then(e =>{ @@ -520,32 +520,32 @@ describe('Read-only API', function(){ return Promise.resolve('done'); }); - //EventHistories associations check - const ehEvent = eventHistory.then(eh =>{ - assert.equal(eh.length, 3, 'Event: should have found 3 EventHistories'); + //EventRecords associations check + const ehEvent = eventRecord.then(eh =>{ + assert.equal(eh.length, 3, 'Event: should have found 3 EventRecords'); const first = eh[0]; - assert.exists(first.getEvent, 'EventHistories: getEvent does not exist'); + assert.exists(first.getEvent, 'EventRecords: getEvent does not exist'); return first.getEvent(); }).then(ehe => { - assert.exists(ehe, 'EventHistories: did not find an event'); + assert.exists(ehe, 'EventRecords: did not find an event'); return Promise.resolve('done'); }); - const ehCreation = eventHistory.then(eh =>{ + const ehCreation = eventRecord.then(eh =>{ const first = eh[0]; - assert.exists(first.getCreation, 'EventHistories: getCreation does not exist'); + assert.exists(first.getCreation, 'EventRecords: getCreation does not exist'); return first.getCreation(); }).then(ehc => { - assert.exists(ehc, 'EventHistories: did not find a creation'); + assert.exists(ehc, 'EventRecords: did not find a creation'); return Promise.resolve('done'); }); - //Check Historical data - const userHistories = init.then(assertCount(sequelize.models.UserHistory, 3)); - const creationHistories = init.then(assertCount(sequelize.models.CreationHistory, 6)); - const tagHistories = init.then(assertCount(sequelize.models.TagHistory, 9)); - const eventHistories = init.then(assertCount(sequelize.models.EventHistory, 6)); - const creationTagHistories = init.then(assertCount(sequelize.models.CreationTagHistory, 1)); + //Check record data + const userRecords = init.then(assertCount(sequelize.models.UserRecord, 3)); + const creationRecords = init.then(assertCount(sequelize.models.CreationRecord, 6)); + const tagRecords = init.then(assertCount(sequelize.models.TagRecord, 9)); + const eventRecords = init.then(assertCount(sequelize.models.EventRecord, 6)); + const creationTagRecords = init.then(assertCount(sequelize.models.CreationTagRecord, 1)); return Promise.all([ @@ -554,26 +554,26 @@ describe('Read-only API', function(){ chTag, chUser, creation, - creationHistories, - creationHistory, - creationTagHistories, + creationRecords, + creationRecord, + creationTagRecords, cUser, eCreation, event, - eventHistories, - eventHistory, + eventRecords, + eventRecord, init, tag, - tagHistories, - tagHistory, + tagRecords, + tagRecord, tCreation, thCreation, thTag, uhCreation, uhUser, user, - userHistories, - userHistory, + userRecords, + userRecord, ehEvent, ehCreation ]); @@ -585,11 +585,11 @@ describe('Read-only API', function(){ //Only added is to test for the model name describe('test suffix ending in T', function() { beforeEach(freshDBWithSuffixEndingWithT); - it('onCreate: should not store the new version in history db' , function() { + it('onCreate: should not store the new version in record db' , function() { return sequelize.models.User.create({ name: 'test' }) .then(assertCount(sequelize.models.User_Hist, 0)); }); - it('onUpdate/onDestroy: should save to the historyDB' , function() { + it('onUpdate/onDestroy: should save to the recordDB' , function() { return sequelize.models.User.create() .then(assertCount(sequelize.models.User_Hist,0)) .then((user) => { @@ -600,7 +600,7 @@ describe('Read-only API', function(){ .then(user => user.destroy()) .then(assertCount(sequelize.models.User_Hist,2)); }); - it('onUpdate: should store the previous version to the historyDB' , function() { + it('onUpdate: should store the previous version to the recordDB' , function() { return sequelize.models.User.create({name: "foo"}) .then(assertCount(sequelize.models.User_Hist,0)) .then((user) => { @@ -617,7 +617,7 @@ describe('Read-only API', function(){ .then((user) => user.destroy()) .then(assertCount(sequelize.models.User_Hist,2)) }); - it('onDelete: should store the previous version to the historyDB' , function() { + it('onDelete: should store the previous version to the recordDB' , function() { return sequelize.models.User.create({name: "foo"}) .then(assertCount(sequelize.models.User_Hist,0)) .then(user => user.destroy()) @@ -632,43 +632,43 @@ describe('Read-only API', function(){ describe('hooks', function() { beforeEach(freshDB); - it('onCreate: should not store the new version in history db' , function() { + it('onCreate: should not store the new version in record db' , function() { return sequelize.models.User.create({ name: 'test' }) - .then(assertCount(sequelize.models.UserHistory, 0)); + .then(assertCount(sequelize.models.UserRecord, 0)); }); - it('onUpdate/onDestroy: should save to the historyDB' , function() { + it('onUpdate/onDestroy: should save to the recordDB' , function() { return sequelize.models.User.create() - .then(assertCount(sequelize.models.UserHistory,0)) + .then(assertCount(sequelize.models.UserRecord,0)) .then((user) => { user.name = "foo"; return user.save(); }) - .then(assertCount(sequelize.models.UserHistory,1)) + .then(assertCount(sequelize.models.UserRecord,1)) .then(user => user.destroy()) - .then(assertCount(sequelize.models.UserHistory,2)) + .then(assertCount(sequelize.models.UserRecord,2)) }); - it('onUpdate: should store the previous version to the historyDB' , function() { + it('onUpdate: should store the previous version to the recordDB' , function() { return sequelize.models.User.create({name: "foo"}) - .then(assertCount(sequelize.models.UserHistory,0)) + .then(assertCount(sequelize.models.UserRecord,0)) .then((user) => { user.name = "bar"; return user.save(); }) - .then(assertCount(sequelize.models.UserHistory,1)) - .then(() => sequelize.models.UserHistory.findAll()) + .then(assertCount(sequelize.models.UserRecord,1)) + .then(() => sequelize.models.UserRecord.findAll()) .then((users) => { assert.equal(users.length,1, "only one entry in DB"); assert.equal(users[0].name, "foo", "previous entry saved"); }).then(user => sequelize.models.User.findOne()) .then(user => user.destroy()) - .then(assertCount(sequelize.models.UserHistory,2)) + .then(assertCount(sequelize.models.UserRecord,2)) }); - it('onDelete: should store the previous version to the historyDB' , function() { + it('onDelete: should store the previous version to the recordDB' , function() { return sequelize.models.User.create({name: "foo"}) - .then(assertCount(sequelize.models.UserHistory,0)) + .then(assertCount(sequelize.models.UserRecord,0)) .then(user => user.destroy()) - .then(assertCount(sequelize.models.UserHistory,1)) - .then(() => sequelize.models.UserHistory.findAll()) + .then(assertCount(sequelize.models.UserRecord,1)) + .then(() => sequelize.models.UserRecord.findAll()) .then((users) => { assert.equal(users.length,1, "only one entry in DB"); assert.equal(users[0].name, "foo", "previous entry saved"); @@ -683,15 +683,15 @@ describe('Read-only API', function(){ .then((t) => { var opts = {transaction: t}; return sequelize.models.User.create({name: "not foo"},opts) - .then(assertCount(sequelize.models.UserHistory,0, opts)) + .then(assertCount(sequelize.models.UserRecord,0, opts)) .then((user) => { user.name = "foo"; user.save(opts); }) - .then(assertCount(sequelize.models.UserHistory,1, opts)) + .then(assertCount(sequelize.models.UserRecord,1, opts)) .then(() => t.rollback()); }) - .then(assertCount(sequelize.models.UserHistory,0)); + .then(assertCount(sequelize.models.UserRecord,0)); }); }); @@ -699,21 +699,21 @@ describe('Read-only API', function(){ beforeEach(freshDB); it('should archive every entry', function() { return sequelize.models.User.bulkCreate([{name: "foo1"},{name: "foo2"}]) - .then(assertCount(sequelize.models.UserHistory,0)) + .then(assertCount(sequelize.models.UserRecord,0)) .then(() => sequelize.models.User.update({ name: 'updated-foo' }, {where: {}})) - .then(assertCount(sequelize.models.UserHistory,2)) + .then(assertCount(sequelize.models.UserRecord,2)) }); it('should revert under transactions', function() { return sequelize.transaction() .then(function(t) { var opts = {transaction: t}; return sequelize.models.User.bulkCreate([{name: "foo1"},{name: "foo2"}], opts) - .then(assertCount(sequelize.models.UserHistory,0,opts)) + .then(assertCount(sequelize.models.UserRecord,0,opts)) .then(() => sequelize.models.User.update({ name: 'updated-foo' }, {where: {}, transaction: t})) - .then(assertCount(sequelize.models.UserHistory,2, opts)) + .then(assertCount(sequelize.models.UserRecord,2, opts)) .then(() => t.rollback()); }) - .then(assertCount(sequelize.models.UserHistory,0)); + .then(assertCount(sequelize.models.UserRecord,0)); }); }); @@ -721,28 +721,28 @@ describe('Read-only API', function(){ beforeEach(freshDB); it('should archive every entry', function() { return sequelize.models.User.bulkCreate([{name: "foo1"},{name: "foo2"}]) - .then(assertCount(sequelize.models.UserHistory,0)) + .then(assertCount(sequelize.models.UserRecord,0)) .then(() => sequelize.models.User.destroy({ where: {}, truncate: true // truncate the entire table })) - .then(assertCount(sequelize.models.UserHistory,2)) + .then(assertCount(sequelize.models.UserRecord,2)) }); it('should revert under transactions', function() { return sequelize.transaction() .then((t) => { var opts = {transaction: t}; return sequelize.models.User.bulkCreate([{name: "foo1"},{name: "foo2"}], opts) - .then(assertCount(sequelize.models.UserHistory,0,opts)) + .then(assertCount(sequelize.models.UserRecord,0,opts)) .then(() => sequelize.models.User.destroy({ where: {}, truncate: true, // truncate the entire table transaction: t })) - .then(assertCount(sequelize.models.UserHistory,2, opts)) + .then(assertCount(sequelize.models.UserRecord,2, opts)) .then(() => t.rollback()); }) - .then(assertCount(sequelize.models.UserHistory,0)); + .then(assertCount(sequelize.models.UserRecord,0)); }); }); @@ -750,14 +750,14 @@ describe('Read-only API', function(){ beforeEach(freshDBWithAssociations); it('should archive every entry', function() { return dataCreate() - .then(assertCount(sequelize.models.UserHistory,3)) + .then(assertCount(sequelize.models.UserRecord,3)) .then(() => sequelize.models.User.destroy({ where: {}, truncate: true // truncate the entire table })) - .then(assertCount(sequelize.models.UserHistory,6)) + .then(assertCount(sequelize.models.UserRecord,6)) .then(() => sequelize.models.User.findOne()) - .then(u => u.getUserHistories()) + .then(u => u.getUserRecords()) .then(uh => assert.exists(uh, 'The truncation did not break the associations')) .catch(err => assert.exists(err,'The truncation broke the associations')); }); @@ -766,30 +766,30 @@ describe('Read-only API', function(){ .then(() => sequelize.transaction()) .then((t) => { var opts = {transaction: t}; - assertCount(sequelize.models.UserHistory,3,opts); + assertCount(sequelize.models.UserRecord,3,opts); return sequelize.models.User.destroy({ where: {}, truncate: true, // truncate the entire table transaction: t }) - .then(assertCount(sequelize.models.UserHistory,6, opts)) + .then(assertCount(sequelize.models.UserRecord,6, opts)) .then(() => t.rollback()) .catch(err => assert.exists(err)); }) - .then(assertCount(sequelize.models.UserHistory,3)); + .then(assertCount(sequelize.models.UserRecord,3)); }); }); describe('read-only ', function() { beforeEach(freshDB); it('should forbid updates' , function() { - var userUpdate = sequelize.models.UserHistory.create({name: 'bla00'}) + var userUpdate = sequelize.models.UserRecord.create({name: 'bla00'}) .then((uh) => uh.update({name: 'bla'})); return assert.isRejected(userUpdate, Error, "Validation error"); }); it('should forbid deletes' , function() { - var userUpdate = sequelize.models.UserHistory.create({name: 'bla00'}) + var userUpdate = sequelize.models.UserRecord.create({name: 'bla00'}) .then(uh => uh.destroy()); return assert.isRejected(userUpdate, Error, "Validation error"); @@ -799,7 +799,7 @@ describe('Read-only API', function(){ describe('interference with the original model', function() { beforeEach(freshDB); it('shouldn\'t delete instance methods' , function() { - Fruit = Temporal(sequelize.define('Fruit', { name: Sequelize.TEXT }), sequelize); + Fruit = Record(sequelize.define('Fruit', { name: Sequelize.TEXT }), sequelize); Fruit.prototype.sayHi = () => { return 2; } return sequelize.sync() @@ -812,7 +812,7 @@ describe('Read-only API', function(){ it('shouldn\'t interfere with hooks of the model' , function() { var triggered = 0; - Fruit = Temporal(sequelize.define('Fruit', { name: Sequelize.TEXT }, { hooks:{ beforeCreate: function(){ triggered++; }}}), sequelize); + Fruit = Record(sequelize.define('Fruit', { name: Sequelize.TEXT }, { hooks:{ beforeCreate: function(){ triggered++; }}}), sequelize); return sequelize.sync() .then(() => Fruit.create()) .then((f) => assert.equal(triggered, 1,"hook trigger count")); @@ -820,7 +820,7 @@ describe('Read-only API', function(){ it('shouldn\'t interfere with setters' , function() { var triggered = 0; - Fruit = Temporal(sequelize.define('Fruit', { + Fruit = Record(sequelize.define('Fruit', { name: { type: Sequelize.TEXT, set: function() { triggered++; } @@ -834,49 +834,49 @@ describe('Read-only API', function(){ describe('full mode', function() { beforeEach(freshDBWithFullModeAndParanoid); - it('onCreate: should store the new version in history db' , function() { + it('onCreate: should store the new version in record db' , function() { return sequelize.models.User.create({ name: 'test' }) - .then(() => sequelize.models.UserHistory.findAll()) - .then((histories) => { - assert.equal(1, histories.length); - assert.equal('test', histories[0].name); + .then(() => sequelize.models.UserRecord.findAll()) + .then((records) => { + assert.equal(1, records.length); + assert.equal('test', records[0].name); }); }); - it('onUpdate: should store the new version to the historyDB' , function() { + it('onUpdate: should store the new version to the recordDB' , function() { return sequelize.models.User.create({ name: 'test' }) .then(user => user.update({ name: 'renamed' })) - .then(() => sequelize.models.UserHistory.findAll()) - .then((histories) => { - assert.equal(histories.length, 2, 'two entries in DB'); - assert.equal(histories[0].name, 'test', 'first version saved'); - assert.equal(histories[1].name, 'renamed', 'second version saved'); + .then(() => sequelize.models.UserRecord.findAll()) + .then((records) => { + assert.equal(records.length, 2, 'two entries in DB'); + assert.equal(records[0].name, 'test', 'first version saved'); + assert.equal(records[1].name, 'renamed', 'second version saved'); }); }); - it('onDelete: should store the previous version to the historyDB' , function() { + it('onDelete: should store the previous version to the recordDB' , function() { return sequelize.models.User.create({ name: 'test' }) .then(user => user.update({ name: 'renamed' })) .then(user=> user.destroy()) - .then(() => sequelize.models.UserHistory.findAll()) - .then((histories) => { - assert.equal(histories.length, 3, 'three entries in DB'); - assert.equal(histories[0].name, 'test', 'first version saved'); - assert.equal(histories[1].name, 'renamed', 'second version saved'); - assert.notEqual(histories[2].deletedAt, null, 'deleted version saved'); + .then(() => sequelize.models.UserRecord.findAll()) + .then((records) => { + assert.equal(records.length, 3, 'three entries in DB'); + assert.equal(records[0].name, 'test', 'first version saved'); + assert.equal(records[1].name, 'renamed', 'second version saved'); + assert.notEqual(records[2].deletedAt, null, 'deleted version saved'); }); }); - it('onRestore: should store the new version to the historyDB' , function() { + it('onRestore: should store the new version to the recordDB' , function() { return sequelize.models.User.create({ name: 'test' }) .then(user => user.destroy()) .then(user => user.restore()) - .then(() => sequelize.models.UserHistory.findAll()) - .then((histories) => { - assert.equal(histories.length, 3, 'three entries in DB'); - assert.equal(histories[0].name, 'test', 'first version saved'); - assert.notEqual(histories[1].deletedAt, null, 'deleted version saved'); - assert.equal(histories[2].deletedAt, null, 'restored version saved'); + .then(() => sequelize.models.UserRecord.findAll()) + .then((records) => { + assert.equal(records.length, 3, 'three entries in DB'); + assert.equal(records[0].name, 'test', 'first version saved'); + assert.notEqual(records[1].deletedAt, null, 'deleted version saved'); + assert.equal(records[2].deletedAt, null, 'restored version saved'); }); }); @@ -887,10 +887,10 @@ describe('Read-only API', function(){ return sequelize.models.User.create({ name: 'test' }, options) .then(user => user.destroy(options)) - .then(assertCount(sequelize.models.UserHistory, 2, options)) + .then(assertCount(sequelize.models.UserRecord, 2, options)) .then(() => transaction.rollback()); }) - .then(assertCount(sequelize.models.UserHistory,0)); + .then(assertCount(sequelize.models.UserRecord,0)); }); }); }); From 38aee0b1b1e0960f9c4546a888154a425ced4406 Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Tue, 30 Apr 2019 22:37:53 +0900 Subject: [PATCH 23/37] Fixed test & finalize --- index.js | 81 +++++++++++++++++++++++----------------------------- package.json | 2 +- test/test.js | 76 ++++++++++++++++++++++++++++++------------------ 3 files changed, 86 insertions(+), 73 deletions(-) diff --git a/index.js b/index.js index 9d87654..faa47b2 100644 --- a/index.js +++ b/index.js @@ -80,48 +80,42 @@ var Record = function(model, sequelize, recordOptions) { } } - var afterBulkSyncHook = function(options){ - sequelize.removeHook('beforeBulkSync', 'RecordBulkSyncHook'); - sequelize.removeHook('afterBulkSync', 'RecordBulkSyncHook'); - return Promise.resolve('Record Hooks Removed'); - } +// var afterBulkSyncHook = function(options){ +// sequelize.removeHook('beforeBulkSync', 'RecordBulkSyncHook'); +// sequelize.removeHook('afterBulkSync', 'RecordBulkSyncHook'); +// return Promise.resolve('Record Hooks Removed'); +// } - - var beforeBulkSyncHook = function(options){ - const allModels = sequelize.models; - - Object.keys(allModels).forEach(key => { - const source = allModels[key]; - const sourceHistName = source.name + recordOptions.modelSuffix; - const sourceHist = allModels[sourceHistName]; - - if(!source.name.endsWith(recordOptions.modelSuffix) && source.associations && recordOptions.addAssociations == true && sourceHist) { - const pkfield = source.primaryKeyField; - //adding associations from record model to origin model's association - Object.keys(source.associations).forEach(key => { - const association = source.associations[key]; - const target = association.target; - const assocName = association.associationType.charAt(0).toLowerCase() + association.associationType.substr(1); - - //handle premary keys for belongsToMany - if(assocName == 'belongsToMany') { - sourceHist.primaryKeys = _.forEach(source.primaryKeys, (x) => x.autoIncrement = false); - sourceHist.primaryKeyField = Object.keys(sourceHist.primaryKeys)[0]; - } - - sourceHist[assocName].apply(sourceHist, [target, association.options]); - - //TODO test with several associations to the same table i.e: addedBy, UpdatedBy - }); - - //adding associations between origin model and record - source.hasMany(sourceHist, { foreignKey: pkfield }); - sourceHist.belongsTo(source, { foreignKey: pkfield }); - - sequelize.models[sourceHistName] = sourceHist; - sequelize.models[sourceHistName].sync(); - } - }); + var beforeSync = function(options) { + const source = this; + const sourceHistName = source.name + recordOptions.modelSuffix; + const sourceHist = sequelize.models[sourceHistName]; + + if(!source.name.endsWith(recordOptions.modelSuffix) && source.associations && recordOptions.addAssociations == true && sourceHist) { + const pkfield = source.primaryKeyField; + //adding associations from record model to origin model's association + Object.keys(source.associations).forEach(assokey => { + const association = source.associations[assokey]; + const associationOptions = _.cloneDeep(association.options); + const target = association.target; + const assocName = association.associationType.charAt(0).toLowerCase() + association.associationType.substr(1); + + //handle premary keys for belongsToMany + if(assocName == 'belongsToMany') { + sourceHist.primaryKeys = _.forEach(source.primaryKeys, (x) => x.autoIncrement = false); + sourceHist.primaryKeyField = Object.keys(sourceHist.primaryKeys)[0]; + } + + sourceHist[assocName].apply(sourceHist, [target, associationOptions]); + }); + + //adding associations between origin model and record + source.hasMany(sourceHist, { foreignKey: pkfield }); + sourceHist.belongsTo(source, { foreignKey: pkfield }); + + sequelize.models[sourceHistName] = sourceHist; + sequelize.models[sourceHistName].sync(); + } return Promise.resolve('Record associations established'); } @@ -148,10 +142,7 @@ var Record = function(model, sequelize, recordOptions) { modelRecord.addHook('beforeUpdate', readOnlyHook); modelRecord.addHook('beforeDestroy', readOnlyHook); - sequelize.removeHook('beforeBulkSync', 'RecordBulkSyncHook');//remove first to avoid duplicating - sequelize.removeHook('afterBulkSync', 'RecordBulkSyncHook');//remove first to avoid duplicating - sequelize.addHook('afterBulkSync', 'RecordBulkSyncHook', afterBulkSyncHook); - sequelize.addHook('beforeBulkSync', 'RecordBulkSyncHook', beforeBulkSyncHook); + model.addHook('beforeSync', 'RecordSyncHook', beforeSync); modelRecord.addAssociations = recordOptions.addAssociations; return model; diff --git a/package.json b/package.json index 38045e0..f30a708 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sequelize-record", - "version": "5.1.0", + "version": "1.0.0", "description": "Record tables for Sequelize", "main": "index.js", "directories": { diff --git a/test/test.js b/test/test.js index eadd9b6..6f2574b 100644 --- a/test/test.js +++ b/test/test.js @@ -17,7 +17,7 @@ describe('Read-only API', function(){ } const dbFile = __dirname + '/.test.sqlite'; - try { fs.unlinkSync(dbFile); } catch {}; + try {fs.unlinkSync(dbFile);} catch {}; sequelize = new Sequelize('', '', '', { dialect: 'sqlite', @@ -27,16 +27,19 @@ describe('Read-only API', function(){ //Define origin models const User = sequelize.define('User', { name: Sequelize.TEXT }, {paranoid: paranoid || false}); - const Creation = sequelize.define('Creation', { name: Sequelize.TEXT, user: Sequelize.INTEGER }, {paranoid: paranoid || false}); + const Creation = sequelize.define('Creation', { name: Sequelize.TEXT, user: Sequelize.INTEGER, user2: Sequelize.INTEGER }, {paranoid: paranoid || false}); const Tag = sequelize.define('Tag', { name: Sequelize.TEXT }, {paranoid: paranoid || false}); const Event = sequelize.define('Event', { name: Sequelize.TEXT, creation: Sequelize.INTEGER }, {paranoid: paranoid || false}); const CreationTag = sequelize.define('CreationTag', { creation: Sequelize.INTEGER, tag: Sequelize.INTEGER }, {paranoid: paranoid || false}); //Associate models - //1.* - User.hasMany(Creation, { foreignKey: 'user' }); - Creation.belongsTo(User, { foreignKey: 'user' }); + //1.* with 2 association to same table + User.hasMany(Creation, { foreignKey: 'user', as: 'creatorCreations' }); + User.hasMany(Creation, { foreignKey: 'user2', as: 'updatorCreations' }); + + Creation.belongsTo(User, { foreignKey: 'user', as: 'createUser' }); + Creation.belongsTo(User, { foreignKey: 'user2', as: 'updateUser' }); //1.1 Event.belongsTo(Creation, { foreignKey: 'creation' }); @@ -56,7 +59,7 @@ describe('Read-only API', function(){ return sequelize.sync({force:true}); } - //Adding 3 tags, 2 creations, 2 events, 1 user + //Adding 3 tags, 2 creations, 2 events, 2 user //each creation has 3 tags //user has 2 creations //creation has 1 event @@ -103,8 +106,19 @@ describe('Read-only API', function(){ u.save(); return u; }); + + const user2 = sequelize.models.User.create({ name: 'user02' }).then( u => { + u.name = 'user02 renamed'; + u.save(); + u.name = 'user02 renamed twice'; + u.save(); + u.name = 'user02 renamed three times'; + u.save(); + return u; + }); - const creation = user.then(u => sequelize.models.Creation.create({ name: 'creation01', user: u.id })) + const creation = Promise.all([user, user2]) + .then(allU => sequelize.models.Creation.create({ name: 'creation01', user: allU[0].id, user2: allU[1].id })) .then( c => { c.name = 'creation01 renamed'; c.save(); @@ -115,7 +129,8 @@ describe('Read-only API', function(){ return c; }); - const creation2 = user.then(u => sequelize.models.Creation.create({ name: 'creation02', user: u.id })) + const creation2 = Promise.all([user, user2]) + .then(allU => sequelize.models.Creation.create({ name: 'creation02', user: allU[0].id, user2: allU[1].id })) .then( c => { c.name = 'creation02 renamed'; c.save(); @@ -211,6 +226,7 @@ describe('Read-only API', function(){ tag2, tag3, user, + user2, creation, creation2, creationTag1, @@ -267,8 +283,9 @@ describe('Read-only API', function(){ }); const creation = user.then(u =>{ - assert.exists(u.getCreations, 'User: getCreations does not exist'); - return u.getCreations(); + assert.exists(u.getCreatorCreations, 'User: getCreatorCreations does not exist'); + assert.exists(u.getUpdatorCreations, 'User: getUpdatorCreations does not exist'); + return u.getCreatorCreations(); }); //Creation associations check @@ -293,10 +310,11 @@ describe('Read-only API', function(){ const cUser = creation.then(c =>{ const first = c[0]; - assert.exists(first.getUser, 'Creation: getUser does not exist'); - return first.getUser(); + assert.exists(first.getCreateUser, 'Creation: getCreateUser does not exist'); + assert.exists(first.getUpdateUser, 'Creation: getUpdateUser does not exist'); + return first.getCreateUser(); }).then(cu => { - assert.exists(cu, 'Creation: did not find user'); + assert.exists(cu, 'Creation: did not find CreateUser'); return Promise.resolve('done'); }); @@ -333,7 +351,7 @@ describe('Read-only API', function(){ }); //Check record data - const userRecords = init.then(assertCount(sequelize.models.UserRecord, 3)); + const userRecords = init.then(assertCount(sequelize.models.UserRecord, 6)); const creationRecords = init.then(assertCount(sequelize.models.CreationRecord, 6)); const tagRecords = init.then(assertCount(sequelize.models.TagRecord, 9)); const eventRecords = init.then(assertCount(sequelize.models.EventRecord, 6)); @@ -376,16 +394,18 @@ describe('Read-only API', function(){ }); const creation = user.then(u =>{ - assert.exists(u.getCreations, 'User: getCreations does not exist'); - return u.getCreations(); + assert.exists(u.getCreatorCreations, 'User: getCreatorCreations does not exist'); + assert.exists(u.getUpdatorCreations, 'User: getUpdatorCreations does not exist'); + return u.getCreatorCreations(); }); //UserRecords associations check const uhCreation = userRecord.then(uh =>{ assert.equal(uh.length, 3, 'User: should have found 3 UserRecords'); const first = uh[0]; - assert.exists(first.getCreations, 'UserRecord: getCreations does not exist'); - return first.getCreations(); + assert.exists(first.getCreatorCreations, 'UserRecord: getCreatorCreations does not exist'); + assert.exists(first.getUpdatorCreations, 'UserRecord: getUpdatorCreations does not exist'); + return first.getCreatorCreations(); }).then(uhc => { assert.equal(uhc.length, 2, 'UserRecord: should have found 2 creations'); return Promise.resolve('done'); @@ -422,10 +442,11 @@ describe('Read-only API', function(){ const cUser = creation.then(c =>{ const first = c[0]; - assert.exists(first.getUser, 'Creation: getUser does not exist'); - return first.getUser(); + assert.exists(first.getCreateUser, 'Creation: getCreateUser does not exist'); + assert.exists(first.getUpdateUser, 'Creation: getUpdateUser does not exist'); + return first.getCreateUser(); }).then(cu => { - assert.exists(cu, 'Creation: did not find a user'); + assert.exists(cu, 'Creation: did not find a create user'); return Promise.resolve('done'); }); @@ -451,8 +472,9 @@ describe('Read-only API', function(){ const chUser = creationRecord.then(ch =>{ const first = ch[0]; - assert.exists(first.getUser, 'CreationRecord: getUser does not exist'); - return first.getUser(); + assert.exists(first.getCreateUser, 'CreationRecord: getCreateUser does not exist'); + assert.exists(first.getUpdateUser, 'CreationRecord: getUpdateUser does not exist'); + return first.getCreateUser(); }).then(chu => { assert.exists(chu, 'CreationRecord: did not find a user'); return Promise.resolve('done'); @@ -541,7 +563,7 @@ describe('Read-only API', function(){ }); //Check record data - const userRecords = init.then(assertCount(sequelize.models.UserRecord, 3)); + const userRecords = init.then(assertCount(sequelize.models.UserRecord, 6)); const creationRecords = init.then(assertCount(sequelize.models.CreationRecord, 6)); const tagRecords = init.then(assertCount(sequelize.models.TagRecord, 9)); const eventRecords = init.then(assertCount(sequelize.models.EventRecord, 6)); @@ -766,17 +788,17 @@ describe('Read-only API', function(){ .then(() => sequelize.transaction()) .then((t) => { var opts = {transaction: t}; - assertCount(sequelize.models.UserRecord,3,opts); + assertCount(sequelize.models.UserRecord,6,opts); return sequelize.models.User.destroy({ where: {}, truncate: true, // truncate the entire table transaction: t }) - .then(assertCount(sequelize.models.UserRecord,6, opts)) + .then(assertCount(sequelize.models.UserRecord,3,opts)) .then(() => t.rollback()) .catch(err => assert.exists(err)); }) - .then(assertCount(sequelize.models.UserRecord,3)); + .then(assertCount(sequelize.models.UserRecord,6)); }); }); From e2cfdf59fdadeff77313c089d3747ca419ba31ed Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Tue, 30 Apr 2019 22:47:56 +0900 Subject: [PATCH 24/37] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b79889..95753fd 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Record tables for Sequelize Warning: this is a fork of [sequelize-temporal](https://github.com/bonaval/sequelize-temporal) that adds the ability to associate data history table to origin tables (table a record is based on) and to specify a different name for the __Record__ tables. -[![Build Status](https://travis-ci.org/kurisutofu/sequelize-record.svg?branch=master)](https://travis-ci.org/opencollective/sequelize-record) [![Dependency Status](https://david-dm.org/kurisutofu/sequelize-record.svg)](https://david-dm.org/opencollective/sequelize-record) [![NPM version](https://img.shields.io/npm/v/sequelize-record.svg)](https://www.npmjs.com/package/sequelize-record) [![Greenkeeper badge](https://badges.greenkeeper.io/kurisutofu/sequelize-record.svg)](https://greenkeeper.io/) +[![Build Status](https://travis-ci.org/kurisutofu/sequelize-record.svg?branch=master)](https://travis-ci.org/kurisutofu/sequelize-record) [![Dependency Status](https://david-dm.org/kurisutofu/sequelize-record.svg)](https://david-dm.org/kurisutofu/sequelize-record) [![NPM version](https://img.shields.io/npm/v/sequelize-record.svg)](https://www.npmjs.com/package/sequelize-record) [![Greenkeeper badge](https://badges.greenkeeper.io/kurisutofu/sequelize-record.svg)](https://greenkeeper.io/) What is it? From 4aefbf8792f1165b5bf4a2ba0cdedc446ec9ffb3 Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Tue, 30 Apr 2019 22:48:28 +0900 Subject: [PATCH 25/37] Updating version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f30a708..0de02f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sequelize-record", - "version": "1.0.0", + "version": "1.0.1", "description": "Record tables for Sequelize", "main": "index.js", "directories": { From 26eda4cd124ca43a7b3b6ca2b5686ca40f6124f9 Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Tue, 30 Apr 2019 22:54:57 +0900 Subject: [PATCH 26/37] Adding Travis --- .travis.yml | 6 ++---- package.json | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index f2b2cf6..de89cef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,4 @@ language: node_js node_js: - - "9.0" - - "8.0" - - "7.0" - - "6.0" + - "11.14.0" + - "11.0" diff --git a/package.json b/package.json index 0de02f6..70b94e7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sequelize-record", - "version": "1.0.1", + "version": "1.0.2", "description": "Record tables for Sequelize", "main": "index.js", "directories": { From 8a507bff66d55f1e9d05079074b4c497844bcdc1 Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Wed, 8 May 2019 23:26:24 +0900 Subject: [PATCH 27/37] Moved logic to use beforeBulkSync --- index.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index faa47b2..7c22b8b 100644 --- a/index.js +++ b/index.js @@ -80,14 +80,18 @@ var Record = function(model, sequelize, recordOptions) { } } -// var afterBulkSyncHook = function(options){ -// sequelize.removeHook('beforeBulkSync', 'RecordBulkSyncHook'); -// sequelize.removeHook('afterBulkSync', 'RecordBulkSyncHook'); -// return Promise.resolve('Record Hooks Removed'); -// } + var beforeBulkSyncHook = function(options){ + const allModels = this.models; + + Object.keys(allModels).forEach(key => { + const source = allModels[key]; + beforeSync(source); + }); + + return Promise.resolve('Record associations established'); + } - var beforeSync = function(options) { - const source = this; + var beforeSync = function(source) { const sourceHistName = source.name + recordOptions.modelSuffix; const sourceHist = sequelize.models[sourceHistName]; @@ -142,7 +146,10 @@ var Record = function(model, sequelize, recordOptions) { modelRecord.addHook('beforeUpdate', readOnlyHook); modelRecord.addHook('beforeDestroy', readOnlyHook); - model.addHook('beforeSync', 'RecordSyncHook', beforeSync); + if(!sequelize.hasHook('beforeBulkSync','RecordBulkSyncHook')) { + sequelize.addHook('beforeBulkSync', 'RecordBulkSyncHook', beforeBulkSyncHook); + } + modelRecord.addAssociations = recordOptions.addAssociations; return model; From c5dd2c083830b65553a8f7cdf0fab926904e798d Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Thu, 9 May 2019 00:23:40 +0900 Subject: [PATCH 28/37] Reverted to BeforeInsert + Reverted to BeforeInsert instead of BeforeBulkInsert + Updated logic to sync with historical + Added origin model as a property of the historical model --- .npmignore | 7 +++++++ index.js | 29 +++++++---------------------- package.json | 2 +- 3 files changed, 15 insertions(+), 23 deletions(-) create mode 100644 .npmignore diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..a2d6e71 --- /dev/null +++ b/.npmignore @@ -0,0 +1,7 @@ +.test.sqlite +node_modules +package-lock.json +server.js +.vscode +/test +.git \ No newline at end of file diff --git a/index.js b/index.js index 7c22b8b..f156f9b 100644 --- a/index.js +++ b/index.js @@ -57,6 +57,7 @@ var Record = function(model, sequelize, recordOptions) { } var modelRecord = sequelize.define(recordName, recordAttributes, recordModelOptions); + modelRecord.originModel = model; // we already get the updatedAt timestamp from our models var insertHook = function(obj, options){ @@ -80,22 +81,11 @@ var Record = function(model, sequelize, recordOptions) { } } - var beforeBulkSyncHook = function(options){ - const allModels = this.models; + var beforeSync = function(options) { + const source = this.originModel; + const sourceHist = this; - Object.keys(allModels).forEach(key => { - const source = allModels[key]; - beforeSync(source); - }); - - return Promise.resolve('Record associations established'); - } - - var beforeSync = function(source) { - const sourceHistName = source.name + recordOptions.modelSuffix; - const sourceHist = sequelize.models[sourceHistName]; - - if(!source.name.endsWith(recordOptions.modelSuffix) && source.associations && recordOptions.addAssociations == true && sourceHist) { + if(source && !source.name.endsWith(recordOptions.modelSuffix) && source.associations && recordOptions.addAssociations == true && sourceHist) { const pkfield = source.primaryKeyField; //adding associations from record model to origin model's association Object.keys(source.associations).forEach(assokey => { @@ -117,8 +107,7 @@ var Record = function(model, sequelize, recordOptions) { source.hasMany(sourceHist, { foreignKey: pkfield }); sourceHist.belongsTo(source, { foreignKey: pkfield }); - sequelize.models[sourceHistName] = sourceHist; - sequelize.models[sourceHistName].sync(); + sequelize.models[sourceHist.name] = sourceHist; } return Promise.resolve('Record associations established'); @@ -145,11 +134,7 @@ var Record = function(model, sequelize, recordOptions) { modelRecord.addHook('beforeUpdate', readOnlyHook); modelRecord.addHook('beforeDestroy', readOnlyHook); - - if(!sequelize.hasHook('beforeBulkSync','RecordBulkSyncHook')) { - sequelize.addHook('beforeBulkSync', 'RecordBulkSyncHook', beforeBulkSyncHook); - } - + modelRecord.addHook('beforeSync', 'RecordSyncHook', beforeSync); modelRecord.addAssociations = recordOptions.addAssociations; return model; diff --git a/package.json b/package.json index 70b94e7..ec54000 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sequelize-record", - "version": "1.0.2", + "version": "1.0.5", "description": "Record tables for Sequelize", "main": "index.js", "directories": { From 4485abcc2efdeb37314994fc12aadcdb3790b208 Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Thu, 9 May 2019 00:35:27 +0900 Subject: [PATCH 29/37] Updating name back to sequelize-historical --- README.md | 106 ++++++++-------- index.js | 26 ++-- package.json | 4 +- test/test.js | 352 +++++++++++++++++++++++++-------------------------- 4 files changed, 244 insertions(+), 244 deletions(-) diff --git a/README.md b/README.md index 95753fd..abcf7a6 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,42 @@ -Record tables for Sequelize +History tables for Sequelize =============================== -Warning: this is a fork of [sequelize-temporal](https://github.com/bonaval/sequelize-temporal) that adds the ability to associate data history table to origin tables (table a record is based on) and to specify a different name for the __Record__ tables. +Warning: this is a fork of [sequelize-temporal](https://github.com/bonaval/sequelize-temporal) that adds support for Sequelize 5 and that adds the ability to associate data history table to origin tables (table a history is based on) and to specify a different name for the __History__ tables. -[![Build Status](https://travis-ci.org/kurisutofu/sequelize-record.svg?branch=master)](https://travis-ci.org/kurisutofu/sequelize-record) [![Dependency Status](https://david-dm.org/kurisutofu/sequelize-record.svg)](https://david-dm.org/kurisutofu/sequelize-record) [![NPM version](https://img.shields.io/npm/v/sequelize-record.svg)](https://www.npmjs.com/package/sequelize-record) [![Greenkeeper badge](https://badges.greenkeeper.io/kurisutofu/sequelize-record.svg)](https://greenkeeper.io/) +[![Build Status](https://travis-ci.org/opencollective/sequelize-historical.svg?branch=master)](https://travis-ci.org/opencollective/sequelize-historical) [![Dependency Status](https://david-dm.org/opencollective/sequelize-historical.svg)](https://david-dm.org/opencollective/sequelize-historical) [![NPM version](https://img.shields.io/npm/v/sequelize-historical.svg)](https://www.npmjs.com/package/sequelize-historical) [![Greenkeeper badge](https://badges.greenkeeper.io/opencollective/sequelize-historical.svg)](https://greenkeeper.io/) What is it? ----------- -___Record__ tables maintain __previous values__ of data. Modifying operations (UPDATE, DELETE) on these tables don't cause permanent changes to entries, but create new versions of them. Hence this might be used to: +___History__ tables maintain __previous values__ of data. Modifying operations (UPDATE, DELETE) on these tables don't cause permanent changes to entries, but create new versions of them. Hence this might be used to: - log changes (security/auditing) - undo functionalities - track interactions (customer support) -Under the hood a record table with the same structure, but without constraints is created (unless option __addAssociation__ is set to __true__). +Under the hood a history table with the same structure, but without constraints is created (unless option __addAssociation__ is set to __true__). The normal singular/plural naming scheme in Sequelize is used: -- model name: `modelName + Record` -- table name: `modelName + Records` +- model name: `modelName + History` +- table name: `modelName + Histories` Installation ------------ ``` -npm install sequelize-record +npm install sequelize-historical ``` How to use ---------- -### 1) Import `sequelize-record` +### 1) Import `sequelize-historical` ``` var Sequelize = require('sequelize'); -var Record = require('sequelize-record'); +var History = require('sequelize-historical'); ``` Create a sequelize instance and your models, e.g. @@ -48,26 +48,26 @@ var sequelize = new Sequelize('', '', '', { }); ``` -### 2) Add the *record* feature to your models +### 2) Add the *history* feature to your models ``` -var User = Record(sequelize.define('User'), sequelize); +var User = History(sequelize.define('User'), sequelize); ``` -The output of `Record` is its input model, so assigning it's output to your +The output of `History` is its input model, so assigning it's output to your Model is not necessary, hence it's just the lazy version of: ``` var User = sequelize.define('User', {.types.}, {.options.}); //Vanilla Sequelize -Record(User, sequelize); +History(User, sequelize); ``` Options ------- -The default syntax for `Record` is: +The default syntax for `History` is: -`Record(model, sequelizeInstance, options)` +`History(model, sequelizeInstance, options)` whereas the options are listed here (with default value). @@ -78,70 +78,70 @@ whereas the options are listed here (with default value). for increased performance without warranties */ blocking: true, /* - By default sequelize-record persist only changes, and saves the previous state in the record table. - The "full" option saves all transactions into the record database + By default sequelize-historical persist only changes, and saves the previous state in the history table. + The "full" option saves all transactions into the history database (i.e. this includes the latest state.) - This allows to only query the record table to get the full record of an entity. + This allows to only query the history table to get the full history of an entity. */ full: false, /* - By default sequelize-record will add 'Record' to the record Model name and 'Records' to the record table. + By default sequelize-historical will add 'History' to the history Model name and 'Histories' to the history table. By updating the modelSuffix value, you can decide what the naming will be. - The value will be appended to the record Model name and its plural will be appended to the record tablename. + The value will be appended to the history Model name and its plural will be appended to the history tablename. examples for table User: - modelSuffix: '_Hist' --> Record Model Name: User_Hist --> Record Table Name: User_Hists - modelSuffix: 'Memory' --> Record Model Name: UserMemory --> Record Table Name: UserMemories - modelSuffix: 'Pass' --> Record Model Name: UserPass --> Record Table Name: UserPasses + modelSuffix: '_Hist' --> History Model Name: User_Hist --> History Table Name: User_Hists + modelSuffix: 'Memory' --> History Model Name: UserMemory --> History Table Name: UserMemories + modelSuffix: 'Pass' --> History Model Name: UserPass --> History Table Name: UserPasses */ - modelSuffix: 'Record', + modelSuffix: 'History', /* - By default sequelize-record will create the record table without associations. - However, setting this flag to true, you can keep association between the record table and the table with the latest value (origin). + By default sequelize-historical will create the history table without associations. + However, setting this flag to true, you can keep association between the history table and the table with the latest value (origin). example for table User: model: 'User' - record model: 'UserRecords' - --> This would add function User.getUserRecords() to return all record entries for that user entry. - --> This would add function UserRecords.getUser() to get the original user from an record. + history model: 'UserHistories' + --> This would add function User.getUserHistories() to return all history entries for that user entry. + --> This would add function UserHistories.getUser() to get the original user from an history. - If a model has associations, those would be mirrored to the record table. - Origin model can only get its own records. - Even if a record table is associated to another origin table thought a foreign key field, the record table is not accessible from that origin table + If a model has associations, those would be mirrored to the history table. + Origin model can only get its own histories. + Even if a history table is associated to another origin table thought a foreign key field, the history table is not accessible from that origin table - Basically, what you can access in the origin table can be accessed from the record table. + Basically, what you can access in the origin table can be accessed from the history table. example: model: User - record model: UserRecords + history model: UserHistories model: Creation - record model: CreationRecords + history model: CreationHistories User <-> Creation: 1 to many User.getCreations() exists (1 to many) Creation.getUser() exists (1 to 1) - User <-> UserRecords: 1 to many + User <-> UserHistories: 1 to many - User.getUserRecords() exists (1 to many) - UserRecords.getUser() exists (1 to 1) + User.getUserHistories() exists (1 to many) + UserHistories.getUser() exists (1 to 1) - Creation <-> CreationRecords: 1 to many + Creation <-> CreationHistories: 1 to many - Creation.getCreationRecords() exists (1 to many) - CreationRecords.getCreation() exists (1 to 1) + Creation.getCreationHistories() exists (1 to many) + CreationHistories.getCreation() exists (1 to 1) - CreationRecords -> User: many to 1 + CreationHistories -> User: many to 1 - CreationRecords.getUser() exists (1 to 1) (same as Creation.getUser()) - User.GetCreationRecords DOES NOT EXIST. THE ORIGIN TABLE IS NOT MODIFIED. + CreationHistories.getUser() exists (1 to 1) (same as Creation.getUser()) + User.GetCreationHistories DOES NOT EXIST. THE ORIGIN TABLE IS NOT MODIFIED. - UserRecords -> Creation: many to many + UserHistories -> Creation: many to many - UserRecords.getCreations() exists (1 to many) (same as User.getCreations()) - CreationRecords.getUser() DOES NOT EXIST. THE ORIGIN TABLE IS NOT MODIFIED. + UserHistories.getCreations() exists (1 to many) (same as User.getCreations()) + CreationHistories.getUser() DOES NOT EXIST. THE ORIGIN TABLE IS NOT MODIFIED. */ addAssociations: false @@ -150,19 +150,19 @@ whereas the options are listed here (with default value). Details -------- -@See: https://wiki.postgresql.org/wiki/SQL2011Record +@See: https://wiki.postgresql.org/wiki/SQL2011History -### Record table +### History table -Record table stores record versions of rows, which are inserted by triggers on every modifying operation executed on current table. It has the same structure and indexes as current table, but it doesn’t have any constraints. Record tables are insert only and creator should prevent other users from executing updates or deletes by correct user rights settings. Otherwise the record can be violated. +History table stores history versions of rows, which are inserted by triggers on every modifying operation executed on current table. It has the same structure and indexes as current table, but it doesn’t have any constraints. History tables are insert only and creator should prevent other users from executing updates or deletes by correct user rights settings. Otherwise the history can be violated. ### Hooks -Triggers for storing old versions of rows to record table are inspired by referential integrity triggers. They are fired for each row before UPDATE and DELETE (within the same transaction) +Triggers for storing old versions of rows to history table are inspired by referential integrity triggers. They are fired for each row before UPDATE and DELETE (within the same transaction) ### Notes -If you only use Postgres, you might want to have a look at the [Record Table](https://github.com/arkhipov/record_tables) extension. +If you only use Postgres, you might want to have a look at the [History Table](https://github.com/arkhipov/history_tables) extension. License ------- diff --git a/index.js b/index.js index f156f9b..a7d14d7 100644 --- a/index.js +++ b/index.js @@ -5,11 +5,11 @@ var recordDefaultOptions = { // for increased performance blocking: true, full: false, - modelSuffix: 'Record', + modelSuffix: 'History', addAssociations: false }; -var Record = function(model, sequelize, recordOptions) { +var Historical = function(model, sequelize, recordOptions) { recordOptions = _.extend({},recordDefaultOptions, recordOptions); var Sequelize = sequelize.Sequelize; @@ -56,15 +56,16 @@ var Record = function(model, sequelize, recordOptions) { recordModelOptions.indexes = indexes.filter(function(index){return !index.unique && index.type != 'UNIQUE';}); } - var modelRecord = sequelize.define(recordName, recordAttributes, recordModelOptions); - modelRecord.originModel = model; + var modelHistory = sequelize.define(recordName, recordAttributes, recordModelOptions); + modelHistory.originModel = model; + modelHistory.addAssociations = recordOptions.addAssociations; // we already get the updatedAt timestamp from our models var insertHook = function(obj, options){ var dataValues = (!recordOptions.full && obj._previousDataValues) || obj.dataValues; - var recordRecord = modelRecord.create(dataValues, {transaction: options.transaction}); + var recordHistory = modelHistory.create(dataValues, {transaction: options.transaction}); if(recordOptions.blocking){ - return recordRecord; + return recordHistory; } } var insertBulkHook = function(options){ @@ -72,7 +73,7 @@ var Record = function(model, sequelize, recordOptions) { var queryAll = model.findAll({where: options.where, transaction: options.transaction}).then(function(hits){ if(hits){ hits = _.map(hits, 'dataValues'); - return modelRecord.bulkCreate(hits, {transaction: options.transaction}); + return modelHistory.bulkCreate(hits, {transaction: options.transaction}); } }); if(recordOptions.blocking){ @@ -110,7 +111,7 @@ var Record = function(model, sequelize, recordOptions) { sequelize.models[sourceHist.name] = sourceHist; } - return Promise.resolve('Record associations established'); + return Promise.resolve('Historical associations established'); } // use `after` to be nonBlocking @@ -132,12 +133,11 @@ var Record = function(model, sequelize, recordOptions) { throw new Error("This is a read-only record database. You aren't allowed to modify it."); }; - modelRecord.addHook('beforeUpdate', readOnlyHook); - modelRecord.addHook('beforeDestroy', readOnlyHook); - modelRecord.addHook('beforeSync', 'RecordSyncHook', beforeSync); + modelHistory.addHook('beforeUpdate', readOnlyHook); + modelHistory.addHook('beforeDestroy', readOnlyHook); + modelHistory.addHook('beforeSync', 'HistoricalSyncHook', beforeSync); - modelRecord.addAssociations = recordOptions.addAssociations; return model; }; -module.exports = Record; +module.exports = Historical; diff --git a/package.json b/package.json index ec54000..a15fd55 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "sequelize-record", + "name": "sequelize-historical", "version": "1.0.5", - "description": "Record tables for Sequelize", + "description": "Historical tables for Sequelize", "main": "index.js", "directories": { "test": "test" diff --git a/test/test.js b/test/test.js index 6f2574b..b03c02d 100644 --- a/test/test.js +++ b/test/test.js @@ -1,4 +1,4 @@ -const Record = require('../'); +const Historical = require('../'); const Sequelize = require('sequelize'); const chai = require("chai"); const chaiAsPromised = require("chai-as-promised"); @@ -49,12 +49,12 @@ describe('Read-only API', function(){ Tag.belongsToMany(Creation, { through: CreationTag, foreignKey: 'tag', otherKey: 'creation' }); Creation.belongsToMany(Tag, { through: CreationTag, foreignKey: 'creation', otherKey: 'tag' }); - //Recordize - Record(User, sequelize, options); - Record(Creation, sequelize, options); - Record(Tag, sequelize, options); - Record(Event, sequelize, options); - Record(CreationTag, sequelize, options); + //Historyize + Historical(User, sequelize, options); + Historical(Creation, sequelize, options); + Historical(Tag, sequelize, options); + Historical(Event, sequelize, options); + Historical(CreationTag, sequelize, options); return sequelize.sync({force:true}); } @@ -63,8 +63,8 @@ describe('Read-only API', function(){ //each creation has 3 tags //user has 2 creations //creation has 1 event - //tags,crestions,user,events are renamed 3 times to generate 3 record data - //1 tag is removed and re-added to a creation to create 1 record entry in the CreationTags table + //tags,crestions,user,events are renamed 3 times to generate 3 history data + //1 tag is removed and re-added to a creation to create 1 history entry in the CreationTags table function dataCreate() { const tag = sequelize.models.Tag.create({ name: 'tag01' }).then( t => { @@ -256,29 +256,29 @@ describe('Read-only API', function(){ return newDB(false, { modelSuffix: '_Hist'}); } - function assertCount(modelRecord, n, opts) { + function assertCount(modelHistory, n, opts) { // wrapped, chainable promise return function(obj) { - return modelRecord.count(opts).then((count) => { - //console.log('Asserting ', modelRecord.name, ' count: ', count, ' expected: ', n); - assert.equal(n, count, "record entries") + return modelHistory.count(opts).then((count) => { + //console.log('Asserting ', modelHistory.name, ' count: ', count, ' expected: ', n); + assert.equal(n, count, "history entries") return obj; }); } } describe('Association Tests', function() { - describe('test there are no record association', function(){ + describe('test there are no history association', function(){ beforeEach(freshDB); - it('Should have relations for origin models but not for record models' , function(){ + it('Should have relations for origin models but not for history models' , function(){ const init = dataCreate(); //Get User const user = init.then(() => sequelize.models.User.findOne()); //User associations check - const userRecord = user.then(u =>{ - assert.notExists(u.getUserRecords, 'User: getUserRecords exists'); + const userHistory = user.then(u =>{ + assert.notExists(u.getUserHistories, 'User: getUserHistories exists'); return Promise.resolve('done'); }); @@ -289,10 +289,10 @@ describe('Read-only API', function(){ }); //Creation associations check - const creationRecord = creation.then(c =>{ + const creationHistory = creation.then(c =>{ assert.equal(c.length, 2, 'User: should have found 2 creations'); const first = c[0]; - assert.notExists(first.getCreationRecords, 'Creation: getCreationRecords exists'); + assert.notExists(first.getCreationHistories, 'Creation: getCreationHistories exists'); return Promise.resolve('done'); }); @@ -319,10 +319,10 @@ describe('Read-only API', function(){ }); //Tag associations check - const tagRecord = tag.then(t =>{ + const tagHistory = tag.then(t =>{ assert.equal(t.length, 3, 'Creation: should have found 3 tags'); const first = t[0]; - assert.notExists(first.getTagRecords, 'Tag: getTagRecords exists'); + assert.notExists(first.getTagHistories, 'Tag: getTagHistories exists'); return Promise.resolve('done'); }); @@ -336,9 +336,9 @@ describe('Read-only API', function(){ }); //Event associations check - const eventRecord = event.then(e =>{ + const eventHistory = event.then(e =>{ assert.exists(e, 'Creation: did not find event'); - assert.notExists(e.getEventRecords, 'Event: getEventRecords exist'); + assert.notExists(e.getEventHistories, 'Event: getEventHistories exist'); return Promise.resolve('done'); }); @@ -350,47 +350,47 @@ describe('Read-only API', function(){ return Promise.resolve('done'); }); - //Check record data - const userRecords = init.then(assertCount(sequelize.models.UserRecord, 6)); - const creationRecords = init.then(assertCount(sequelize.models.CreationRecord, 6)); - const tagRecords = init.then(assertCount(sequelize.models.TagRecord, 9)); - const eventRecords = init.then(assertCount(sequelize.models.EventRecord, 6)); - const creationTagRecords = init.then(assertCount(sequelize.models.CreationTagRecord, 1)); + //Check history data + const userHistories = init.then(assertCount(sequelize.models.UserHistory, 6)); + const creationHistories = init.then(assertCount(sequelize.models.CreationHistory, 6)); + const tagHistories = init.then(assertCount(sequelize.models.TagHistory, 9)); + const eventHistories = init.then(assertCount(sequelize.models.EventHistory, 6)); + const creationTagHistories = init.then(assertCount(sequelize.models.CreationTagHistory, 1)); return Promise.all([ creation, - creationRecords, - creationRecord, - creationTagRecords, + creationHistories, + creationHistory, + creationTagHistories, cUser, eCreation, event, - eventRecords, - eventRecord, + eventHistories, + eventHistory, init, tag, - tagRecords, - tagRecord, + tagHistories, + tagHistory, tCreation, user, - userRecords, - userRecord + userHistories, + userHistory ]); }); }); - describe('test there are associations are created between origin and record', function(){ + describe('test there are associations are created between origin and history', function(){ beforeEach(freshDBWithAssociations); - it('Should have relations for origin models and for record models to origin' , function(){ + it('Should have relations for origin models and for history models to origin' , function(){ const init = dataCreate(); //Get User const user = init.then(() => sequelize.models.User.findOne()); //User associations check - const userRecord = user.then(u =>{ - assert.exists(u.getUserRecords, 'User: getUserRecords does not exist'); - return u.getUserRecords(); + const userHistory = user.then(u =>{ + assert.exists(u.getUserHistories, 'User: getUserHistories does not exist'); + return u.getUserHistories(); }); const creation = user.then(u =>{ @@ -399,33 +399,33 @@ describe('Read-only API', function(){ return u.getCreatorCreations(); }); - //UserRecords associations check - const uhCreation = userRecord.then(uh =>{ - assert.equal(uh.length, 3, 'User: should have found 3 UserRecords'); + //UserHistories associations check + const uhCreation = userHistory.then(uh =>{ + assert.equal(uh.length, 3, 'User: should have found 3 UserHistories'); const first = uh[0]; - assert.exists(first.getCreatorCreations, 'UserRecord: getCreatorCreations does not exist'); - assert.exists(first.getUpdatorCreations, 'UserRecord: getUpdatorCreations does not exist'); + assert.exists(first.getCreatorCreations, 'UserHistory: getCreatorCreations does not exist'); + assert.exists(first.getUpdatorCreations, 'UserHistory: getUpdatorCreations does not exist'); return first.getCreatorCreations(); }).then(uhc => { - assert.equal(uhc.length, 2, 'UserRecord: should have found 2 creations'); + assert.equal(uhc.length, 2, 'UserHistory: should have found 2 creations'); return Promise.resolve('done'); }); - const uhUser = userRecord.then(uh =>{ + const uhUser = userHistory.then(uh =>{ const first = uh[0]; - assert.exists(first.getUser, 'UserRecord: getUser does not exist'); + assert.exists(first.getUser, 'UserHistory: getUser does not exist'); return first.getUser(); }).then(uhu => { - assert.exists(uhu, 'UserRecord: did not find a user'); + assert.exists(uhu, 'UserHistory: did not find a user'); return Promise.resolve('done'); }); //Creation associations check - const creationRecord = creation.then(c =>{ + const creationHistory = creation.then(c =>{ assert.equal(c.length, 2, 'User: should have found 2 creations'); const first = c[0]; - assert.exists(first.getCreationRecords, 'Creation: getCreationRecords does not exist'); - return first.getCreationRecords(); + assert.exists(first.getCreationHistories, 'Creation: getCreationHistories does not exist'); + return first.getCreationHistories(); }); const tag = creation.then(c =>{ @@ -450,52 +450,52 @@ describe('Read-only API', function(){ return Promise.resolve('done'); }); - //CreationRecords association check - const chCreation = creationRecord.then(ch =>{ - assert.equal(ch.length, 3, 'Creation: should have found 3 CreationRecords'); + //CreationHistories association check + const chCreation = creationHistory.then(ch =>{ + assert.equal(ch.length, 3, 'Creation: should have found 3 CreationHistories'); const first = ch[0]; - assert.exists(first.getCreation, 'CreationRecord: getCreation does not exist'); + assert.exists(first.getCreation, 'CreationHistory: getCreation does not exist'); return first.getCreation(); }).then(chc => { - assert.exists(chc, 'CreationRecord: did noy find a creation'); + assert.exists(chc, 'CreationHistory: did noy find a creation'); return Promise.resolve('done'); }); - const chTag = creationRecord.then(ch =>{ + const chTag = creationHistory.then(ch =>{ const first = ch[0]; - assert.exists(first.getTags, 'CreationRecord: getTags does not exist'); + assert.exists(first.getTags, 'CreationHistory: getTags does not exist'); return first.getTags(); }).then(uht => { assert.equal(uht.length, 3); return Promise.resolve('done'); }); - const chUser = creationRecord.then(ch =>{ + const chUser = creationHistory.then(ch =>{ const first = ch[0]; - assert.exists(first.getCreateUser, 'CreationRecord: getCreateUser does not exist'); - assert.exists(first.getUpdateUser, 'CreationRecord: getUpdateUser does not exist'); + assert.exists(first.getCreateUser, 'CreationHistory: getCreateUser does not exist'); + assert.exists(first.getUpdateUser, 'CreationHistory: getUpdateUser does not exist'); return first.getCreateUser(); }).then(chu => { - assert.exists(chu, 'CreationRecord: did not find a user'); + assert.exists(chu, 'CreationHistory: did not find a user'); return Promise.resolve('done'); }); - const chEvent = creationRecord.then(ch =>{ + const chEvent = creationHistory.then(ch =>{ const first = ch[0]; - assert.exists(first.getEvent, 'CreationRecord: getEvent does not exist'); + assert.exists(first.getEvent, 'CreationHistory: getEvent does not exist'); return first.getEvent(); }).then(che => { - assert.exists(che, 'CreationRecord: did not find an event'); + assert.exists(che, 'CreationHistory: did not find an event'); return Promise.resolve('done'); }); //Tag associations check - const tagRecord = tag.then(t =>{ + const tagHistory = tag.then(t =>{ assert.equal(t.length, 3, 'Creation: should have found 3 tags'); const first = t[0]; - assert.exists(first.getTagRecords, 'Tag: getTagRecords does not exist'); - return first.getTagRecords(); + assert.exists(first.getTagHistories, 'Tag: getTagHistories does not exist'); + return first.getTagHistories(); }); const tCreation = tag.then(t =>{ @@ -507,31 +507,31 @@ describe('Read-only API', function(){ return Promise.resolve('done'); }); - //TagRecords associations check - const thTag = tagRecord.then(th =>{ - assert.equal(th.length, 3, 'TagRecord: should have found 3 TagRecords'); + //TagHistories associations check + const thTag = tagHistory.then(th =>{ + assert.equal(th.length, 3, 'TagHistory: should have found 3 TagHistories'); const first = th[0]; - assert.exists(first.getTag, 'TagRecord: getTag does not exist'); + assert.exists(first.getTag, 'TagHistory: getTag does not exist'); return first.getTag(); }).then(tht => { - assert.exists(tht, 'TagRecord: did not find a tag'); + assert.exists(tht, 'TagHistory: did not find a tag'); return Promise.resolve('done'); }); - const thCreation = tagRecord.then(th =>{ + const thCreation = tagHistory.then(th =>{ const first = th[0]; - assert.exists(first.getCreations, 'TagRecord: getCreations does not exist'); + assert.exists(first.getCreations, 'TagHistory: getCreations does not exist'); return first.getCreations(); }).then(thc => { - assert.equal(thc.length, 2, 'TagRecord: should have found 2 creations'); + assert.equal(thc.length, 2, 'TagHistory: should have found 2 creations'); return Promise.resolve('done'); }); //Event associations check - const eventRecord = event.then(e =>{ + const eventHistory = event.then(e =>{ assert.exists(e, 'Creation: did not find an event'); - assert.exists(e.getEventRecords, 'Event: getEventRecords does not exist'); - return e.getEventRecords(); + assert.exists(e.getEventHistories, 'Event: getEventHistories does not exist'); + return e.getEventHistories(); }); const eCreation = event.then(e =>{ @@ -542,32 +542,32 @@ describe('Read-only API', function(){ return Promise.resolve('done'); }); - //EventRecords associations check - const ehEvent = eventRecord.then(eh =>{ - assert.equal(eh.length, 3, 'Event: should have found 3 EventRecords'); + //EventHistories associations check + const ehEvent = eventHistory.then(eh =>{ + assert.equal(eh.length, 3, 'Event: should have found 3 EventHistories'); const first = eh[0]; - assert.exists(first.getEvent, 'EventRecords: getEvent does not exist'); + assert.exists(first.getEvent, 'EventHistories: getEvent does not exist'); return first.getEvent(); }).then(ehe => { - assert.exists(ehe, 'EventRecords: did not find an event'); + assert.exists(ehe, 'EventHistories: did not find an event'); return Promise.resolve('done'); }); - const ehCreation = eventRecord.then(eh =>{ + const ehCreation = eventHistory.then(eh =>{ const first = eh[0]; - assert.exists(first.getCreation, 'EventRecords: getCreation does not exist'); + assert.exists(first.getCreation, 'EventHistories: getCreation does not exist'); return first.getCreation(); }).then(ehc => { - assert.exists(ehc, 'EventRecords: did not find a creation'); + assert.exists(ehc, 'EventHistories: did not find a creation'); return Promise.resolve('done'); }); - //Check record data - const userRecords = init.then(assertCount(sequelize.models.UserRecord, 6)); - const creationRecords = init.then(assertCount(sequelize.models.CreationRecord, 6)); - const tagRecords = init.then(assertCount(sequelize.models.TagRecord, 9)); - const eventRecords = init.then(assertCount(sequelize.models.EventRecord, 6)); - const creationTagRecords = init.then(assertCount(sequelize.models.CreationTagRecord, 1)); + //Check history data + const userHistories = init.then(assertCount(sequelize.models.UserHistory, 6)); + const creationHistories = init.then(assertCount(sequelize.models.CreationHistory, 6)); + const tagHistories = init.then(assertCount(sequelize.models.TagHistory, 9)); + const eventHistories = init.then(assertCount(sequelize.models.EventHistory, 6)); + const creationTagHistories = init.then(assertCount(sequelize.models.CreationTagHistory, 1)); return Promise.all([ @@ -576,26 +576,26 @@ describe('Read-only API', function(){ chTag, chUser, creation, - creationRecords, - creationRecord, - creationTagRecords, + creationHistories, + creationHistory, + creationTagHistories, cUser, eCreation, event, - eventRecords, - eventRecord, + eventHistories, + eventHistory, init, tag, - tagRecords, - tagRecord, + tagHistories, + tagHistory, tCreation, thCreation, thTag, uhCreation, uhUser, user, - userRecords, - userRecord, + userHistories, + userHistory, ehEvent, ehCreation ]); @@ -607,11 +607,11 @@ describe('Read-only API', function(){ //Only added is to test for the model name describe('test suffix ending in T', function() { beforeEach(freshDBWithSuffixEndingWithT); - it('onCreate: should not store the new version in record db' , function() { + it('onCreate: should not store the new version in history db' , function() { return sequelize.models.User.create({ name: 'test' }) .then(assertCount(sequelize.models.User_Hist, 0)); }); - it('onUpdate/onDestroy: should save to the recordDB' , function() { + it('onUpdate/onDestroy: should save to the historyDB' , function() { return sequelize.models.User.create() .then(assertCount(sequelize.models.User_Hist,0)) .then((user) => { @@ -622,7 +622,7 @@ describe('Read-only API', function(){ .then(user => user.destroy()) .then(assertCount(sequelize.models.User_Hist,2)); }); - it('onUpdate: should store the previous version to the recordDB' , function() { + it('onUpdate: should store the previous version to the historyDB' , function() { return sequelize.models.User.create({name: "foo"}) .then(assertCount(sequelize.models.User_Hist,0)) .then((user) => { @@ -639,7 +639,7 @@ describe('Read-only API', function(){ .then((user) => user.destroy()) .then(assertCount(sequelize.models.User_Hist,2)) }); - it('onDelete: should store the previous version to the recordDB' , function() { + it('onDelete: should store the previous version to the historyDB' , function() { return sequelize.models.User.create({name: "foo"}) .then(assertCount(sequelize.models.User_Hist,0)) .then(user => user.destroy()) @@ -654,43 +654,43 @@ describe('Read-only API', function(){ describe('hooks', function() { beforeEach(freshDB); - it('onCreate: should not store the new version in record db' , function() { + it('onCreate: should not store the new version in history db' , function() { return sequelize.models.User.create({ name: 'test' }) - .then(assertCount(sequelize.models.UserRecord, 0)); + .then(assertCount(sequelize.models.UserHistory, 0)); }); - it('onUpdate/onDestroy: should save to the recordDB' , function() { + it('onUpdate/onDestroy: should save to the historyDB' , function() { return sequelize.models.User.create() - .then(assertCount(sequelize.models.UserRecord,0)) + .then(assertCount(sequelize.models.UserHistory,0)) .then((user) => { user.name = "foo"; return user.save(); }) - .then(assertCount(sequelize.models.UserRecord,1)) + .then(assertCount(sequelize.models.UserHistory,1)) .then(user => user.destroy()) - .then(assertCount(sequelize.models.UserRecord,2)) + .then(assertCount(sequelize.models.UserHistory,2)) }); - it('onUpdate: should store the previous version to the recordDB' , function() { + it('onUpdate: should store the previous version to the historyDB' , function() { return sequelize.models.User.create({name: "foo"}) - .then(assertCount(sequelize.models.UserRecord,0)) + .then(assertCount(sequelize.models.UserHistory,0)) .then((user) => { user.name = "bar"; return user.save(); }) - .then(assertCount(sequelize.models.UserRecord,1)) - .then(() => sequelize.models.UserRecord.findAll()) + .then(assertCount(sequelize.models.UserHistory,1)) + .then(() => sequelize.models.UserHistory.findAll()) .then((users) => { assert.equal(users.length,1, "only one entry in DB"); assert.equal(users[0].name, "foo", "previous entry saved"); }).then(user => sequelize.models.User.findOne()) .then(user => user.destroy()) - .then(assertCount(sequelize.models.UserRecord,2)) + .then(assertCount(sequelize.models.UserHistory,2)) }); - it('onDelete: should store the previous version to the recordDB' , function() { + it('onDelete: should store the previous version to the historyDB' , function() { return sequelize.models.User.create({name: "foo"}) - .then(assertCount(sequelize.models.UserRecord,0)) + .then(assertCount(sequelize.models.UserHistory,0)) .then(user => user.destroy()) - .then(assertCount(sequelize.models.UserRecord,1)) - .then(() => sequelize.models.UserRecord.findAll()) + .then(assertCount(sequelize.models.UserHistory,1)) + .then(() => sequelize.models.UserHistory.findAll()) .then((users) => { assert.equal(users.length,1, "only one entry in DB"); assert.equal(users[0].name, "foo", "previous entry saved"); @@ -705,15 +705,15 @@ describe('Read-only API', function(){ .then((t) => { var opts = {transaction: t}; return sequelize.models.User.create({name: "not foo"},opts) - .then(assertCount(sequelize.models.UserRecord,0, opts)) + .then(assertCount(sequelize.models.UserHistory,0, opts)) .then((user) => { user.name = "foo"; user.save(opts); }) - .then(assertCount(sequelize.models.UserRecord,1, opts)) + .then(assertCount(sequelize.models.UserHistory,1, opts)) .then(() => t.rollback()); }) - .then(assertCount(sequelize.models.UserRecord,0)); + .then(assertCount(sequelize.models.UserHistory,0)); }); }); @@ -721,21 +721,21 @@ describe('Read-only API', function(){ beforeEach(freshDB); it('should archive every entry', function() { return sequelize.models.User.bulkCreate([{name: "foo1"},{name: "foo2"}]) - .then(assertCount(sequelize.models.UserRecord,0)) + .then(assertCount(sequelize.models.UserHistory,0)) .then(() => sequelize.models.User.update({ name: 'updated-foo' }, {where: {}})) - .then(assertCount(sequelize.models.UserRecord,2)) + .then(assertCount(sequelize.models.UserHistory,2)) }); it('should revert under transactions', function() { return sequelize.transaction() .then(function(t) { var opts = {transaction: t}; return sequelize.models.User.bulkCreate([{name: "foo1"},{name: "foo2"}], opts) - .then(assertCount(sequelize.models.UserRecord,0,opts)) + .then(assertCount(sequelize.models.UserHistory,0,opts)) .then(() => sequelize.models.User.update({ name: 'updated-foo' }, {where: {}, transaction: t})) - .then(assertCount(sequelize.models.UserRecord,2, opts)) + .then(assertCount(sequelize.models.UserHistory,2, opts)) .then(() => t.rollback()); }) - .then(assertCount(sequelize.models.UserRecord,0)); + .then(assertCount(sequelize.models.UserHistory,0)); }); }); @@ -743,28 +743,28 @@ describe('Read-only API', function(){ beforeEach(freshDB); it('should archive every entry', function() { return sequelize.models.User.bulkCreate([{name: "foo1"},{name: "foo2"}]) - .then(assertCount(sequelize.models.UserRecord,0)) + .then(assertCount(sequelize.models.UserHistory,0)) .then(() => sequelize.models.User.destroy({ where: {}, truncate: true // truncate the entire table })) - .then(assertCount(sequelize.models.UserRecord,2)) + .then(assertCount(sequelize.models.UserHistory,2)) }); it('should revert under transactions', function() { return sequelize.transaction() .then((t) => { var opts = {transaction: t}; return sequelize.models.User.bulkCreate([{name: "foo1"},{name: "foo2"}], opts) - .then(assertCount(sequelize.models.UserRecord,0,opts)) + .then(assertCount(sequelize.models.UserHistory,0,opts)) .then(() => sequelize.models.User.destroy({ where: {}, truncate: true, // truncate the entire table transaction: t })) - .then(assertCount(sequelize.models.UserRecord,2, opts)) + .then(assertCount(sequelize.models.UserHistory,2, opts)) .then(() => t.rollback()); }) - .then(assertCount(sequelize.models.UserRecord,0)); + .then(assertCount(sequelize.models.UserHistory,0)); }); }); @@ -772,14 +772,14 @@ describe('Read-only API', function(){ beforeEach(freshDBWithAssociations); it('should archive every entry', function() { return dataCreate() - .then(assertCount(sequelize.models.UserRecord,3)) + .then(assertCount(sequelize.models.UserHistory,3)) .then(() => sequelize.models.User.destroy({ where: {}, truncate: true // truncate the entire table })) - .then(assertCount(sequelize.models.UserRecord,6)) + .then(assertCount(sequelize.models.UserHistory,6)) .then(() => sequelize.models.User.findOne()) - .then(u => u.getUserRecords()) + .then(u => u.getUserHistories()) .then(uh => assert.exists(uh, 'The truncation did not break the associations')) .catch(err => assert.exists(err,'The truncation broke the associations')); }); @@ -788,30 +788,30 @@ describe('Read-only API', function(){ .then(() => sequelize.transaction()) .then((t) => { var opts = {transaction: t}; - assertCount(sequelize.models.UserRecord,6,opts); + assertCount(sequelize.models.UserHistory,6,opts); return sequelize.models.User.destroy({ where: {}, truncate: true, // truncate the entire table transaction: t }) - .then(assertCount(sequelize.models.UserRecord,3,opts)) + .then(assertCount(sequelize.models.UserHistory,3,opts)) .then(() => t.rollback()) .catch(err => assert.exists(err)); }) - .then(assertCount(sequelize.models.UserRecord,6)); + .then(assertCount(sequelize.models.UserHistory,6)); }); }); describe('read-only ', function() { beforeEach(freshDB); it('should forbid updates' , function() { - var userUpdate = sequelize.models.UserRecord.create({name: 'bla00'}) + var userUpdate = sequelize.models.UserHistory.create({name: 'bla00'}) .then((uh) => uh.update({name: 'bla'})); return assert.isRejected(userUpdate, Error, "Validation error"); }); it('should forbid deletes' , function() { - var userUpdate = sequelize.models.UserRecord.create({name: 'bla00'}) + var userUpdate = sequelize.models.UserHistory.create({name: 'bla00'}) .then(uh => uh.destroy()); return assert.isRejected(userUpdate, Error, "Validation error"); @@ -821,7 +821,7 @@ describe('Read-only API', function(){ describe('interference with the original model', function() { beforeEach(freshDB); it('shouldn\'t delete instance methods' , function() { - Fruit = Record(sequelize.define('Fruit', { name: Sequelize.TEXT }), sequelize); + Fruit = Historical(sequelize.define('Fruit', { name: Sequelize.TEXT }), sequelize); Fruit.prototype.sayHi = () => { return 2; } return sequelize.sync() @@ -834,7 +834,7 @@ describe('Read-only API', function(){ it('shouldn\'t interfere with hooks of the model' , function() { var triggered = 0; - Fruit = Record(sequelize.define('Fruit', { name: Sequelize.TEXT }, { hooks:{ beforeCreate: function(){ triggered++; }}}), sequelize); + Fruit = Historical(sequelize.define('Fruit', { name: Sequelize.TEXT }, { hooks:{ beforeCreate: function(){ triggered++; }}}), sequelize); return sequelize.sync() .then(() => Fruit.create()) .then((f) => assert.equal(triggered, 1,"hook trigger count")); @@ -842,7 +842,7 @@ describe('Read-only API', function(){ it('shouldn\'t interfere with setters' , function() { var triggered = 0; - Fruit = Record(sequelize.define('Fruit', { + Fruit = Historical(sequelize.define('Fruit', { name: { type: Sequelize.TEXT, set: function() { triggered++; } @@ -856,49 +856,49 @@ describe('Read-only API', function(){ describe('full mode', function() { beforeEach(freshDBWithFullModeAndParanoid); - it('onCreate: should store the new version in record db' , function() { + it('onCreate: should store the new version in history db' , function() { return sequelize.models.User.create({ name: 'test' }) - .then(() => sequelize.models.UserRecord.findAll()) - .then((records) => { - assert.equal(1, records.length); - assert.equal('test', records[0].name); + .then(() => sequelize.models.UserHistory.findAll()) + .then((histories) => { + assert.equal(1, histories.length); + assert.equal('test', histories[0].name); }); }); - it('onUpdate: should store the new version to the recordDB' , function() { + it('onUpdate: should store the new version to the historyDB' , function() { return sequelize.models.User.create({ name: 'test' }) .then(user => user.update({ name: 'renamed' })) - .then(() => sequelize.models.UserRecord.findAll()) - .then((records) => { - assert.equal(records.length, 2, 'two entries in DB'); - assert.equal(records[0].name, 'test', 'first version saved'); - assert.equal(records[1].name, 'renamed', 'second version saved'); + .then(() => sequelize.models.UserHistory.findAll()) + .then((histories) => { + assert.equal(histories.length, 2, 'two entries in DB'); + assert.equal(histories[0].name, 'test', 'first version saved'); + assert.equal(histories[1].name, 'renamed', 'second version saved'); }); }); - it('onDelete: should store the previous version to the recordDB' , function() { + it('onDelete: should store the previous version to the historyDB' , function() { return sequelize.models.User.create({ name: 'test' }) .then(user => user.update({ name: 'renamed' })) .then(user=> user.destroy()) - .then(() => sequelize.models.UserRecord.findAll()) - .then((records) => { - assert.equal(records.length, 3, 'three entries in DB'); - assert.equal(records[0].name, 'test', 'first version saved'); - assert.equal(records[1].name, 'renamed', 'second version saved'); - assert.notEqual(records[2].deletedAt, null, 'deleted version saved'); + .then(() => sequelize.models.UserHistory.findAll()) + .then((histories) => { + assert.equal(histories.length, 3, 'three entries in DB'); + assert.equal(histories[0].name, 'test', 'first version saved'); + assert.equal(histories[1].name, 'renamed', 'second version saved'); + assert.notEqual(histories[2].deletedAt, null, 'deleted version saved'); }); }); - it('onRestore: should store the new version to the recordDB' , function() { + it('onRestore: should store the new version to the historyDB' , function() { return sequelize.models.User.create({ name: 'test' }) .then(user => user.destroy()) .then(user => user.restore()) - .then(() => sequelize.models.UserRecord.findAll()) - .then((records) => { - assert.equal(records.length, 3, 'three entries in DB'); - assert.equal(records[0].name, 'test', 'first version saved'); - assert.notEqual(records[1].deletedAt, null, 'deleted version saved'); - assert.equal(records[2].deletedAt, null, 'restored version saved'); + .then(() => sequelize.models.UserHistory.findAll()) + .then((histories) => { + assert.equal(histories.length, 3, 'three entries in DB'); + assert.equal(histories[0].name, 'test', 'first version saved'); + assert.notEqual(histories[1].deletedAt, null, 'deleted version saved'); + assert.equal(histories[2].deletedAt, null, 'restored version saved'); }); }); @@ -909,10 +909,10 @@ describe('Read-only API', function(){ return sequelize.models.User.create({ name: 'test' }, options) .then(user => user.destroy(options)) - .then(assertCount(sequelize.models.UserRecord, 2, options)) + .then(assertCount(sequelize.models.UserHistory, 2, options)) .then(() => transaction.rollback()); }) - .then(assertCount(sequelize.models.UserRecord,0)); + .then(assertCount(sequelize.models.UserHistory,0)); }); }); }); From 343b8a1f521e6f372488e184da8b547508e7b021 Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Thu, 9 May 2019 00:48:04 +0900 Subject: [PATCH 30/37] Update names --- README.md | 22 +++++++++++----------- index.d.ts | 2 +- index.js | 50 +++++++++++++++++++++++++------------------------- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index abcf7a6..1ff585f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -History tables for Sequelize +Historical tables for Sequelize =============================== -Warning: this is a fork of [sequelize-temporal](https://github.com/bonaval/sequelize-temporal) that adds support for Sequelize 5 and that adds the ability to associate data history table to origin tables (table a history is based on) and to specify a different name for the __History__ tables. +Warning: this is a fork of [sequelize-temporal](https://github.com/bonaval/sequelize-temporal) that adds support for Sequelize 5 and that adds the ability to associate data history table to origin tables (table a history is based on) and to specify a different name for the __Historical__ tables. [![Build Status](https://travis-ci.org/opencollective/sequelize-historical.svg?branch=master)](https://travis-ci.org/opencollective/sequelize-historical) [![Dependency Status](https://david-dm.org/opencollective/sequelize-historical.svg)](https://david-dm.org/opencollective/sequelize-historical) [![NPM version](https://img.shields.io/npm/v/sequelize-historical.svg)](https://www.npmjs.com/package/sequelize-historical) [![Greenkeeper badge](https://badges.greenkeeper.io/opencollective/sequelize-historical.svg)](https://greenkeeper.io/) @@ -9,7 +9,7 @@ Warning: this is a fork of [sequelize-temporal](https://github.com/bonaval/seque What is it? ----------- -___History__ tables maintain __previous values__ of data. Modifying operations (UPDATE, DELETE) on these tables don't cause permanent changes to entries, but create new versions of them. Hence this might be used to: +___Historical__ tables maintain __Historical versions__ of data. Modifying operations (UPDATE, DELETE) on these tables don't cause permanent changes to entries, but create new versions of them. Hence this might be used to: - log changes (security/auditing) - undo functionalities @@ -36,7 +36,7 @@ How to use ``` var Sequelize = require('sequelize'); -var History = require('sequelize-historical'); +var Historical = require('sequelize-historical'); ``` Create a sequelize instance and your models, e.g. @@ -48,26 +48,26 @@ var sequelize = new Sequelize('', '', '', { }); ``` -### 2) Add the *history* feature to your models +### 2) Add the *historical* feature to your models ``` -var User = History(sequelize.define('User'), sequelize); +var User = Historical(sequelize.define('User'), sequelize); ``` -The output of `History` is its input model, so assigning it's output to your +The output of `Historical` is its input model, so assigning it's output to your Model is not necessary, hence it's just the lazy version of: ``` var User = sequelize.define('User', {.types.}, {.options.}); //Vanilla Sequelize -History(User, sequelize); +Historical(User, sequelize); ``` Options ------- -The default syntax for `History` is: +The default syntax for `Historical` is: -`History(model, sequelizeInstance, options)` +`Historical(model, sequelizeInstance, options)` whereas the options are listed here (with default value). @@ -162,7 +162,7 @@ Triggers for storing old versions of rows to history table are inspired by refer ### Notes -If you only use Postgres, you might want to have a look at the [History Table](https://github.com/arkhipov/history_tables) extension. +If you only use Postgres, you might want to have a look at the [Temporal Table](https://github.com/arkhipov/temporal_tables) extension. License ------- diff --git a/index.d.ts b/index.d.ts index c29b3b6..54bf03e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,4 +1,4 @@ -declare module 'sequelize-record' { +declare module 'sequelize-historical' { interface Options { blocking?:boolean, full?:boolean, diff --git a/index.js b/index.js index a7d14d7..a7ee12d 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ var _ = require('lodash'); -var recordDefaultOptions = { +var temporalDefaultOptions = { // runs the insert within the sequelize hook chain, disable // for increased performance blocking: true, @@ -9,14 +9,14 @@ var recordDefaultOptions = { addAssociations: false }; -var Historical = function(model, sequelize, recordOptions) { - recordOptions = _.extend({},recordDefaultOptions, recordOptions); +var Historical = function(model, sequelize, historyOptions) { + historyOptions = _.extend({},temporalDefaultOptions, historyOptions); var Sequelize = sequelize.Sequelize; - var recordName = model.name + recordOptions.modelSuffix; + var historyName = model.name + historyOptions.modelSuffix; - var recordOwnAttrs = { + var historyOwnAttrs = { hid: { type: Sequelize.BIGINT, primaryKey: true, @@ -31,41 +31,41 @@ var Historical = function(model, sequelize, recordOptions) { }; var excludedAttributes = ["Model","unique","primaryKey","autoIncrement", "set", "get", "_modelAttribute"]; - var recordAttributes = _(model.rawAttributes).mapValues(function(v){ + var historyAttributes = _(model.rawAttributes).mapValues(function(v){ v = _.omit(v, excludedAttributes); // remove the "NOW" defaultValue for the default timestamps - // we want to save them, but just a copy from our master record + // we want to save them, but just a copy from our master history if(v.fieldName == "createdAt" || v.fieldName == "updatedAt"){ v.type = Sequelize.DATE; } return v; - }).assign(recordOwnAttrs).value(); + }).assign(historyOwnAttrs).value(); // If the order matters, use this: - //recordAttributes = _.assign({}, recordOwnAttrs, recordAttributes); + //historyAttributes = _.assign({}, historyOwnAttrs, historyAttributes); - var recordOwnOptions = { + var historyOwnOptions = { timestamps: false }; var excludedNames = ["name", "tableName", "sequelize", "uniqueKeys", "hasPrimaryKey", "hooks", "scopes", "instanceMethods", "defaultScope"]; var modelOptions = _.omit(model.options, excludedNames); - var recordModelOptions = _.assign({}, modelOptions, recordOwnOptions); + var historyModelOptions = _.assign({}, modelOptions, historyOwnOptions); // We want to delete indexes that have unique constraint - var indexes = recordModelOptions.indexes; + var indexes = historyModelOptions.indexes; if(Array.isArray(indexes)){ - recordModelOptions.indexes = indexes.filter(function(index){return !index.unique && index.type != 'UNIQUE';}); + historyModelOptions.indexes = indexes.filter(function(index){return !index.unique && index.type != 'UNIQUE';}); } - var modelHistory = sequelize.define(recordName, recordAttributes, recordModelOptions); + var modelHistory = sequelize.define(historyName, historyAttributes, historyModelOptions); modelHistory.originModel = model; - modelHistory.addAssociations = recordOptions.addAssociations; + modelHistory.addAssociations = historyOptions.addAssociations; // we already get the updatedAt timestamp from our models var insertHook = function(obj, options){ - var dataValues = (!recordOptions.full && obj._previousDataValues) || obj.dataValues; - var recordHistory = modelHistory.create(dataValues, {transaction: options.transaction}); - if(recordOptions.blocking){ - return recordHistory; + var dataValues = (!historyOptions.full && obj._previousDataValues) || obj.dataValues; + var historyHistory = modelHistory.create(dataValues, {transaction: options.transaction}); + if(historyOptions.blocking){ + return historyHistory; } } var insertBulkHook = function(options){ @@ -76,7 +76,7 @@ var Historical = function(model, sequelize, recordOptions) { return modelHistory.bulkCreate(hits, {transaction: options.transaction}); } }); - if(recordOptions.blocking){ + if(historyOptions.blocking){ return queryAll; } } @@ -86,9 +86,9 @@ var Historical = function(model, sequelize, recordOptions) { const source = this.originModel; const sourceHist = this; - if(source && !source.name.endsWith(recordOptions.modelSuffix) && source.associations && recordOptions.addAssociations == true && sourceHist) { + if(source && !source.name.endsWith(historyOptions.modelSuffix) && source.associations && historyOptions.addAssociations == true && sourceHist) { const pkfield = source.primaryKeyField; - //adding associations from record model to origin model's association + //adding associations from history model to origin model's association Object.keys(source.associations).forEach(assokey => { const association = source.associations[assokey]; const associationOptions = _.cloneDeep(association.options); @@ -104,7 +104,7 @@ var Historical = function(model, sequelize, recordOptions) { sourceHist[assocName].apply(sourceHist, [target, associationOptions]); }); - //adding associations between origin model and record + //adding associations between origin model and history source.hasMany(sourceHist, { foreignKey: pkfield }); sourceHist.belongsTo(source, { foreignKey: pkfield }); @@ -116,7 +116,7 @@ var Historical = function(model, sequelize, recordOptions) { // use `after` to be nonBlocking // all hooks just create a copy - if (recordOptions.full) { + if (historyOptions.full) { model.addHook('afterCreate', insertHook); model.addHook('afterUpdate', insertHook); model.addHook('afterDestroy', insertHook); @@ -130,7 +130,7 @@ var Historical = function(model, sequelize, recordOptions) { model.addHook('beforeBulkDestroy', insertBulkHook); var readOnlyHook = function(){ - throw new Error("This is a read-only record database. You aren't allowed to modify it."); + throw new Error("This is a read-only history database. You aren't allowed to modify it."); }; modelHistory.addHook('beforeUpdate', readOnlyHook); From d2e8925761d30ba2ac12f0d7e4859dfadd19c696 Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Thu, 9 May 2019 00:57:52 +0900 Subject: [PATCH 31/37] Name update again --- README.md | 12 ++++++------ index.js | 7 ++++--- package.json | 12 +++++------- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 1ff585f..b383827 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Historical tables for Sequelize =============================== -Warning: this is a fork of [sequelize-temporal](https://github.com/bonaval/sequelize-temporal) that adds support for Sequelize 5 and that adds the ability to associate data history table to origin tables (table a history is based on) and to specify a different name for the __Historical__ tables. +Warning: this is a fork of [sequelize-temporal](https://github.com/bonaval/sequelize-temporal) that adds support for Sequelize 5 and that adds the ability to associate data history table to origin tables (table a history is based on) and to specify a different name for the __historical__ tables. [![Build Status](https://travis-ci.org/opencollective/sequelize-historical.svg?branch=master)](https://travis-ci.org/opencollective/sequelize-historical) [![Dependency Status](https://david-dm.org/opencollective/sequelize-historical.svg)](https://david-dm.org/opencollective/sequelize-historical) [![NPM version](https://img.shields.io/npm/v/sequelize-historical.svg)](https://www.npmjs.com/package/sequelize-historical) [![Greenkeeper badge](https://badges.greenkeeper.io/opencollective/sequelize-historical.svg)](https://greenkeeper.io/) @@ -9,7 +9,7 @@ Warning: this is a fork of [sequelize-temporal](https://github.com/bonaval/seque What is it? ----------- -___Historical__ tables maintain __Historical versions__ of data. Modifying operations (UPDATE, DELETE) on these tables don't cause permanent changes to entries, but create new versions of them. Hence this might be used to: +Historical tables maintain __historical versions__ of data. Modifying operations (UPDATE, DELETE) on these tables don't cause permanent changes to entries, but create new versions of them. Hence this might be used to: - log changes (security/auditing) - undo functionalities @@ -54,11 +54,11 @@ var sequelize = new Sequelize('', '', '', { var User = Historical(sequelize.define('User'), sequelize); ``` -The output of `Historical` is its input model, so assigning it's output to your +The output of `historical` is its input model, so assigning it's output to your Model is not necessary, hence it's just the lazy version of: ``` -var User = sequelize.define('User', {.types.}, {.options.}); //Vanilla Sequelize +var User = sequelize.define('User', {.types.}, {.options.}); // Sequelize Docu Historical(User, sequelize); ``` @@ -150,11 +150,11 @@ whereas the options are listed here (with default value). Details -------- -@See: https://wiki.postgresql.org/wiki/SQL2011History +@See: https://wiki.postgresql.org/wiki/SQL2011Temporal ### History table -History table stores history versions of rows, which are inserted by triggers on every modifying operation executed on current table. It has the same structure and indexes as current table, but it doesn’t have any constraints. History tables are insert only and creator should prevent other users from executing updates or deletes by correct user rights settings. Otherwise the history can be violated. +History table stores historical versions of rows, which are inserted by triggers on every modifying operation executed on current table. It has the same structure and indexes as current table, but it doesn’t have any constraints. History tables are insert only and creator should prevent other users from executing updates or deletes by correct user rights settings. Otherwise the history can be violated. ### Hooks diff --git a/index.js b/index.js index a7ee12d..42939e7 100644 --- a/index.js +++ b/index.js @@ -34,7 +34,7 @@ var Historical = function(model, sequelize, historyOptions) { var historyAttributes = _(model.rawAttributes).mapValues(function(v){ v = _.omit(v, excludedAttributes); // remove the "NOW" defaultValue for the default timestamps - // we want to save them, but just a copy from our master history + // we want to save them, but just a copy from our master record if(v.fieldName == "createdAt" || v.fieldName == "updatedAt"){ v.type = Sequelize.DATE; } @@ -63,11 +63,12 @@ var Historical = function(model, sequelize, historyOptions) { // we already get the updatedAt timestamp from our models var insertHook = function(obj, options){ var dataValues = (!historyOptions.full && obj._previousDataValues) || obj.dataValues; - var historyHistory = modelHistory.create(dataValues, {transaction: options.transaction}); + var historyRecord = modelHistory.create(dataValues, {transaction: options.transaction}); if(historyOptions.blocking){ - return historyHistory; + return historyRecord; } } + var insertBulkHook = function(options){ if(!options.individualHooks){ var queryAll = model.findAll({where: options.where, transaction: options.transaction}).then(function(hits){ diff --git a/package.json b/package.json index a15fd55..3483f02 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sequelize-historical", - "version": "1.0.5", + "version": "5.1.1", "description": "Historical tables for Sequelize", "main": "index.js", "directories": { @@ -11,7 +11,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/kurisutofu/sequelize-record.git" + "url": "git+https://github.com/opencollective/sequelize-historical.git" }, "keywords": [ "sequelize", @@ -20,16 +20,14 @@ "sql11", "undo", "paranoid", - "historical", - "record", - "history" + "historical" ], "author": "greenify (https://github.com/greenify)", "license": "MIT", "bugs": { - "url": "https://github.com/kurisutofu/sequelize-record/issues" + "url": "https://github.com/opencollective/sequelize-historical/issues" }, - "homepage": "https://github.com/kurisutofu/sequelize-record#readme", + "homepage": "https://github.com/opencollective/sequelize-historical#readme", "dependencies": { "lodash": "^4.17.11" }, From 7d5fe153cc21f94c3ba5ef1aea125be5836d4a43 Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Thu, 30 May 2019 22:57:46 +0900 Subject: [PATCH 32/37] Renaming to temporal --- README.md | 35 ++++++++++++++++------------------- index.d.ts | 2 +- index.js | 34 +++++++++++++++++----------------- package.json | 10 +++++----- 4 files changed, 39 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index b383827..5eadb59 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,14 @@ -Historical tables for Sequelize -=============================== +Temporal tables for Sequelize +============================= +(aka "Historical records") -Warning: this is a fork of [sequelize-temporal](https://github.com/bonaval/sequelize-temporal) that adds support for Sequelize 5 and that adds the ability to associate data history table to origin tables (table a history is based on) and to specify a different name for the __historical__ tables. - -[![Build Status](https://travis-ci.org/opencollective/sequelize-historical.svg?branch=master)](https://travis-ci.org/opencollective/sequelize-historical) [![Dependency Status](https://david-dm.org/opencollective/sequelize-historical.svg)](https://david-dm.org/opencollective/sequelize-historical) [![NPM version](https://img.shields.io/npm/v/sequelize-historical.svg)](https://www.npmjs.com/package/sequelize-historical) [![Greenkeeper badge](https://badges.greenkeeper.io/opencollective/sequelize-historical.svg)](https://greenkeeper.io/) +[![Build Status](https://travis-ci.org/bonaval/sequelize-temporal.svg?branch=master)](https://travis-ci.org/bonaval/sequelize-temporal) [![Dependency Status](https://david-dm.org/bonaval/sequelize-temporal.svg)](https://david-dm.org/bonaval/sequelize-temporal) [![NPM version](https://img.shields.io/npm/v/sequelize-temporal.svg)](https://www.npmjs.com/package/sequelize-temporal) What is it? ----------- -Historical tables maintain __historical versions__ of data. Modifying operations (UPDATE, DELETE) on these tables don't cause permanent changes to entries, but create new versions of them. Hence this might be used to: +Temporal tables maintain __historical versions__ of data. Modifying operations (UPDATE, DELETE) on these tables don't cause permanent changes to entries, but create new versions of them. Hence this might be used to: - log changes (security/auditing) - undo functionalities @@ -26,7 +25,7 @@ Installation ------------ ``` -npm install sequelize-historical +npm install sequelize-temporal ``` How to use @@ -36,7 +35,7 @@ How to use ``` var Sequelize = require('sequelize'); -var Historical = require('sequelize-historical'); +var Temporal = require('sequelize-temporal'); ``` Create a sequelize instance and your models, e.g. @@ -48,38 +47,36 @@ var sequelize = new Sequelize('', '', '', { }); ``` -### 2) Add the *historical* feature to your models +### 2) Add the *temporal* feature to your models ``` -var User = Historical(sequelize.define('User'), sequelize); +var User = Temporal(sequelize.define('User'), sequelize); ``` -The output of `historical` is its input model, so assigning it's output to your +The output of `temporal` is its input model, so assigning it's output to your Model is not necessary, hence it's just the lazy version of: ``` var User = sequelize.define('User', {.types.}, {.options.}); // Sequelize Docu -Historical(User, sequelize); +Temporal(User, sequelize); ``` Options ------- -The default syntax for `Historical` is: +The default syntax for `Temporal` is: -`Historical(model, sequelizeInstance, options)` +`Temporal(model, sequelizeInstance, options)` whereas the options are listed here (with default value). ```js { - /* - Runs the insert within the sequelize hook chain, disable + /* runs the insert within the sequelize hook chain, disable for increased performance without warranties */ blocking: true, - /* - By default sequelize-historical persist only changes, and saves the previous state in the history table. - The "full" option saves all transactions into the history database + /* By default sequelize-temporal persist only changes, and saves the previous state in the history table. + The "full" option saves all transactions into the temporal database (i.e. this includes the latest state.) This allows to only query the history table to get the full history of an entity. */ diff --git a/index.d.ts b/index.d.ts index 54bf03e..7281eba 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,4 +1,4 @@ -declare module 'sequelize-historical' { +declare module 'sequelize-temporal' { interface Options { blocking?:boolean, full?:boolean, diff --git a/index.js b/index.js index 42939e7..4421eb4 100644 --- a/index.js +++ b/index.js @@ -9,12 +9,12 @@ var temporalDefaultOptions = { addAssociations: false }; -var Historical = function(model, sequelize, historyOptions) { - historyOptions = _.extend({},temporalDefaultOptions, historyOptions); +var Temporal = function(model, sequelize, temporalOptions) { + temporalOptions = _.extend({},temporalDefaultOptions, temporalOptions); var Sequelize = sequelize.Sequelize; - var historyName = model.name + historyOptions.modelSuffix; + var historyName = model.name + temporalOptions.modelSuffix; var historyOwnAttrs = { hid: { @@ -31,7 +31,7 @@ var Historical = function(model, sequelize, historyOptions) { }; var excludedAttributes = ["Model","unique","primaryKey","autoIncrement", "set", "get", "_modelAttribute"]; - var historyAttributes = _(model.rawAttributes).mapValues(function(v){ + var historyAttributes = _(model.rawAttributes).mapValues(function(v){ v = _.omit(v, excludedAttributes); // remove the "NOW" defaultValue for the default timestamps // we want to save them, but just a copy from our master record @@ -48,23 +48,23 @@ var Historical = function(model, sequelize, historyOptions) { }; var excludedNames = ["name", "tableName", "sequelize", "uniqueKeys", "hasPrimaryKey", "hooks", "scopes", "instanceMethods", "defaultScope"]; var modelOptions = _.omit(model.options, excludedNames); - var historyModelOptions = _.assign({}, modelOptions, historyOwnOptions); + var historyOptions = _.assign({}, modelOptions, historyOwnOptions); // We want to delete indexes that have unique constraint - var indexes = historyModelOptions.indexes; + var indexes = historyOptions.indexes; if(Array.isArray(indexes)){ - historyModelOptions.indexes = indexes.filter(function(index){return !index.unique && index.type != 'UNIQUE';}); + historyOptions.indexes = indexes.filter(function(index){return !index.unique && index.type != 'UNIQUE';}); } - var modelHistory = sequelize.define(historyName, historyAttributes, historyModelOptions); + var modelHistory = sequelize.define(historyName, historyAttributes, historyOptions); modelHistory.originModel = model; - modelHistory.addAssociations = historyOptions.addAssociations; + modelHistory.addAssociations = temporalOptions.addAssociations; // we already get the updatedAt timestamp from our models var insertHook = function(obj, options){ - var dataValues = (!historyOptions.full && obj._previousDataValues) || obj.dataValues; + var dataValues = (!temporalOptions.full && obj._previousDataValues) || obj.dataValues; var historyRecord = modelHistory.create(dataValues, {transaction: options.transaction}); - if(historyOptions.blocking){ + if(temporalOptions.blocking){ return historyRecord; } } @@ -77,7 +77,7 @@ var Historical = function(model, sequelize, historyOptions) { return modelHistory.bulkCreate(hits, {transaction: options.transaction}); } }); - if(historyOptions.blocking){ + if(temporalOptions.blocking){ return queryAll; } } @@ -87,7 +87,7 @@ var Historical = function(model, sequelize, historyOptions) { const source = this.originModel; const sourceHist = this; - if(source && !source.name.endsWith(historyOptions.modelSuffix) && source.associations && historyOptions.addAssociations == true && sourceHist) { + if(source && !source.name.endsWith(temporalOptions.modelSuffix) && source.associations && temporalOptions.addAssociations == true && sourceHist) { const pkfield = source.primaryKeyField; //adding associations from history model to origin model's association Object.keys(source.associations).forEach(assokey => { @@ -96,7 +96,7 @@ var Historical = function(model, sequelize, historyOptions) { const target = association.target; const assocName = association.associationType.charAt(0).toLowerCase() + association.associationType.substr(1); - //handle premary keys for belongsToMany + //handle primary keys for belongsToMany if(assocName == 'belongsToMany') { sourceHist.primaryKeys = _.forEach(source.primaryKeys, (x) => x.autoIncrement = false); sourceHist.primaryKeyField = Object.keys(sourceHist.primaryKeys)[0]; @@ -112,12 +112,12 @@ var Historical = function(model, sequelize, historyOptions) { sequelize.models[sourceHist.name] = sourceHist; } - return Promise.resolve('Historical associations established'); + return Promise.resolve('Temporal associations established'); } // use `after` to be nonBlocking // all hooks just create a copy - if (historyOptions.full) { + if (temporalOptions.full) { model.addHook('afterCreate', insertHook); model.addHook('afterUpdate', insertHook); model.addHook('afterDestroy', insertHook); @@ -141,4 +141,4 @@ var Historical = function(model, sequelize, historyOptions) { return model; }; -module.exports = Historical; +module.exports = Temporal; diff --git a/package.json b/package.json index 3483f02..49c3ad3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "sequelize-historical", - "version": "5.1.1", - "description": "Historical tables for Sequelize", + "name": "sequelize-temporal", + "version": "2.0.1", + "description": "Temporal tables for Sequelize", "main": "index.js", "directories": { "test": "test" @@ -11,7 +11,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/opencollective/sequelize-historical.git" + "url": "git+https://github.com/bonaval/sequelize-temporal.git" }, "keywords": [ "sequelize", @@ -25,7 +25,7 @@ "author": "greenify (https://github.com/greenify)", "license": "MIT", "bugs": { - "url": "https://github.com/opencollective/sequelize-historical/issues" + "url": "https://github.com/bonaval/sequelize-temporal/issues" }, "homepage": "https://github.com/opencollective/sequelize-historical#readme", "dependencies": { From 9e04d960eba43aab2aba96302c1e0b3d08b202b6 Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Thu, 30 May 2019 23:02:12 +0900 Subject: [PATCH 33/37] Fixed ReadMe --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5eadb59..b97b35d 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ npm install sequelize-temporal How to use ---------- -### 1) Import `sequelize-historical` +### 1) Import `sequelize-temporal` ``` var Sequelize = require('sequelize'); @@ -82,7 +82,7 @@ whereas the options are listed here (with default value). */ full: false, /* - By default sequelize-historical will add 'History' to the history Model name and 'Histories' to the history table. + By default sequelize-temporal will add 'History' to the history Model name and 'Histories' to the history table. By updating the modelSuffix value, you can decide what the naming will be. The value will be appended to the history Model name and its plural will be appended to the history tablename. @@ -93,7 +93,7 @@ whereas the options are listed here (with default value). */ modelSuffix: 'History', /* - By default sequelize-historical will create the history table without associations. + By default sequelize-temporal will create the history table without associations. However, setting this flag to true, you can keep association between the history table and the table with the latest value (origin). example for table User: From 283e62584f33a19cbc7009cbd0b42871b4c0bd77 Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Sun, 2 Jun 2019 13:14:30 +0900 Subject: [PATCH 34/37] Association onDelete/onUpdate actions + Updated the association copied from the original model to not cascade the update/delete actions. --- index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 4421eb4..a1a68e6 100644 --- a/index.js +++ b/index.js @@ -94,7 +94,9 @@ var Temporal = function(model, sequelize, temporalOptions) { const association = source.associations[assokey]; const associationOptions = _.cloneDeep(association.options); const target = association.target; - const assocName = association.associationType.charAt(0).toLowerCase() + association.associationType.substr(1); + const assocName = association.associationType.charAt(0).toLowerCase() + association.associationType.substr(1); + associationOptions.onDelete = 'NO ACTION'; + associationOptions.onUpdate = 'NO ACTION'; //handle primary keys for belongsToMany if(assocName == 'belongsToMany') { From 1bb6c2371b823c6ed3f3e8ceb0388798245c91b9 Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Sun, 2 Jun 2019 13:54:23 +0900 Subject: [PATCH 35/37] Removing associations + Removed associations from historical model to avoid error if table in different DB --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index a1a68e6..c04d133 100644 --- a/index.js +++ b/index.js @@ -30,7 +30,7 @@ var Temporal = function(model, sequelize, temporalOptions) { } }; - var excludedAttributes = ["Model","unique","primaryKey","autoIncrement", "set", "get", "_modelAttribute"]; + var excludedAttributes = ["Model","unique","primaryKey","autoIncrement", "set", "get", "_modelAttribute","references","onDelete","onUpdate"]; var historyAttributes = _(model.rawAttributes).mapValues(function(v){ v = _.omit(v, excludedAttributes); // remove the "NOW" defaultValue for the default timestamps From d7e9a68236d01d605e32b33348557ea7bd1d5cc9 Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Tue, 11 Jun 2019 23:29:14 +0900 Subject: [PATCH 36/37] AllowTransaction flag Added the ability --- README.md | 10 ++++- index.js | 13 +++--- test/test.js | 114 ++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 116 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index b97b35d..2768922 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,8 @@ whereas the options are listed here (with default value). By default sequelize-temporal will create the history table without associations. However, setting this flag to true, you can keep association between the history table and the table with the latest value (origin). + NOTE: THIS DOES NOT WORK IF YOU ARE USING A SEPARATE DB FOR THE HISTORICAL TABLES. IN THAT CASE, KEEP THE VALUE TO FALSE OR YOU WILL GET AN ERROR. + example for table User: model: 'User' history model: 'UserHistories' @@ -141,7 +143,13 @@ whereas the options are listed here (with default value). CreationHistories.getUser() DOES NOT EXIST. THE ORIGIN TABLE IS NOT MODIFIED. */ - addAssociations: false + addAssociations: false, + /* + By default, transactions are allowed but can be disabled with that flag for the historical tables (transactions on original tables should stay the same). It is useful in case you are using a separate DB than the one use by the original DB. + + NOTE: IF YOU USE A SEPARATE DB FOR HISTORICAL TABLE, SET THE VALUE TO FALSE OR YOU WILL GET AN ERROR. + */ + allowTransactions: true ``` Details diff --git a/index.js b/index.js index c04d133..f48a255 100644 --- a/index.js +++ b/index.js @@ -6,7 +6,8 @@ var temporalDefaultOptions = { blocking: true, full: false, modelSuffix: 'History', - addAssociations: false + addAssociations: false, + allowTransactions: true, }; var Temporal = function(model, sequelize, temporalOptions) { @@ -61,9 +62,9 @@ var Temporal = function(model, sequelize, temporalOptions) { modelHistory.addAssociations = temporalOptions.addAssociations; // we already get the updatedAt timestamp from our models - var insertHook = function(obj, options){ - var dataValues = (!temporalOptions.full && obj._previousDataValues) || obj.dataValues; - var historyRecord = modelHistory.create(dataValues, {transaction: options.transaction}); + var insertHook = function(obj, options){ + var dataValues = (!temporalOptions.full && obj._previousDataValues) || obj.dataValues; + var historyRecord = modelHistory.create(dataValues, {transaction: temporalOptions.allowTransactions? options.transaction: null}); if(temporalOptions.blocking){ return historyRecord; } @@ -73,8 +74,8 @@ var Temporal = function(model, sequelize, temporalOptions) { if(!options.individualHooks){ var queryAll = model.findAll({where: options.where, transaction: options.transaction}).then(function(hits){ if(hits){ - hits = _.map(hits, 'dataValues'); - return modelHistory.bulkCreate(hits, {transaction: options.transaction}); + hits = _.map(hits, 'dataValues'); + return modelHistory.bulkCreate(hits, {transaction: temporalOptions.allowTransactions? options.transaction: null}); } }); if(temporalOptions.blocking){ diff --git a/test/test.js b/test/test.js index b03c02d..6bcf615 100644 --- a/test/test.js +++ b/test/test.js @@ -8,7 +8,8 @@ const assert = chai.assert; const eventually = assert.eventually; describe('Read-only API', function(){ - var sequelize; + var sequelize; + var sequelizeHist; function newDB(paranoid, options){ if(sequelize) { @@ -16,14 +17,31 @@ describe('Read-only API', function(){ sequelize = null; } - const dbFile = __dirname + '/.test.sqlite'; - try {fs.unlinkSync(dbFile);} catch {}; + const separate = !(!options || !options.test || !options.test.separate || options.test.separate == false) + + const dbFile = __dirname + '/.test.sqlite'; + + try {fs.unlinkSync(dbFile);} catch {}; sequelize = new Sequelize('', '', '', { dialect: 'sqlite', storage: dbFile, logging: false//console.log - }); + }); + + if(separate == true) { + console.warn('Test is using separate DBs'); + + const dbFile2 = __dirname + '/.test2.sqlite'; + + try {fs.unlinkSync(dbFile2);} catch {}; + + sequelizeHist = new Sequelize('', '', '', { + dialect: 'sqlite', + storage: dbFile2, + logging: false//console.log + }); + } //Define origin models const User = sequelize.define('User', { name: Sequelize.TEXT }, {paranoid: paranoid || false}); @@ -50,13 +68,13 @@ describe('Read-only API', function(){ Creation.belongsToMany(Tag, { through: CreationTag, foreignKey: 'creation', otherKey: 'tag' }); //Historyize - Historical(User, sequelize, options); - Historical(Creation, sequelize, options); - Historical(Tag, sequelize, options); - Historical(Event, sequelize, options); - Historical(CreationTag, sequelize, options); - - return sequelize.sync({force:true}); + Historical(User, separate == true? sequelizeHist: sequelize, options); + Historical(Creation, separate == true? sequelizeHist: sequelize, options); + Historical(Tag, separate == true? sequelizeHist: sequelize, options); + Historical(Event, separate == true? sequelizeHist: sequelize, options); + Historical(CreationTag, separate == true? sequelizeHist: sequelize, options); + + return sequelize.sync({force:true}).then( s => separate == true ? sequelizeHist.sync({force:true}): s); } //Adding 3 tags, 2 creations, 2 events, 2 user @@ -244,6 +262,10 @@ describe('Read-only API', function(){ return newDB(); } + function freshDBWithSeparateHistoryDB(){ + return newDB(false, { allowTransactions: false , test: {separate: true}}); + } + function freshDBWithAssociations(){ return newDB(false, { addAssociations: true}); } @@ -258,15 +280,79 @@ describe('Read-only API', function(){ function assertCount(modelHistory, n, opts) { // wrapped, chainable promise - return function(obj) { - return modelHistory.count(opts).then((count) => { - //console.log('Asserting ', modelHistory.name, ' count: ', count, ' expected: ', n); + return function(obj) { + return modelHistory.count(opts).then((count) => { assert.equal(n, count, "history entries") return obj; }); } } + describe('Separate DB Tests', function() { + beforeEach(freshDBWithSeparateHistoryDB); + + it('onUpdate/onDestroy: should save to the historyDB' , function() { + return sequelize.models.User.create() + .then(assertCount(sequelizeHist.models.UserHistory,0)) + .then((user) => { + user.name = "foo"; + return user.save(); + }) + .then(assertCount(sequelizeHist.models.UserHistory,1)) + .then(user => user.destroy()) + .then(assertCount(sequelizeHist.models.UserHistory,2)) + }); + + it('revert on failed transactions' , function() { + return sequelize.transaction() + .then((t) => { + var opts = {transaction: t}; + return sequelize.models.User.create({name: "not foo"}) + .then(assertCount(sequelizeHist.models.UserHistory,0)) + .then((user) => { + user.name = "foo"; + user.save(opts); + }) + .then(assertCount(sequelizeHist.models.UserHistory,1)) + .then(() => t.rollback()); + }) + .then(assertCount(sequelizeHist.models.UserHistory,1)); + }); + + it('should archive every entry', function() { + return sequelize.models.User.bulkCreate([{name: "foo1"},{name: "foo2"}]) + .then(assertCount(sequelizeHist.models.UserHistory,0)) + .then(() => sequelize.models.User.update({ name: 'updated-foo' }, {where: {}})) + .then(assertCount(sequelizeHist.models.UserHistory,2)) + }); + + it('should revert under transactions', function() { + return sequelize.transaction() + .then(function(t) { + var opts = {transaction: t}; + return sequelize.models.User.bulkCreate([{name: "foo1"},{name: "foo2"}], opts) + .then(assertCount(sequelizeHist.models.UserHistory,0)) + .then(() => sequelize.models.User.update({ name: 'updated-foo' }, {where: {}, transaction: t})) + .then(assertCount(sequelizeHist.models.UserHistory,2)) + .then(() => t.rollback()); + }) + .then(assertCount(sequelizeHist.models.UserHistory,2)); + }); + + it('should revert on failed transactions, even when using after hooks' , function(){ + return sequelize.transaction() + .then((transaction) => { + var options = { transaction: transaction }; + + return sequelize.models.User.create({ name: 'test' }, options) + .then(user => user.destroy(options)) + .then(assertCount(sequelizeHist.models.UserHistory, 1)) + .then(() => transaction.rollback()); + }) + .then(assertCount(sequelizeHist.models.UserHistory,1)); + }); + }); + describe('Association Tests', function() { describe('test there are no history association', function(){ beforeEach(freshDB); From f611c01bf999fc9cb9da436427fc60e00470189d Mon Sep 17 00:00:00 2001 From: kurisutofu Date: Wed, 12 Jun 2019 02:41:27 +0900 Subject: [PATCH 37/37] Added AfterBulk hooks --- index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index f48a255..c35ab17 100644 --- a/index.js +++ b/index.js @@ -122,8 +122,10 @@ var Temporal = function(model, sequelize, temporalOptions) { // all hooks just create a copy if (temporalOptions.full) { model.addHook('afterCreate', insertHook); - model.addHook('afterUpdate', insertHook); - model.addHook('afterDestroy', insertHook); + model.addHook('afterUpdate', insertHook); + model.addHook('afterBulkUpdate', insertBulkHook); + model.addHook('afterDestroy', insertHook); + model.addHook('afterBulkDestroy', insertBulkHook); model.addHook('afterRestore', insertHook); } else { model.addHook('beforeUpdate', insertHook);