Hi guys,
I am desperate and could need some guidance on how to keep the relations of “products and categories” in sync with each other.
There are 2 collections: products and categories, related to each other:
- 1 product can be assigned to n categories
- 1 category can contain n products
Right now I am trying to synchronize both collections via the “collection-hooks”-package and SimpleSchema.autoValue()
, but the below code does NOT work: it ends up in a loop, where 2 hooks keep on triggering each other.
In the end I’d like those commands to end up syncing both collections:
// Example commands
// inserts / updates in "Products"-Collection
// should lead to category.productIds synced within "Category"-Collection
Products.Collection.insert({
name: 'Product Name 1',
categoryIds: [
category1._id,
],
})
Products.Collection.update(knownProduct._id, { $set: {
categoryIds: [
category1._id,
category2._id,
],
} })
// on the other hand,
// inserts / updates in "Category"-Collection
// should lead to products.categoryIds synced within "Products"-Collection
Categories.Collection.insert({
name: 'Category Name 1',
productIds: [
product1._id,
],
})
Categories.Collection.update(knownCategory._id, {
$set: productIds: [
product1._id,
product2._id,
],
})
How would you solve this problem in meteor?
This is the buggy code (SimpleSchema & CollectionHooks based)
Products.Schema = new SimpleSchema({
name: {
type: String,
optional: false,
label: 'name',
},
// CATEGORIES (1 product can be in n categories)
// we use ``categoryIds`` to assign via autoform
// and denormalize instances in ``categories.instances``
categoryIds: {
type: [String],
optional: false,
label: 'categories',
autoform: {
type: 'select-checkbox',
options: function () {
return Categories.Collection.find().map(function (c) {
return { label: c.toString(), value: c._id }
});
},
},
},
// denormalization of instances
categories: {
autoform: {
omit: true,
},
type: Object,
optional: true,
blackbox: true,
autoValue: function () {
const values = this.field('categoryIds').value
if (values) {
const returnValue = []
for (const value of values) {
const cat = Categories.Collection.findOne({ _id: value })
returnValue.push(cat)
}
return { instances: returnValue }
}
},
},
})
// the loop-PROBLEM lies here
Products.Collection.after.update(function(userId, doc, fieldNames, modifier, options) {
if (Meteor.isServer) {
Categories.Collection.find({ categoryIds: doc._id }}).forEach((doc) => {
Categories.Schema.clean(doc, {filter: false})
// write the NEW cleaned doc back to the db
// NOTE: make it a blackbox write: SKIP Collection2 and SimpleSchema totally
// to prevent autoValues from running again!
Categories.Collection.update({_id: doc._id}, { $set: doc }, {bypassCollection2: true, validate: false, filter: false, autoConvert: false, removeEmptyStrings:false, getAutoValues: false })
}
}
})
Categories.Schema = new SimpleSchema({
name: {
type: String,
optional: false,
label: 'name',
},
// PRODUCTS (1 category can have n products)
// we use ``productIds`` to assign via autoform
// and denormalize instances in ``products.instances``
productIds: {
type: [String],
optional: true,
label: 'products',
autoform: {
type: 'select-checkbox',
options: function () {
return Products.Collection.find().map(function (c) {
return { label: c.toString(), value: c._id }
});
},
},
},
// denormalization of instances
products: {
autoform: {
omit: true,
},
type: Object,
optional: true,
blackbox: true,
autoValue: function () {
const values = this.field('productIds').value
if (values) {
const returnValue = []
for (const value of values) {
const cat = Products.Collection.findOne({ _id: value })
returnValue.push(cat)
}
return { instances: returnValue }
}
},
},
})
// the loop-PROBLEM lies here
Categories.Collection.after.update(function(userId, doc, fieldNames, modifier, options) {
if (Meteor.isServer) {
Products.Collection.find({ categoryIds: doc._id }}).forEach((doc) => {
Products.Schema.clean(doc, {filter: false})
// write the NEW cleaned doc back to the db
// NOTE: make it a blackbox write: SKIP Collection2 and SimpleSchema totally
// to prevent autoValues from running again!
Products.Collection.update({_id: doc._id}, { $set: doc }, {bypassCollection2: true, validate: false, filter: false, autoConvert: false, removeEmptyStrings:false, getAutoValues: false })
}
}
})