Skip to content

Commit 40712e1

Browse files
committed
feat(User): add Datastore support
1 parent a494d0e commit 40712e1

File tree

4 files changed

+99
-2
lines changed

4 files changed

+99
-2
lines changed

docs/scripting.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,21 @@ robot.respond /sleep it off/i, (res) ->
623623
res.reply 'zzzzz'
624624
```
625625

626+
The datastore also allows setting and getting values which are scoped to individual users:
627+
628+
```coffeescript
629+
module.exports = (robot) ->
630+
631+
robot.respond /who is @?([\w .\-]+)\?*$/i, (res) ->
632+
name = res.match[1].trim()
633+
634+
users = robot.brain.usersForFuzzyName(name)
635+
if users.length is 1
636+
user = users[0]
637+
user.get('roles').then (roles) ->
638+
res.send "#{name} is #{roles.join(', ')}"
639+
```
640+
626641
## Script Loading
627642

628643
There are three main sources to load scripts from:

src/brain.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const User = require('./user')
1010
// 2. If the original object was a User object, the original object
1111
// 3. If the original object was a plain JavaScript object, return
1212
// a User object with all of the original object's properties.
13-
let reconstructUserIfNecessary = function (user) {
13+
let reconstructUserIfNecessary = function (user, robot) {
1414
if (!user) {
1515
return null
1616
}
@@ -20,6 +20,9 @@ let reconstructUserIfNecessary = function (user) {
2020
delete user.id
2121
// Use the old user as the "options" object,
2222
// populating the new user with its values.
23+
// Also add the `robot` field so it gets a reference.
24+
user.robot = robot
25+
2326
return new User(id, user)
2427
} else {
2528
return user
@@ -36,6 +39,7 @@ class Brain extends EventEmitter {
3639
users: {},
3740
_private: {}
3841
}
42+
this.robot = robot
3943

4044
this.autoSave = true
4145

@@ -142,7 +146,7 @@ class Brain extends EventEmitter {
142146
if (data && data.users) {
143147
for (let k in data.users) {
144148
let user = this.data.users[k]
145-
this.data.users[k] = reconstructUserIfNecessary(user)
149+
this.data.users[k] = reconstructUserIfNecessary(user, this.robot)
146150
}
147151
}
148152

@@ -161,6 +165,10 @@ class Brain extends EventEmitter {
161165
// Returns a User instance of the specified user.
162166
userForId (id, options) {
163167
let user = this.data.users[id]
168+
if (!options) {
169+
options = {}
170+
}
171+
options.robot = this.robot
164172

165173
if (!user) {
166174
user = new User(id, options)

src/user.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use strict'
22

3+
const DataStoreUnavailable = require('./datastore').DataStoreUnavailable
4+
35
class User {
46
// Represents a participating user in the chat.
57
//
@@ -12,6 +14,17 @@ class User {
1214
options = {}
1315
}
1416

17+
// Define a getter method so we don't actually store the
18+
// robot itself on the user object, preventing it from
19+
// being serialized into the brain.
20+
if (options.robot) {
21+
let robot = options.robot
22+
delete options.robot
23+
this._getRobot = function () { return robot }
24+
} else {
25+
this._getRobot = function () { }
26+
}
27+
1528
Object.keys(options).forEach((key) => {
1629
this[key] = options[key]
1730
})
@@ -20,6 +33,33 @@ class User {
2033
this.name = this.id.toString()
2134
}
2235
}
36+
37+
set (key, value) {
38+
this._checkDatastoreAvailable()
39+
return this._getDatastore()._set(this._constructKey(key), value, 'users')
40+
}
41+
42+
get (key) {
43+
this._checkDatastoreAvailable()
44+
return this._getDatastore()._get(this._constructKey(key), 'users')
45+
}
46+
47+
_constructKey (key) {
48+
return `${this.id}+${key}`
49+
}
50+
51+
_checkDatastoreAvailable () {
52+
if (!this._getDatastore()) {
53+
throw new DataStoreUnavailable('datastore is not initialized')
54+
}
55+
}
56+
57+
_getDatastore () {
58+
let robot = this._getRobot()
59+
if (robot) {
60+
return robot.datastore
61+
}
62+
}
2363
}
2464

2565
module.exports = User

test/datastore_test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,38 @@ describe('Datastore', function () {
113113
})
114114
})
115115
})
116+
117+
describe('User scope', function () {
118+
it('has access to the robot object', function () {
119+
let user = this.robot.brain.userForId('1')
120+
expect(user._getRobot()).to.equal(this.robot)
121+
})
122+
123+
it('can store user data which is separate from global data', function () {
124+
let user = this.robot.brain.userForId('1')
125+
return user.set('blah', 'blah').then(() => {
126+
return user.get('blah').then((userBlah) => {
127+
return this.robot.datastore.get('blah').then((datastoreBlah) => {
128+
expect(userBlah).to.not.equal(datastoreBlah)
129+
expect(userBlah).to.equal('blah')
130+
expect(datastoreBlah).to.be.an('undefined')
131+
})
132+
})
133+
})
134+
})
135+
136+
it('stores user data separate per-user', function () {
137+
let userOne = this.robot.brain.userForId('1')
138+
let userTwo = this.robot.brain.userForId('2')
139+
return userOne.set('blah', 'blah').then(() => {
140+
return userOne.get('blah').then((valueOne) => {
141+
return userTwo.get('blah').then((valueTwo) => {
142+
expect(valueOne).to.not.equal(valueTwo)
143+
expect(valueOne).to.equal('blah')
144+
expect(valueTwo).to.be.an('undefined')
145+
})
146+
})
147+
})
148+
})
149+
})
116150
})

0 commit comments

Comments
 (0)