Created
March 22, 2020 13:55
-
-
Save Sleavely/8048e5b93e4cf522a258f8f89125ff12 to your computer and use it in GitHub Desktop.
Product attribute schema POC
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const attributes = { | |
// Attribute Keys are always localized | |
'color': { | |
name: { | |
'en-US': 'Color', | |
'sv-SE': 'Färg', | |
}, | |
}, | |
'size': { | |
name: { | |
'en-US': 'Size', | |
'sv-SE': 'Storlek', | |
}, | |
}, | |
'weight': { | |
name: { | |
'en-US': 'Weight', | |
'sv-SE': 'Vikt', | |
}, | |
}, | |
} | |
// Base model | |
const baseModel = { | |
id: '1234-abcd-5678-efgh', | |
attributes: [ | |
{ | |
name: 'size', | |
// Language-neutral value | |
value: 'M', | |
}, | |
{ | |
name: 'color', | |
// Localized value | |
value: { | |
'en-US': 'Green', | |
'sv-SE': 'Grön', | |
}, | |
}, | |
], | |
} | |
const variantModel = { | |
id: 'kjhf-3425-6jhi-42nj', | |
parentId: '1234-abcd-5678-efgh', | |
// A list of attribtues that override the parent | |
attributes: [ | |
{ | |
name: 'color', | |
value: { | |
'en-US': 'Blue', | |
'sv-SE': 'Blå', | |
}, | |
}, | |
], | |
} | |
/* everything below here happens on the client */ | |
// The client will proactively download a list of all attribute Keys for a given locale | |
const clientLocale = 'sv-SE' | |
const localizedAttributesAsDownloadedByTheClient = Object.entries(attributes).reduce((attrMap, [key, attr]) => { | |
attrMap[key] = attr.name[clientLocale] | |
return attrMap | |
}, {}) | |
// API cache? | |
const cachedProducts = [ | |
baseModel, | |
variantModel, | |
] | |
const getProduct = (id) => { | |
let product = cachedProducts.find(({ id: cachedId }) => cachedId === id) | |
if (!product) | |
{ | |
// call API and add product to cache | |
} | |
return cachedProducts.find(({ id: cachedId }) => cachedId === id) | |
} | |
/** | |
* Resolve an attribute for by traversing the parent tree | |
* | |
* @param {object} product | |
* @param {string} rawAttributeName | |
* @return {object} Attribute object with resolved localized values | |
*/ | |
const resolveAttribute = (product, rawAttributeName) => { | |
const rawAttribute = product.attributes.find(({ name }) => name === rawAttributeName) | |
if (!rawAttribute && product.parentId) | |
{ | |
return resolveAttribute(getProduct(product.parentId), rawAttributeName) | |
} | |
// If there is still no raw attribute after recursing parents, surrender | |
if (!rawAttribute) return { | |
name: localizedAttributesAsDownloadedByTheClient[rawAttributeName] || rawAttributeName, | |
value: undefined, | |
} | |
// Resolve localization if present. | |
const localizedAttribute = { | |
name: localizedAttributesAsDownloadedByTheClient[rawAttribute.name], | |
value: rawAttribute.value[clientLocale] || rawAttribute.value, | |
} | |
return localizedAttribute | |
} | |
/** | |
* Resolve the product chain to find out which attributes they have | |
* | |
* @param {object} product | |
* @return {string[]} List of attribute keys | |
*/ | |
const resolveProductAttributeKeys = (product) => { | |
const currentKeys = product.attributes.map(attr => attr.name) | |
if (product.parentId) { | |
const parentAttributeKeys = resolveProductAttributeKeys(getProduct(product.parentId)) | |
return Array.from(new Set(currentKeys.concat(parentAttributeKeys))) | |
} | |
return currentKeys | |
} | |
/** | |
* Find all attribute values for a given product | |
* | |
* @param {object} product | |
* @return {object[]} Attribute objects with resolved localized values | |
*/ | |
const resolveProductAttributes = (product) => { | |
const availableAttributeKeys = resolveProductAttributeKeys(product) | |
return availableAttributeKeys.map(attrName => resolveAttribute(product, attrName)) | |
} | |
Promise.all([ | |
(() => { | |
// Assert that the variants attribute takes precedence and respects the client locale | |
const { name: attributeName, value: resolvedValue } = resolveAttribute(variantModel, 'color') | |
console.log(attributeName, resolvedValue) | |
console.log('Expected?', resolvedValue === 'Blå') | |
})(), | |
(() => { | |
// When variant is a missing value, it refer to the parent which has a language-neutral value, "M" | |
const { name: attributeName, value: resolvedValue } = resolveAttribute(variantModel, 'size') | |
console.log(attributeName, resolvedValue) | |
console.log('Expected?', resolvedValue === 'M') | |
})(), | |
(() => { | |
// When neither has the attribute, return undefined | |
const { name: attributeName, value: resolvedValue } = resolveAttribute(variantModel, 'bankaccount') || {} | |
console.log(attributeName, resolvedValue) | |
console.log('Expected?', resolvedValue === undefined) | |
})(), | |
(() => { | |
// Ability to resolve all attributes from a product and its parents | |
const allAttrs = resolveProductAttributes(variantModel) | |
console.log('All attributes:\n', allAttrs) | |
})(), | |
]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment