Skip to content

Instantly share code, notes, and snippets.

@Sleavely
Created March 22, 2020 13:55
Show Gist options
  • Save Sleavely/8048e5b93e4cf522a258f8f89125ff12 to your computer and use it in GitHub Desktop.
Save Sleavely/8048e5b93e4cf522a258f8f89125ff12 to your computer and use it in GitHub Desktop.
Product attribute schema POC
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