Skip to content

Commit 56a7ab3

Browse files
committed
- fix circular dependencies in DataStore
1 parent 008ed95 commit 56a7ab3

File tree

4 files changed

+165
-35
lines changed

4 files changed

+165
-35
lines changed

src/data/store/index.js

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Utils from '../../utils'
22
import { deprecated } from '../../decorators'
3-
import User from '../../users/user'
3+
import { resolveModelClassFromString } from '../utils'
44

55
import { loadRelations, setRelation, addRelation, deleteRelation } from './relations'
66
import { bulkCreate, bulkUpdate, bulkDelete } from './bulk'
@@ -9,12 +9,6 @@ import { save } from './save'
99
import { remove } from './remove'
1010
import { getObjectCount } from './count'
1111

12-
const createModelClassFromString = (/** className */) => {
13-
//TODO:fix me
14-
return function() {
15-
}
16-
}
17-
1812
//TODO: will be removed when remove sync methods
1913
const namespaceLabel = 'Backendless.Data.of(<ClassName>)'
2014

@@ -23,9 +17,7 @@ class DataStore {
2317
constructor(model) {
2418
if (Utils.isString(model)) {
2519
this.className = model
26-
this.model = model === User.className
27-
? User
28-
: createModelClassFromString(model)
20+
this.model = resolveModelClassFromString(this.className)
2921

3022
} else {
3123
this.className = Utils.getClassName(model)

src/data/store/parse.js

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,79 @@
11
import Utils from '../../utils'
2+
import { resolveModelClassFromString } from '../utils'
23

3-
//TODO: refactor me
4+
function isObject(item) {
5+
return typeof item === 'object' && item !== null
6+
}
47

5-
function formCircDeps(obj) {
8+
function parseCircularDependencies(obj) {
69
const result = new obj.constructor()
7-
const circDepsIDs = {}
8-
const iteratedObjects = []
10+
const subIds = {}
11+
const postAssign = []
12+
const iteratedItems = []
913

10-
const _formCircDepsHelper = (obj, res) => {
11-
if (iteratedObjects.indexOf(obj) === -1) {
12-
iteratedObjects.push(obj)
14+
function ensureCircularDep(source, target, prop) {
15+
if (subIds[source[prop].__originSubID]) {
16+
target[prop] = subIds[source[prop].__originSubID]
17+
} else {
18+
postAssign.push([target, prop, source[prop].__originSubID])
19+
}
20+
}
1321

14-
if (obj.hasOwnProperty('__subID')) {
15-
circDepsIDs[obj['__subID']] = res
16-
delete obj['__subID']
17-
}
22+
function processModel(source, target, prop) {
23+
const Model = resolveModelClassFromString(source[prop].___class) || source[prop].constructor
24+
25+
target[prop] = new Model()
26+
27+
if (source[prop].__subID) {
28+
subIds[source[prop].__subID] = target[prop]
29+
delete source[prop].__subID
30+
}
31+
}
32+
33+
function buildCircularDeps(source, target) {
34+
if (iteratedItems.indexOf(source) === -1) {
35+
iteratedItems.push(source)
36+
37+
for (const prop in source) {
38+
if (source.hasOwnProperty(prop)) {
39+
if (Array.isArray(source[prop])) {
40+
buildCircularDeps(source[prop], target[prop] = [])
41+
42+
} else if (isObject(source[prop])) {
43+
if (source[prop].__originSubID) {
44+
ensureCircularDep(source, target, prop)
1845

19-
for (const prop in obj) {
20-
if (obj.hasOwnProperty(prop)) {
21-
if (typeof obj[prop] === 'object' && obj[prop] != null) {
22-
if (obj[prop].hasOwnProperty('__originSubID')) {
23-
res[prop] = circDepsIDs[obj[prop]['__originSubID']]
2446
} else {
25-
res[prop] = new (obj[prop].constructor)()
26-
_formCircDepsHelper(obj[prop], res[prop])
47+
processModel(source, target, prop)
48+
49+
buildCircularDeps(source[prop], target[prop])
2750
}
51+
2852
} else {
29-
res[prop] = obj[prop]
53+
target[prop] = source[prop]
3054
}
3155
}
3256
}
3357
}
3458
}
3559

36-
_formCircDepsHelper(obj, result)
60+
buildCircularDeps(obj, result)
61+
62+
postAssign.forEach(([target, prop, __originSubID]) => target[prop] = subIds[__originSubID])
3763

3864
return result
3965
}
4066

4167
export function parseFindResponse(response, Model) {
42-
Model = Utils.isFunction(Model) ? Model : undefined
43-
4468
const sanitizeResponseItem = resp => {
45-
const item = Model ? new Model() : {}
69+
Model = Utils.isFunction(Model) ? Model : resolveModelClassFromString(resp.___class)
4670

47-
return Utils.deepExtend(item, resp.fields || resp)
71+
return Utils.deepExtend(new Model(), resp.fields || resp)
4872
}
4973

5074
const result = Utils.isArray(response)
5175
? response.map(sanitizeResponseItem)
5276
: sanitizeResponseItem(response)
5377

54-
return formCircDeps(result)
78+
return parseCircularDependencies(result)
5579
}

src/data/utils.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import User from '../users/user'
2+
3+
export function resolveModelClassFromString (className) {
4+
if(className === User.className){
5+
return User
6+
}
7+
8+
return function() {
9+
}
10+
}

test/e2e/specs/user-relations.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import '../helpers/global'
2+
import sandbox from '../helpers/sandbox'
3+
4+
const Backendless = sandbox.Backendless
5+
6+
describe('User - Relations', function() {
7+
let consoleApi
8+
let appId
9+
10+
sandbox.forTest()
11+
12+
const createRelationColumn = (tableName, columnName, toTableName, relationshipType) => {
13+
return consoleApi.tables.createColumn(appId, { name: tableName }, {
14+
dataType: 'DATA_REF',
15+
name : columnName,
16+
toTableName,
17+
relationshipType
18+
})
19+
}
20+
21+
beforeEach(function() {
22+
consoleApi = this.consoleApi
23+
appId = this.app.id
24+
})
25+
26+
it('Circular Users relations', function() {
27+
const usersStore = Backendless.Data.of(Backendless.User)
28+
29+
let user1 = { email: 'user1@com', password: '123456' }
30+
let user2 = { email: 'user2@com', password: '123456' }
31+
let user3 = { email: 'user3@com', password: '123456' }
32+
let user4 = { email: 'user4@com', password: '123456' }
33+
let user5 = { email: 'user5@com', password: '123456' }
34+
let user6 = { email: 'user6@com', password: '123456' }
35+
36+
return Promise.resolve()
37+
.then(() => {
38+
return Promise.resolve()
39+
.then(() => usersStore.save(user1))
40+
.then(() => usersStore.save(user2))
41+
.then(() => usersStore.save(user3))
42+
.then(() => usersStore.save(user4))
43+
.then(() => usersStore.save(user5))
44+
.then(() => usersStore.save(user6))
45+
.then(() => usersStore.find(Backendless.DataQueryBuilder.create().setSortBy('created')))
46+
})
47+
.then(users => {
48+
user1 = users[0]
49+
user2 = users[1]
50+
user3 = users[2]
51+
user4 = users[3]
52+
user5 = users[4]
53+
user6 = users[5]
54+
})
55+
.then(() => {
56+
return Promise.all([
57+
createRelationColumn('Users', 'friends', 'Users', 'ONE_TO_MANY'),
58+
createRelationColumn('Users', 'blocked', 'Users', 'ONE_TO_MANY'),
59+
])
60+
})
61+
.then(() => {
62+
return Promise.all([
63+
usersStore.addRelation(user1.objectId, 'friends', [user2, user3, user6]),
64+
usersStore.addRelation(user1.objectId, 'blocked', [user2, user3, user4, user5]),
65+
])
66+
})
67+
.then(() => {
68+
const query = Backendless.DataQueryBuilder
69+
.create()
70+
.setWhereClause(`objectId = '${user1.objectId}'`)
71+
.setRelated(['friends', 'blocked'])
72+
.setSortBy('created')
73+
74+
return Backendless.Data.of('Users').find(query)
75+
})
76+
.then(users => {
77+
expect(users[0].objectId).to.be.equal(user1.objectId)
78+
79+
expect(users[0].friends.length).to.be.equal(3)
80+
expect(users[0].blocked.length).to.be.equal(4)
81+
82+
const friendsUser2 = users[0].friends.find(o => o.objectId === user2.objectId)
83+
const friendsUser3 = users[0].friends.find(o => o.objectId === user3.objectId)
84+
const friendsUser6 = users[0].friends.find(o => o.objectId === user6.objectId)
85+
86+
const blockedUser2 = users[0].blocked.find(o => o.objectId === user2.objectId)
87+
const blockedUser3 = users[0].blocked.find(o => o.objectId === user3.objectId)
88+
const blockedUser4 = users[0].blocked.find(o => o.objectId === user4.objectId)
89+
const blockedUser5 = users[0].blocked.find(o => o.objectId === user5.objectId)
90+
91+
expect(friendsUser2.objectId).to.be.equal(user2.objectId)
92+
expect(friendsUser3.objectId).to.be.equal(user3.objectId)
93+
expect(friendsUser6.objectId).to.be.equal(user6.objectId)
94+
95+
expect(blockedUser2.objectId).to.be.equal(user2.objectId)
96+
expect(blockedUser3.objectId).to.be.equal(user3.objectId)
97+
expect(blockedUser4.objectId).to.be.equal(user4.objectId)
98+
expect(blockedUser5.objectId).to.be.equal(user5.objectId)
99+
100+
expect(friendsUser2).to.be.equal(blockedUser2)
101+
expect(friendsUser3).to.be.equal(blockedUser3)
102+
})
103+
})
104+
})

0 commit comments

Comments
 (0)