Skip to content

Commit d6a4b79

Browse files
committed
fix(deserializer): Fixes various relations issues
Major refactor of model relationships code. Breaks out and tests various pieces of the process such as handling of hasMany, hasOne, belongsTo and hasManyThrough Fixes issue where relationship operations were not being waited for
1 parent a6ee62d commit d6a4b79

14 files changed

+562
-236
lines changed

lib/relationships.js

Lines changed: 28 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
var _ = require('lodash')
44
var utils = require('./utils')
5-
var debug = require('debug')('loopback-component-jsonapi')
5+
const linkRelatedModels = require(
6+
'./utilities/relationship-utils'
7+
).linkRelatedModels
68

79
module.exports = function (app, options) {
810
// get remote methods.
@@ -24,9 +26,7 @@ module.exports = function (app, options) {
2426
data = options.data
2527
model = utils.getModelFromContext(ctx, app)
2628

27-
relationships(id, data, model)
28-
29-
next()
29+
relationships(id, data, model).then(() => next()).catch(err => next(err))
3030
})
3131

3232
// for create
@@ -41,144 +41,40 @@ module.exports = function (app, options) {
4141
id = ctx.result.data.id
4242
data = options.data
4343
model = utils.getModelFromContext(ctx, app)
44-
relationships(id, data, model)
44+
return relationships(id, data, model)
45+
.then(() => next())
46+
.catch(err => next(err))
4547
}
4648

4749
next()
4850
})
4951
}
5052

51-
function relationships (id, data, model) {
52-
if (!data || !data.data || !id || !model || !data.data.relationships) {
53-
return
53+
function extractIdsFromResource (resource) {
54+
if (_.isArray(resource)) {
55+
return _.map(resource, 'id')
5456
}
55-
56-
_.each(data.data.relationships, function (relationship, name) {
57-
var serverRelation = model.relations[name]
58-
if (!serverRelation) return
59-
var type = serverRelation.type
60-
61-
// don't handle belongsTo in relationships function
62-
if (type === 'belongsTo') return
63-
64-
var modelTo = serverRelation.modelTo
65-
66-
var fkName = serverRelation.keyTo
67-
68-
if (type === 'belongsTo') {
69-
fkName = serverRelation.keyFrom
70-
modelTo = serverRelation.modelFrom
71-
}
72-
73-
if (!modelTo) {
74-
return false
75-
}
76-
77-
var setTo = {}
78-
setTo[fkName] = null
79-
var where = {}
80-
where[fkName] = id
81-
82-
// remove all relations to the model (eg .: post/1)
83-
if (type !== 'belongsTo') {
84-
modelTo.updateAll(where, setTo, function (err, info) {
85-
if (err) console.log(err)
86-
})
87-
}
88-
89-
var idToFind = null
90-
91-
if (_.isArray(relationship.data)) {
92-
// find all instance from the relation data eg
93-
// [{type: "comments", id: 1}, {type: "comments", id: 2}]
94-
_.each(relationship.data, function (item) {
95-
idToFind = item.id
96-
97-
if (type === 'belongsTo') {
98-
where[fkName] = item.id
99-
idToFind = id
100-
}
101-
102-
updateRelation(modelTo, idToFind, where)
103-
})
104-
105-
if (serverRelation.modelThrough) {
106-
var modelThrough = serverRelation.modelThrough
107-
var key = keyByModel(modelThrough, modelTo)
108-
var data = {}
109-
data[fkName] = id
110-
var stringIds = false
111-
112-
var payloadIds = _.map(relationship.data, function (item) {
113-
if (typeof item.id === 'string') {
114-
stringIds = true
115-
}
116-
return item.id
117-
})
118-
119-
modelThrough.find({ where: data, fields: key }, function (
120-
err,
121-
instances
122-
) {
123-
if (err) return
124-
var serverIds = _.map(instances, function (instance) {
125-
return stringIds ? instance[key].toString() : instance[key]
126-
})
127-
// to delete
128-
var toDelete = _.difference(serverIds, payloadIds)
129-
_.each(toDelete, function (id) {
130-
data[key] = id
131-
modelThrough.destroyAll(data)
132-
})
133-
// new
134-
var newAssocs = _.difference(payloadIds, serverIds)
135-
_.each(newAssocs, function (id) {
136-
data[key] = id
137-
modelThrough.create(data)
138-
})
139-
})
140-
}
141-
} else {
142-
if (relationship.data === null) {
143-
where[fkName] = null
144-
updateRelation(model, id, where)
145-
return
146-
}
147-
148-
idToFind = relationship.data.id
149-
150-
if (type === 'belongsTo') {
151-
idToFind = id
152-
where[fkName] = relationship.data.id
153-
}
154-
// relationship: {data: {type: "comments": id: 1}}
155-
updateRelation(modelTo, idToFind, where)
156-
}
157-
})
57+
return _.get(resource, 'id', null)
15858
}
15959

160-
// if the instance exist, then update it (create relationship),
161-
// according to JSON API spec we MUST NOT create new ones
162-
function updateRelation (model, id, data) {
163-
model.findById(id, function (err, instance) {
164-
if (err) console.log(err)
60+
function relationships (id, payload, ModelFrom) {
61+
if (!id || !ModelFrom) return
62+
const relationships = _.get(payload, 'data.relationships', {})
16563

166-
if (instance) {
167-
instance.updateAttributes(data)
168-
}
169-
})
170-
}
64+
return Promise.all(
65+
Object.keys(relationships).map(name => {
66+
const relationship = relationships[name]
67+
const relationDefn = ModelFrom.relations[name]
68+
if (!relationDefn) return
17169

172-
function keyByModel (assocModel, model) {
173-
var key = null
174-
_.each(assocModel.relations, function (relation) {
175-
if (relation.modelTo.modelName === model.modelName) {
176-
key = relation.keyFrom
177-
}
178-
})
70+
const type = relationDefn.type
71+
const ModelTo = relationDefn.modelTo
17972

180-
if (key === null) {
181-
debug('Can not find relation for ' + model.modelName)
182-
}
183-
return key
73+
// don't handle belongsTo in relationships function
74+
if (!ModelTo || type === 'belongsTo') return
75+
76+
const data = extractIdsFromResource(relationship.data)
77+
return linkRelatedModels(name, ModelFrom, id, ModelTo, data)
78+
})
79+
)
18480
}

lib/serialize.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,9 @@ module.exports = function (app, defaults) {
8383
var relation = model.relations[relationName]
8484
if (relationName && relation) {
8585
if (relation.polymorphic && utils.relationFkOnModelFrom(relation)) {
86-
var discriminator = utils.clone(ctx.instance)[relation.polymorphic.discriminator]
86+
var discriminator = utils.clone(ctx.instance)[
87+
relation.polymorphic.discriminator
88+
]
8789
relatedModel = app.models[discriminator]
8890
} else {
8991
relatedModel = relation.modelTo
@@ -102,7 +104,9 @@ module.exports = function (app, defaults) {
102104

103105
// If we're sideloading, we need to add the includes
104106
if (ctx.req.isSideloadingRelationships) {
105-
requestedIncludes = utils.setRequestedIncludes(ctx.req.remotingContext.args.filter.include)
107+
requestedIncludes = utils.setRequestedIncludes(
108+
ctx.req.remotingContext.args.filter.include
109+
)
106110
}
107111

108112
if (model.definition.settings.scope) {
@@ -119,7 +123,9 @@ module.exports = function (app, defaults) {
119123
if (typeof include === 'string') {
120124
requestedIncludes.push(include)
121125
} else if (_.isArray(include)) {
122-
requestedIncludes = requestedIncludes.concat(utils.setRequestedIncludes(include))
126+
requestedIncludes = requestedIncludes.concat(
127+
utils.setRequestedIncludes(include)
128+
)
123129
}
124130
}
125131

lib/serializer.js

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -404,13 +404,14 @@ function handleIncludes (resp, includes, relations, options) {
404404
})
405405
embeds = _.compact(embeds)
406406

407-
resource.relationships[include].data = resource.attributes[include]
408-
.map(function (relData) {
409-
return {
410-
id: String(relData[propertyKey]),
411-
type: plural
412-
}
413-
})
407+
resource.relationships[include].data = resource.attributes[
408+
include
409+
].map(function (relData) {
410+
return {
411+
id: String(relData[propertyKey]),
412+
type: plural
413+
}
414+
})
414415
} else {
415416
var rel = utils.clone(resource.attributes[include])
416417
var compoundIncludes = createCompoundIncludes(
@@ -471,14 +472,25 @@ function handleIncludes (resp, includes, relations, options) {
471472
* @param {Object} options
472473
* @return {Object}
473474
*/
474-
function createCompoundIncludes (relationship, key, fk, type, includedRelations, options) {
475+
function createCompoundIncludes (
476+
relationship,
477+
key,
478+
fk,
479+
type,
480+
includedRelations,
481+
options
482+
) {
475483
var compoundInclude = makeRelation(type, String(relationship[key]))
476484

477485
if (options && !_.isEmpty(includedRelations)) {
478486
var defaultModelPath = options.modelPath
479487
options.modelPath = type
480488

481-
compoundInclude.relationships = parseRelations(relationship, includedRelations, options)
489+
compoundInclude.relationships = parseRelations(
490+
relationship,
491+
includedRelations,
492+
options
493+
)
482494

483495
options.modelPath = defaultModelPath
484496
}

0 commit comments

Comments
 (0)