Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Tom Kirkpatrick committed Feb 8, 2017
0 parents commit 8eb408b
Show file tree
Hide file tree
Showing 11 changed files with 465 additions and 0 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
coverage
4 changes: 4 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "fullcube",
"root": true
}
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
npm-debug.log
node_modules/
coverage/
.idea
.nyc_output/
4 changes: 4 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
.git
.idea/
npm-debug.log
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
language: node_js
node_js:
- "6"
- "5"
- "4"
85 changes: 85 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
CASCADE UPDATE MIXIN
================

This module is designed for the [Strongloop Loopback](https://github.com/strongloop/loopback) framework. It provides cascade update functionality with a simple configuration on your models. This can be used to keep property values synchronised across related models.

INSTALL
================

```bash
npm install --save loopback-ds-cascade-update-mixin
```

SERVER CONFIG
=============
Add the mixins property to your server/model-config.json:

```json
{
"_meta": {
"sources": [
"loopback/common/models",
"loopback/server/models",
"../common/models",
"./models"
],
"mixins": [
"loopback/common/mixins",
"../node_modules/loopback-ds-cascade-update-mixin/lib",
"../common/mixins"
]
}
}
```

CONFIG
=============

To use with your Models add the `mixins` attribute to the definition object of your model config.

The config keys are the property names that you want to cascade.

The config values are an array of relationships that the property updates should cascade to.


```json
{
"name": "Product",
"properties": {
"name": {
"type": "string",
}
},
"relations": {
"relation1": {
"type": "hasMany",
"model": "Property",
"foreignKey": ""
}
},
"mixins": {
"CascadeUpdate": {
"status": [ "relation1", "relation2" ],
"isTest": [ "relation1", "relation2" "relation3" ]
}
}
}
```

TESTING
=============

Run the tests in `test.js`

```bash
npm test
```

DEBUGGING
=============

Run with debugging output on:

```bash
DEBUG='loopback:mixins:cascade-update' npm test
```
85 changes: 85 additions & 0 deletions lib/cascade-update.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
'use strict'

const debug = require('debug')('loopback:mixins:cascade-update')
const _ = require('lodash')

module.exports = function cascadeUpdateMixin(Model, options) {

debug('CascadeUpdate mixin for Model %s with options %o', Model.modelName, options)

function idName(m) {
return m.definition.idName() || 'id'
}

function getIdValue(m, data) {
return _.get(data, idName(m))
}

function cascadeUpdates(instance) {
return Promise.resolve(Object.keys(options))
.each(property => Promise.resolve([].concat(options[property]))
.map(relation => {
debug(`Cascading '${property}' property via '${relation}' relation on ${Model.definition.name} model`)

if (!Model.relations[relation]) {
debug(`Relation ${relation} not found for model ${Model.definition.name}`)
return null
}

if (typeof instance[property] === 'undefined') {
debug(`Property ${property} not found for model ${Model.definition.name}`)
return null
}

const where = {}
const data = {}

let relationModel = Model.relations[relation].modelTo
const relationKey = Model.relations[relation].keyTo

if (Model.relations[relation].modelThrough) {
relationModel = Model.relations[relation].modelThrough
}

if (Model.relations[relation].polymorphic) {
where[Model.relations[relation].polymorphic.discriminator] = Model.definition.name
}

where[relationKey] = getIdValue(Model, instance)
data[property] = instance[property]

debug(`Calling ${relationModel.definition.name}.updateAll(%o, %o)`, where, data)

return relationModel.updateAll(where, data)
.then(res => debug(`Updated ${res.count} items from the ${relationModel.definition.name} model`))
})
)
}

Model.observe('after save', (ctx, next) => {
debug('after save: ctx.instance', ctx.instance)
debug('after save: ctx.where', ctx.where)
debug('after save: ctx.data', ctx.data)

const name = idName(Model)
const hasInstanceId = ctx.instance && ctx.instance[name]
const hasWhereId = ctx.where && ctx.where[name]

if (ctx.isNewInstance) {
debug('Skipping update for ', Model.definition.name, 'isNewInstance')
return next()
}

if (!(hasWhereId || hasInstanceId)) {
debug('Skipping update for ', Model.definition.name, 'Multi-instance update')
return next()
}

return cascadeUpdates(ctx.instance || ctx.data)
.then(() => debug('Cascade update has successfully finished'))
.catch(err => {
debug('Error with cascading updates', err)
throw err
})
})
}
62 changes: 62 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"name": "loopback-ds-cascade-update-mixin",
"description": "Loopback cascade update mixin.",
"version": "0.0.0-development",
"author": {
"name": "Tom Kirkpatrick @mrfelton"
},
"repository": {
"type": "git",
"url": "https://github.com/fullcube/loopback-ds-cascade-update-mixin.git"
},
"keywords": [
"loopback",
"strongloop",
"cascade"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/fullcube/loopback-ds-cascade-update-mixin/issues"
},
"homepage": "https://github.com/fullcube/loopback-ds-cascade-update-mixin",
"files": [
"lib",
"test"
],
"directories": {
"lib": "lib",
"test": "test"
},
"main": "lib/cascade-update.js",
"scripts": {
"lint": "eslint .",
"pretest": "npm run lint",
"test": "nyc --reporter=lcov --reporter=text --reporter=text-summary mocha test/*test.js",
"test:watch": "npm run test -- -w",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"semantic-release": "semantic-release pre && npm publish && semantic-release post"
},
"devDependencies": {
"chai": "^3.5.0",
"condition-circle": "^1.5.0",
"coveralls": "^2.11.16",
"dirty-chai": "^1.2.2",
"eslint": "2.13.1",
"eslint-config-fullcube": "latest",
"eslint-plugin-mocha": "latest",
"loopback": "^2.36.0",
"mocha": "^3.2.0",
"mocha-sinon": "^1.1.6",
"nyc": "^10.1.2",
"semantic-release": "^6.3.2",
"sinon": "^1.17.6",
"sinon-chai": "^2.8.0"
},
"dependencies": {
"lodash": "^4.17.2",
"debug": "^2.3.3"
},
"release": {
"verifyConditions": "condition-circle"
}
}
9 changes: 9 additions & 0 deletions test/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "fullcube/mocha",
"root": true,
"globals": {
"loopback": true,
"expect": true,
"sinon": true
}
}
1 change: 1 addition & 0 deletions test/mocha.opts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--reporter spec
Loading

0 comments on commit 8eb408b

Please sign in to comment.