Head's Up! These forums are read-only. All users and content have migrated. Please join us at community.neo4j.com.
05-23-2020 09:17 PM
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")
}
05-23-2020 11:10 PM
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;
}
05-25-2020 07:08 PM
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")
}
05-25-2020 09:01 PM
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
"""
)
05-26-2020 11:30 AM
This feels good but its not complete, based on the Product->Option->Selection schema:
With this as my graph
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:
Todo:
My guess is that appending the arrays used earlier in the query is the way
06-13-2020 07:29 AM
EDIT: CORRECTED
Finally I have my creation method but its currently decoupled from the NewSelection mutation that I previously described in my schema.
After having a while to think about it i think its best if the variants are never created directly but get generated or persist, update and delete automatically all based on the behavior of the Selections, each selection change CASE at a time. Initially I thought it was going to be more like one big scary red generate/update/delete button.
MATCH (cartesian:Product)-->(o:Option)-->(s:Selection)
WITH o, COLLECT(s) 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 variants
UNWIND variants as selections
WITH variants, selections
WITH variants, REDUCE(s = HEAD(variants), n IN TAIL(variants) | s.name + '-' + n.name) AS name, selections
MERGE (v:Variant {name:name})
MERGE (s:Selection {id:selections.id})
MERGE (s)-[:OF]->(v)
RETURN s, v
Next steps:
All the sessions of the conference are now available online