Hiero
Hiero

Reputation: 2205

MongoDB updated object with item remove not saving

I'm using Angular Fullstack for an web app.

I'm posting my data by $http.post() my object:

{ title: "Some title", tags: ["tag1", "tag2", "tag3"] }

When I edit my object and try to $http.put() for example:

{ title: "Some title", tags: ["tag1"] }

In console I get HTTP PUT 200 but when I refresh the page I still recive the object with all 3 tags.

This is how I save in the MongoDB:

exports.update = function(req, res) {
  if (req.body._id) {
    delete req.body._id;
  }

  Question.findByIdAsync(req.params.id)
    .then(handleEntityNotFound(res))
    .then(saveUpdates(req.body))
    .then(responseWithResult(res))
    .catch(handleError(res));
};


function saveUpdates(updates) {

  return function(entity) {

    var data = _.merge(entity.toJSON(), updates);
    var updated = _.extend(entity, data);

    return updated.saveAsync()
      .spread(function(updated) {
        return updated;
      });
  };
}

Can someone explain how to save the object with removed items?

What I'm doing wrong?

Upvotes: 1

Views: 457

Answers (1)

Blakes Seven
Blakes Seven

Reputation: 50416

This is pretty bad practice to use things like _.merge or _.extend in client ( meaning your nodejs client to database and not browser ) code after retrieving from the database. Also notably _.merge is the problem here as it is not going to "take away" things, but rather "augment" what is already there with the information you have provided. Not what you want here, but there is also a better way.

You should simply using "atomic operators" like $set to do this instead:

Question.findByIdAndUpdateAsync(
    req.params.id,
    { "$set": { "tags": req.body.tags } },
    { "new": true }
)
.then(function(result) {
    // deal with returned result
});

You also really should be targeting your endpoints and not having a "generic" object write. So the obove would be specically targeted at "PUT" for related "tags" only and not touch other fields in the object.

If you really must throw a whole object at it and expect an update from all the content, then use a helper to fix the update statement correctly:

function dotNotate(obj,target,prefix) {
  target = target || {},
  prefix = prefix || "";

  Object.keys(obj).forEach(function(key) {
    if ( typeof(obj[key]) === "object" ) {
      dotNotate(obj[key],target,prefix + key + ".");
    } else {
      return target[prefix + key] = obj[key];
    }
  });

  return target;
}

var update = { "$set": dotNotate(req.body) };

Question.findByIdAndUpdateAsync(
    req.params.id,
    update,
    { "new": true }
)
.then(function(result) {
    // deal with returned result
});

Which will correctly structure not matter what the object you throw at it.

Though in this case then probably just directly is good enough:

Question.findByIdAndUpdateAsync(
    req.params.id,
    { "$set": req.body },
    { "new": true }
)
.then(function(result) {
    // deal with returned result
});

There are other approaches with atomic operators that you could also fit into your logic for handling. But it is best considered that you do these per element, being at least root document properties and things like arrays treated separately as a child.

All the atomic operations interact with the document "in the database" and "as is at modification". Pulling data from the database, modifiying it, then saving back offers no such guarnatees that the data has not already been changed and that you just may be overwriting other changes already comitted.

I truth your "browser client" should have been aware that the "tags" array had the other two entries and then your "modify request" should simply be to $pull the entries to be removed from the array, like so:

Question.findByIdAndUpdateAsync(
    req.params.id,
    { "$pull": { "tags": { "$in": ["tag2", "tag3"] } } },
    { "new": true }
)
.then(function(result) {
    // deal with returned result
});

And then, "regardless" of the current state of the document on the server when modified, those changes would be the only ones made. So if something else modified at added "tag4", and the client had yet to get the noficiation of such a change before the modification was sent, then the return response would include that as well and everything would be in sync.

Learn the update modifiers of MongoDB, as they will serve you well.

Upvotes: 1

Related Questions