Ecommerce Schema design

So I have an ecommerce use case. I'm trying to figure out how to abstract the steps of generating the variants of each product.

I need at least one type to signify the option with category and selection fields.

Then I think these [Options] can be aggregated using collect(Option.category) and Option.selection could be combined into a larger than average cartesian product which represents all of the Products-Models->Variants

Are there are any strategies or tools for generating variants like these when using cypher/apoc? Are there are examples?

My current approach schema example:

type Product {
  id: ID!
  variants: [Variant] @relation(name: "MODEL", direction: "OUT")
  name: String!
  handle: String!
  description: String
  options: [Option] @relation(name: "KIND", direction: "OUT")
}

type Variant {
  id: ID!
  name: String
  options: [String!]
  production: Int
  price: Int
  sku: String
  stock: Int
  sold: Int
  images: [Image] @relation(name: "VARIANTIMAGE", direction: "OUT")
  product: Product @relation(name: "MODEL", direction: "IN")
}

type Option {
  id: ID!
  category: String           //ex. 'Color' //ex. 'Material'
  selection: String          //ex. 'Red'   //ex. 'Denim'
  image: Image @relation(name: "OPTIONIMAGE", direction: "OUT")
  product: Product @relation(name: "KIND", direction: "IN")
}

Getting warmer?

UNWIND {first} as first
UNWIND {second} as second
RETURN first, second

Thanks cypher this is much more elegant than the javascript equivalent:

const options = [[first],[second]]
const Combos = (options) => {
  var results = [[]];
  for (var i = 0; i < options.length; i++) {
    var currentSubArray = options[i]; 
    var temp = [];
    for (var j = 0; j < results.length; j++) { 
       for (var k = 0; k < currentSubArray.length; k++) { 
         temp.push(results[j].concat(currentSubArray[k])); 
       }
    } 
    results = temp;
  }
  return results;
}

Just going to leave my trial and error notes here for the next student who comes here looking to do the same.

{"
Product Options Query: 
Returns Rows of 2 columns
1. a Product's Options by Category beside
2. that Category's Selections in an array
"} 

MATCH (product:Product {id: ${params.id})-[:KIND]->(o:Option)
WITH (o.category) as category, collect(o.selection) as selection
return category, selection

{"I don't really wanna work through 
more complex queries and mutations. 
I'm finding it preferable to organize 
the options in two types:"}

type Option {
  id: ID!
  name: String
  product: Product! @relation(name: "KIND", direction: "IN")
  selection: Selection @relation(name: "SELECT", direction: "OUT")
}

type Selection {
  id: ID!
  name: String
  image: Image @relation(name: "SELECTIONIMAGE", direction: "OUT")
  option: Option! @relation(name: "SELECT", direction: "IN")
}

Considering the shopify/wix/webflow product creation CMS flow it makes sense that you'd create the Option Category before defining the Option's actual choices.

Type Mutation {
  NewOption(id: String!, name: String!): Option
    @cypher(
      statement: """
      MATCH (p:Product {id: $id})
      CREATE (o:Option {
        id: apoc.create.uuid(),
        name: $name })
      CREATE (p)-[k:KIND]->(o)
      RETURN o
      """
    )
  NewSelection(id: String!, name: String!): Selection
    @cypher(
      statement: """
      MATCH (o:Option {id: $id})
      CREATE (s:Selection {
        id: apoc.create.uuid(),
        name: $name})
      CREATE (o)-[k:SELECT]->(s)
      RETURN s
      """
    )

This feels good but its not complete, based on the Product->Option->Selection schema:

With this as my graph

image

And this cypher query
Adapted from https://staging.thepavilion.io/t/cartesian-product-from-array/2312
Thank you andrew.bowman

MATCH (p:Product)-->(o:Option)-->(s:Selection)
WITH o, COLLECT([o.name, s.name]) AS s
With COLLECT(s) as input 
UNWIND range(0, size(input)-1) as bucket
UNWIND range(0, size(input[bucket])-1) as subIndex
WITH input, collect([bucket, subIndex]) as coords
WITH input, apoc.coll.combinations(coords, size(input)) as combos
UNWIND combos as combo
WITH input, combo, size(apoc.coll.toSet([coord in combo | coord[0]])) as bucketsUsed
WHERE bucketsUsed = size(input)
WITH [coord in combo | input[coord[0]][coord[1]]] as combo
RETURN apoc.map.fromPairs(combo)

I can return this:

image

Todo:

  1. I need to add a property to each selection to offset the price derived for each of the returned variant objects
  2. I need to add additional fields including a concatenated name, a generated sku, etc.

My guess is that appending the arrays used earlier in the query is the way

from: https://stackoverflow.com/questions/20797561/neo4j-how-to-compare-two-list-and-return-the-different-items submitted by : jjaderberg

"Delete/Create variants after comparison with filter example"
WITH [1,2,3,4,5,6] as combinations, [1,2,3] as existingvariants
RETURN FILTER( n IN combinations WHERE NOT n IN existingvariants ) as createnewvariants

"result:"
createnewvariants
4, 5, 6