Skip to content

Commit 20da249

Browse files
authored
feat: allow methods to pass arguments to force update status (#5)
* feat: listen to allowForce component and allow to force status change * chore: delete the unwanted code from tests * test: add some test to cover force status update * documentation: update readme for force status change option * remove .only from new tests * test: clean up force change tests * docs: reword docs for allowForce setting * test: add additional test to verify sequential state change * refactor: improve code readability * refactor: always expect params as first method argument * docs: document the params method argument
1 parent 571d99f commit 20da249

File tree

9 files changed

+191
-379
lines changed

9 files changed

+191
-379
lines changed

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ When a model method that is controlled by the Finite State Machine is called it
3030
"mixins": {
3131
"StateMachine": {
3232
"stateProperty": "status",
33+
"settings": {
34+
"allowForce": true
35+
},
3336
"events": [
3437
{ "name": "activate", "from": "none", "to": "active", "transitionOptions": { "skipBeforeSave" : true } },
3538
{ "name": "cancel", "from": "active", "to": "canceled" },
@@ -46,6 +49,10 @@ When a model method that is controlled by the Finite State Machine is called it
4649

4750
[String] : The name of the model's state property. *(default: 'state')*
4851

52+
- `settings`
53+
54+
[Object] : Settings used to control state machine operations. Currently the only supported option is `allowForce` which when set to `true` makes it possible to carry out an otherwise invalid state change by passing in `{ force: true }` at method call time. This option can also be set per event. *(default: {})*
55+
4956
- `events`
5057

5158
[Array] : A list of events available to the state machine. Refer to the [FSM As Promised documentation](https://github.com/vstirbu/fsm-as-promised) for details on how events should be defined. *(default: [])*
@@ -86,7 +93,7 @@ example, the above mixin configuration will result in the following methods bein
8693
- `MyModel.prototype.reactivate`
8794
- `MyModel.prototype.expire`
8895

89-
These methods can be called as any other:
96+
These methods all accept a first argument that is a settings object that is used by the fsm to determine how it functions (eg passing `{ force: true }` (see `allowForce` above). All arguments will be available from within the various fsm notifications.
9097

9198
```javascript
9299
MyModel.findOne()
@@ -96,7 +103,7 @@ MyModel.findOne()
96103
})
97104
.then(instance => {
98105
log.debug(`Current state is: ${instance.state}`) // Current state is: canceled
99-
return instance.reactivate()
106+
return instance.reactivate({ force: true })
100107
})
101108
.then(instance => {
102109
log.debug(`Current state is: ${instance.state}`) // Current state is: active

lib/mixins/state-machine.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,21 @@ module.exports = function StateMachine(Model, settings) {
127127

128128
// Add event methods to the prototype.
129129
Model.getEventNames(settings.events).forEach(eventName => {
130-
Model.prototype[eventName] = function event(...args) {
130+
Model.prototype[eventName] = function event(params = {}, ...args) {
131+
const eventNameIndex = _.findKey(settings.events, { 'name': eventName })
132+
const forceFsmSetting = _.get(settings, 'settings.allowForce')
133+
const forceEventSetting = _.get(settings, `events[${eventNameIndex}].settings.allowForce`)
134+
const force = _.get(params, 'force')
135+
136+
// If force has been requested, override the state machine config.
137+
if (force && (forceFsmSetting || forceEventSetting)) {
138+
_.assign(settings.events[eventNameIndex].from, [ this.status ])
139+
}
140+
141+
// Get the FSM.
131142
const fms = Model.app.getStateMachine(this)
132143

144+
// Run the event method.
133145
return fms[eventName](this, ...args)
134146
.then(result => result)
135147
.finally(() => Model.app.deleteStateMachine(this))

test/fullcube-state-machine/.apiconnect/config

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
{
2+
"name": "Order",
3+
"base": "PersistedModel",
4+
"idInjection": true,
5+
"options": {
6+
"validateUpsert": true
7+
},
8+
"properties": {
9+
"status": {
10+
"type": "string",
11+
"required": true,
12+
"default": "prepare"
13+
}
14+
},
15+
"validations": [],
16+
"relations": {},
17+
"acls": [],
18+
"methods": {
19+
"cancel": {
20+
"isStatic": false,
21+
"accepts": [],
22+
"returns": {
23+
"arg": "order",
24+
"type": "Order",
25+
"root": true,
26+
"description": "The updated order"
27+
},
28+
"description": "Cancel a order",
29+
"http": [
30+
{
31+
"path": "/cancel",
32+
"verb": "put"
33+
}
34+
]
35+
},
36+
"deliver": {
37+
"isStatic": false,
38+
"accepts": [],
39+
"returns": {
40+
"arg": "order",
41+
"type": "Order",
42+
"root": true,
43+
"description": "The updated order"
44+
},
45+
"description": "Deliver a canceled order",
46+
"http": [
47+
{
48+
"path": "/deliver",
49+
"verb": "put"
50+
}
51+
]
52+
},
53+
"prepare": {
54+
"isStatic": false,
55+
"accepts": [{
56+
"arg": "params",
57+
"type": "Object",
58+
"description": "Transition params."
59+
}],
60+
"returns": {
61+
"arg": "order",
62+
"type": "Order",
63+
"root": true,
64+
"description": "The updated order"
65+
},
66+
"description": "Prepare a order",
67+
"http": [
68+
{
69+
"path": "/prepare",
70+
"verb": "put"
71+
}
72+
]
73+
}
74+
},
75+
"mixins": {
76+
"StateMachine": {
77+
"stateProperty": "status",
78+
"settings": {
79+
"allowForce": true
80+
},
81+
"events": [
82+
{ "name": "cancel", "from": "prepare", "to": "canceled" },
83+
{ "name": "deliver", "from": "prepare", "to": "delivered", "settings": { "allowForce" : true } },
84+
{ "name": "prepare", "from": [ "none" ], "to": "prepared" }
85+
]
86+
}
87+
}
88+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict'
2+
3+
const Promise = require('bluebird')
4+
const log = require('loglevel')
5+
6+
log.enableAll()
7+
8+
module.exports = class FcOrder {
9+
constructor(instance) {
10+
this.instance = instance
11+
}
12+
prepare() {
13+
return Promise.delay(2000).then(() => {
14+
log.info(`Order ${this.instance.id} prepared`)
15+
})
16+
}
17+
18+
cancel() {
19+
return Promise.delay(2000).then(() => {
20+
log.info(`Order ${this.instance.id} canceled`)
21+
})
22+
}
23+
24+
deliver() {
25+
return Promise.delay(2000).then(() => {
26+
log.info(`Order ${this.instance.id} delivered`)
27+
})
28+
}
29+
}

test/fullcube-state-machine/definitions/fullcube-state-machine-product.yaml

Lines changed: 0 additions & 25 deletions
This file was deleted.

0 commit comments

Comments
 (0)