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 })
    }
  }
})
