Skip to content

Commit c3a8da1

Browse files
committed
fix(links): Fix issue with link resolution
Bad use of memoization which prevented it from being used correctly, leading to issues with larger data structures
1 parent 65958be commit c3a8da1

File tree

2 files changed

+96
-75
lines changed

2 files changed

+96
-75
lines changed

lib/mixins/link-getters.js

Lines changed: 77 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -9,89 +9,92 @@ import {partial, memoize} from 'lodash/function'
99
* @param {Object} includes - Object with lists of Entry, Asset, DeletedEntry and DeletedAsset
1010
*/
1111
export default function mixinLinkGetters (items, includes) {
12-
each(items, item => setLocalizedFieldGetters(item.fields, includes, !!item.sys.locale))
13-
}
14-
15-
/**
16-
* If a field does not have a locale defined in sys, the content of that field
17-
* is an object where the keys are each available locale, and we need to iterate
18-
* over each of those
19-
* @private
20-
* @param {Object} fields - Fields object
21-
* @param {Object} includes - Object with lists of Entry, Asset, DeletedEntry and DeletedAsset
22-
* @param {boolean} hasLocale - If entry has been requested with a locale
23-
*/
24-
function setLocalizedFieldGetters (fields, includes, hasLocale) {
25-
if (hasLocale) {
26-
setFieldGettersForFields(fields, includes)
27-
} else {
28-
each(fields, localizedField => setFieldGettersForFields(localizedField, includes))
29-
}
30-
}
12+
const linkGetter = memoize(getLinksFromIncludes, memoizationResolver)
13+
each(items, item => setLocalizedFieldGetters(item.fields, !!item.sys.locale))
3114

32-
/**
33-
* Sets getters on each link field or list of link fields for each item
34-
* @private
35-
* @param {Object} fields - Fields object
36-
* @param {Object} includes - Object with lists of Entry, Asset, DeletedEntry and DeletedAsset
37-
*/
38-
function setFieldGettersForFields (fields, includes) {
39-
each(fields, (field, fieldKey) => {
40-
if (Array.isArray(field)) {
41-
addGetterForLinkArray(field, fieldKey, fields, includes)
15+
/**
16+
* If a field does not have a locale defined in sys, the content of that field
17+
* is an object where the keys are each available locale, and we need to iterate
18+
* over each of those
19+
* @private
20+
* @param {Object} fields - Fields object
21+
* @param {boolean} hasLocale - If entry has been requested with a locale
22+
*/
23+
function setLocalizedFieldGetters (fields, hasLocale) {
24+
if (hasLocale) {
25+
setFieldGettersForFields(fields)
4226
} else {
43-
addGetterForLink(field, fieldKey, fields, includes)
27+
each(fields, localizedField => setFieldGettersForFields(localizedField))
4428
}
45-
})
46-
}
29+
}
4730

48-
/**
49-
* Sets a getter which resolves the link of the given fieldKey in fields
50-
* @private
51-
* @param {Object} field - Field object
52-
* @param {string} fieldKey
53-
* @param {Object} fields - Fields object
54-
* @param {Object} includes - Object with lists of Entry, Asset, DeletedEntry and DeletedAsset
55-
*/
56-
function addGetterForLink (field, fieldKey, fields, includes) {
57-
if (get(field, 'sys.type') === 'Link') {
58-
Object.defineProperty(fields, fieldKey, {
59-
get: memoize(partial(linkGetter, includes, field))
31+
/**
32+
* Sets getters on each link field or list of link fields for each item
33+
* @private
34+
* @param {Object} fields - Fields object
35+
*/
36+
function setFieldGettersForFields (fields) {
37+
each(fields, (field, fieldKey) => {
38+
if (Array.isArray(field)) {
39+
addGetterForLinkArray(field, fieldKey, fields)
40+
} else {
41+
addGetterForLink(field, fieldKey, fields)
42+
}
6043
})
6144
}
62-
}
6345

64-
/**
65-
* Sets a getter which resolves the array of links of the given fieldKey in fields
66-
* @private
67-
* @param {Array<Object>} field - List field array
68-
* @param {string} fieldKey
69-
* @param {Object} fields - Fields object
70-
* @param {Object} includes - Object with lists of Entry, Asset, DeletedEntry and DeletedAsset
71-
*/
72-
function addGetterForLinkArray (listField, fieldKey, fields, includes) {
73-
if (get(listField[0], 'sys.type') === 'Link') {
74-
Object.defineProperty(fields, fieldKey, {
75-
get: memoize(function () {
76-
return map(listField, partial(linkGetter, includes))
46+
/**
47+
* Sets a getter which resolves the link of the given fieldKey in fields
48+
* @private
49+
* @param {Object} field - Field object
50+
* @param {string} fieldKey
51+
* @param {Object} fields - Fields object
52+
*/
53+
function addGetterForLink (field, fieldKey, fields) {
54+
if (get(field, 'sys.type') === 'Link') {
55+
Object.defineProperty(fields, fieldKey, {
56+
get: partial(linkGetter, field)
7757
})
78-
})
58+
}
7959
}
80-
}
8160

82-
/**
83-
* Looks for given link field in includes.
84-
* If linked entity is not found, it returns the original link.
85-
* @private
86-
* @param {Object} field - Field object
87-
* @param {Object} includes - Object with lists of Entry, Asset, DeletedEntry and DeletedAsset
88-
* @return {Object} Field, or link if field not resolved
89-
*/
90-
function linkGetter (includes, field) {
91-
var link = find(includes[field.sys.linkType], ['sys.id', field.sys.id])
92-
if (link && link.fields) {
93-
setLocalizedFieldGetters(link.fields, includes, !!link.sys.locale)
94-
return link
61+
/**
62+
* Sets a getter which resolves the array of links of the given fieldKey in fields
63+
* @private
64+
* @param {Array<Object>} field - List field array
65+
* @param {string} fieldKey
66+
* @param {Object} fields - Fields object
67+
*/
68+
function addGetterForLinkArray (listField, fieldKey, fields) {
69+
if (get(listField[0], 'sys.type') === 'Link') {
70+
Object.defineProperty(fields, fieldKey, {
71+
get: function () {
72+
return map(listField, partial(linkGetter))
73+
}
74+
})
75+
}
76+
}
77+
78+
/**
79+
* Looks for given link field in includes.
80+
* If linked entity is not found, it returns the original link.
81+
* This method shouldn't be used directly, and it's memoized whenever this
82+
* module's main method is used.
83+
* This is done to prevent the same link being resolved multiple times.
84+
* @private
85+
* @param {Object} field - Field object
86+
* @return {Object} Field, or link if field not resolved
87+
*/
88+
function getLinksFromIncludes (field) {
89+
var link = find(includes[field.sys.linkType], ['sys.id', field.sys.id])
90+
if (link && link.fields) {
91+
setLocalizedFieldGetters(link.fields, !!link.sys.locale)
92+
return link
93+
}
94+
return field
95+
}
96+
97+
function memoizationResolver (link) {
98+
return link.sys.id
9599
}
96-
return field
97100
}

test/unit/entities/entry-test.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ test('Entry collection is wrapped', t => {
3737
t.end()
3838
})
3939

40-
test('Entry collection links are resolved', t => {
40+
test.only('Entry collection links are resolved', t => {
4141
const entryCollection = {
4242
total: 1,
4343
skip: 0,
@@ -47,6 +47,7 @@ test('Entry collection links are resolved', t => {
4747
cloneDeep(entryMock)
4848
],
4949
includes: {
50+
Entry: [ cloneDeep(entryMock) ],
5051
Asset: [ cloneDeep(assetMock) ]
5152
}
5253
}
@@ -70,6 +71,21 @@ test('Entry collection links are resolved', t => {
7071
entryCollection.includes.Asset[0].sys.id = 'asset1'
7172
// setup second linked entry
7273
entryCollection.items[1].sys.id = 'entry3'
74+
entryCollection.items[1].fields.linked1 = {
75+
sys: {
76+
id: 'entry4',
77+
type: 'Link',
78+
linkType: 'Entry'
79+
}
80+
}
81+
entryCollection.includes.Entry[0].sys.id = 'entry4'
82+
entryCollection.includes.Entry[0].fields.linked1 = {
83+
sys: {
84+
id: 'entry3',
85+
type: 'Link',
86+
linkType: 'Entry'
87+
}
88+
}
7389

7490
const wrappedEntry = wrapEntryCollection(entryCollection, true).toPlainObject()
7591
// first linked entry resolved from includes
@@ -78,5 +94,7 @@ test('Entry collection links are resolved', t => {
7894
// second linked entry resolved from items list
7995
t.equals(wrappedEntry.items[0].fields.linked2.sys.type, 'Entry', 'second linked entity is resolved')
8096
t.ok(wrappedEntry.items[0].fields.linked2.fields, 'second linked entity has fields')
97+
t.equals(wrappedEntry.items[1].fields.linked1.sys.type, 'Entry', 'third linked entity is resolved')
98+
t.ok(wrappedEntry.items[1].fields.linked1.fields, 'third linked entity has fields')
8199
t.end()
82100
})

0 commit comments

Comments
 (0)