Search code examples
mongodbmeteorcursor

Meteor: reactive local collection (client-only) correct observeChanges


I have a collection which contains several arrays, i.e.

memos: [...]
todos: [...]

I insert each document from an array into a local collection on the client:

if (Meteor.isClient) {

  this.Memos = new Mongo.Collection(null);

  var results;

  results = Projects.findOne({
    _id: currentProject_id
  });

  results.memos.forEach(function(memo) {
    return Memos.insert(memo);
  });
}

If I call a method to change a memo in the server collection:

({
  completeMemo: function(project_id, memo_id, ifCompleted) {
    return Projects.update({
      _id: project_id,
      'memo._id': memo_id
    }, {
      $set: {
        'memos.$.completed': ifCompleted
      }
    });
  }
});

Meteor tries to re-insert the changed document into the local collection, throwing this error:

Exception from Tracker recompute function:
MinimongoError: Duplicate _id '43ttergerg33t3t'

Instead of re-inserting the document, I just want the changes to be reflected.

I've tried modifying return Memos.insert(memo) to:

Memos.upsert({
  _id: memo._id
}, {
  $set: {
    direction: memo.direction,
    sender: memo.sender,
    sender_id: memo.sender_id,
    sentAt: memo.sentAt,
    text: memo.text,
    type: memo.type,
    viewed: memo.viewed
  }
});

However this throws the same error. UPDATE the above works perfectly, the error was from something else. See my answer below.

How can I observe changes on these documents and just update the fields that have changed?

I think I need something like the following:

Memos = new Mongo.Collection;

query = Projects.findOne({
  _id: currentProject_id
});

handle = query.memos.observeChanges({
  added: function(id, doc) {
    Memos.insert(doc);
  },

  changed: function(id, doc) {
    ..........???;
  },

  removed: function(id) {
    Memos.remove({
      _id: id
    });
  }
});

Solution

  • Solution:

    1. I changed insert to upsert to prevent meteor from trying to insert a new document with the same _id
    2. Meteor doesn't allow entire documents to be replaced on the client, so I specified the fields to be updated:

    results.memos.forEach(function(memo) {
      return Memos.upsert({
        _id: memo._id
      }, {
        $set: {
          direction: memo.direction,
          sender: memo.sender,
          sender_id: memo.sender_id,
          sentAt: memo.sentAt,
          text: memo.text,
          type: memo.type,
          viewed: memo.viewed
        }
      });
    });