Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
vamsee committed Oct 6, 2020
2 parents 3a375fc + 4168619 commit b53cd7d
Show file tree
Hide file tree
Showing 12 changed files with 473 additions and 85 deletions.
108 changes: 108 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# oe-common-mixins


- [Introduction](#introduction)
* [dependency](#dependency)
* [Getting Started](#getting-started)
Expand All @@ -17,6 +18,10 @@
+ [Loading Mixin using app-list.json](#loading-mixin-using-app-listjson-1)
+ [Loading Mixin pragmatically](#loading-mixin-pragmatically-1)
* [Developer Considerations](#developer-considerations-1)
* [EmbedsOne Relation](#embedsone-relation)
+ [POST - create parent record](#post---create-parent-record)
+ [PUT - Update parent record](#put---update-parent-record)
+ [PUT - Updating embedded record](#put---updating-embedded-record)
- [Soft Delete Mixin](#soft-delete-mixin)
* [Using SoftDeleteMixin](#using-softdeletemixin)
+ [Loading Mixin using model-config.json](#loading-mixin-using-model-configjson-1)
Expand Down Expand Up @@ -272,6 +277,109 @@ This can be confusing because for some models you will pass version while deleti

* If programmer calls updateAll method in javascript, version checking would not be possible as multiple records are getting updated. For such models where version mixin is enabled, you should disable updateAll method. If it is not disabled or it is somehow gets called, concurrent update of same records could be possible.

## EmbedsOne Relation
When model is Embedded, version mixin behavior is little different and you need to take care of special case.

### POST - create parent record

When you are creating record in parent model and passing data of embedded model along, you don't have to worry about anything special.

Once record is created in parent model, _version will automatically generated and same _version field will be populated for both parent and embedded record. Response of such request will look like

POST /api/Books
```
{
"name": "a",
"id": "a",
"_isDeleted": false,
"_version": "679d0579-67d2-4e12-83a1-f1c8c20981c9",
"publisher": {
"name": "a",
"age": 0,
"id": "a",
"_isDeleted": false,
"_version": "679d0579-67d2-4e12-83a1-f1c8c20981c9"
}
}
```
**Validation** : Child record and parent record both are validated.

### PUT - Update parent record

For this operation, _version field must be populated in your request body. _version must be existing version of the existing record that you are trying to modify.
If no record with _version is found then oecloud will throw error.

PUT /api/Books/a
```
{
"name": "a-changed",
"id": "a",
"_version": "679d0579-67d2-4e12-83a1-f1c8c20981c9"
}
```
Response

```
{
"name": "a-changed",
"id": "a",
"_isDeleted": false,
"_oldVersion": "679d0579-67d2-4e12-83a1-f1c8c20981c9",
"_version": "c289c6d2-4bdc-482a-8adc-76d215a402f5",
"publisher": {
"name": "a",
"age": 0,
"id": "a",
"_isDeleted": false,
"_version": "679d0579-67d2-4e12-83a1-f1c8c20981c9"
}
}
```
Note that only parent record's _version is updated with new version. This will make _version field in embedded model and parent model go **out of sync**.

**Validation** : only parent data is validated.

### PUT - Updating embedded record

This is tricky operation. Here you want to just update embedded model record. But since embedded data is not residing in separate collection (or table), you are really modifying parent record. Therefore you need to supply parent record's version as well as child record's version. Parent record's version is supplied in _parentVersion field.

/api/Books/a/personRel
```
{
"name": "publsher updatted",
"age": 11,
"id": "n",
"_version": "679d0579-67d2-4e12-83a1-f1c8c20981c9",
"_parentVersion" :"c289c6d2-4bdc-482a-8adc-76d215a402f5"
}
```
As you can see above, you will see both _version and _parentVersion is supplied. This becomes important because parent record's version and embedded record version went out of sync.

However when both are in 'sync', you may either supply only _parentVersion or _version.
However it is good practice to supply both of these fields.

Response of such request will look like

```
{
"name": "publsher updatted",
"age": 11,
"id": "n",
"_isDeleted": false,
"_version": "486c7ea9-f1b6-413a-afdf-64210e72e81e",
"_parentVersion": "c289c6d2-4bdc-482a-8adc-76d215a402f5"
}
```
if you go to database, you will find _version field of both parent and embedded record is same.

**Validation** : All fields in embedded model are validated. However _version is validated for parent Model also. **before save** hooks on embedded Model and parent model are called.


**Embedded model without Version Mixin**
In above examples, we have assumed that embedded model will too have VersionMixin enabled. But if that is not the case, then it will not be possible to pass _parentVersion as **strict** flag on model is true by default.




# Soft Delete Mixin

Expand Down
6 changes: 5 additions & 1 deletion common/mixins/history-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@ function createHistoryData(ctx, next) {
if (!historyModel) {
return next();
}
if (ctx && ctx.embedsOne ) {
return next();
}
if (ctx.currentInstance) {
ctx.hookState.historyData = [ctx.currentInstance.toObject()];
return next();
Expand Down Expand Up @@ -238,13 +241,14 @@ function createHistoryData(ctx, next) {
if (err) {
return next(err);
}

if (!data) {
var e = new Error('Model ID error ' + id);
e.message = 'Model ID error ' + id;
return next(e);
}
ctx.hookState.historyData = [data.toObject()];
ctx.currentInstance = data;
// ctx.currentInstance = data;
return next();
});
} else {
Expand Down
38 changes: 37 additions & 1 deletion common/mixins/version-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

const uuidv4 = require('uuid/v4');
const oecloudutil = require('oe-cloud/lib/common/util');
const isMixinEnabled = require('../../lib/utils').isMixinEnabled;

module.exports = function VersionMixin(Model) {
if (Model.modelName === 'BaseEntity') {
Expand All @@ -42,6 +43,11 @@ module.exports = function VersionMixin(Model) {
type: String
});

Model.defineProperty('_parentVersion', {
type: String
});


// Model.settings._versioning = true;
// Model.settings.updateOnLoad = true;

Expand Down Expand Up @@ -112,18 +118,48 @@ module.exports = function VersionMixin(Model) {
data._version = data._newVersion || data._version || uuidv4();
delete data._oldVersion;
delete data._newVersion;
}
if (ctx.Model.relations) {
var relations = ctx.Model.relations;
for (var r in ctx.Model.relations) {
if (relations[r].type !== 'embedsOne' && relations[r].type !== 'embedsMany') {
continue;
}
var keyFrom = relations[r].keyFrom;
if (!data._version && !ctx.isNewInstance && data[keyFrom] && typeof data[keyFrom] === 'object') {
if (Array.isArray(data[keyFrom]) && data[keyFrom].length > 0 ) {
// Atul : For embeds many, it will be array
data._version = data[keyFrom][0]._parentVersion || data[keyFrom][0]._version;
} else {
data._version = data[keyFrom]._parentVersion || data[keyFrom]._version;
}
break;
} else if (ctx.isNewInstance && isMixinEnabled(relations[r].modelTo, 'VersionMixin')) {
if (relations[r].type === 'embedsOne' && data[keyFrom]) {
data[keyFrom]._version = data._version;
} else if (relations[r].type === 'embedsMany' && Array.isArray(data[keyFrom]) && data[keyFrom].length) {
data[keyFrom].forEach(function (item) {
item._version = data._version;
});
}
}
}
}
if (ctx.isNewInstance) {
return next();
}

var error;
var id = oecloudutil.getIdValue(ctx.Model, data);
var _version = data._version;

if (!data._version) {
error = new Error();
Object.assign(error, { name: 'Data Error', message: 'Version must be defined. id ' + id + ' for model ' + Model.modelName, code: 'DATA_ERROR_071', type: 'DataModifiedError', retriable: false, status: 422 });
return next(error);
}
if (ctx.currentInstance) {
if (ctx.currentInstance._version !== ctx.data._version) {
if (ctx.currentInstance._version !== data._version) {
error = new Error();
Object.assign(error, { name: 'Data Error', message: 'Version must be be same. id ' + id + ' for model ' + Model.modelName + ' Version ' + _version + ' <> ' + ctx.currentInstance._version, code: 'DATA_ERROR_071', type: 'DataModifiedError', retriable: false, status: 422 });
return next(error);
Expand Down
36 changes: 30 additions & 6 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/**
*
* ©2018-2019 EdgeVerve Systems Limited (a fully owned Infosys subsidiary),
* Bangalore, India. All Rights Reserved.
*
/**
*
* ©2018-2019 EdgeVerve Systems Limited (a fully owned Infosys subsidiary),
* Bangalore, India. All Rights Reserved.
*
*/
/**
* This is file contains utility function to check if version of record being updated
Expand All @@ -12,9 +12,10 @@
* @author : Atul Pandit
*/

// Author : Atul
// Author : Atul
const log = require('oe-logger')('oe-common-mixins-utils');
const oeutils = require('oe-cloud/lib/common/util');
const loopback = require('loopback');
module.exports.checkIfVersionMatched = function checkIfVersionMatched(Model, id, version, next) {
if (!version) {
var error = new Error();
Expand Down Expand Up @@ -52,4 +53,27 @@ module.exports.checkIfVersionMatched = function checkIfVersionMatched(Model, id,
});
};

module.exports.isMixinEnabled = function isMixinEnabled(model, mixin) {
var Model;

if (typeof model === 'string') {
Model = loopback.findModel(model);
} else {
Model = model;
}
if (!Model.settings || !Model.settings.mixins) {
return false;
}
var flag = Model.settings.mixins[mixin];
if (!flag) {
return false;
}
if (flag) {
if (Model.settings.overridingMixins && !Model.settings.overridingMixins[mixin]) {
return false;
}
}
return flag;
};


Loading

0 comments on commit b53cd7d

Please sign in to comment.