19

Using Mongoose version 3.6.4

Say I have a MongoDB document like so:

{
    "_id" : "5187b74e66ee9af96c39d3d6",
    "profile" : {
        "name" : {
            "first" : "Joe",
            "last" : "Pesci",
            "middle" : "Frank"
        }
    }
}

And I have the following schema for Users:

var UserSchema = new mongoose.Schema({
  _id:    { type: String },
  email:  { type: String, required: true, index: { unique: true }},
  active: { type: Boolean, required: true, 'default': false },
  profile: {
    name: {
      first:    { type: String, required: true },
      last:     { type: String, required: true },
      middle:   { type: String }
    }
  }
  created:    { type: Date, required: true, 'default': Date.now},
  updated:    { type: Date, required: true, 'default': Date.now}
);

And I submit a form passing a field named: profile[name][first] with a value of Joseph

and thus I want to update just the user's first name, but leave his last and middle alone, I thought I would just do:

User.update({email: "joe@foo.com"}, req.body, function(err, result){});

But when I do that, it "deletes" the profile.name.last and profile.name.middle properties and I end up with a doc that looks like:

{
    "_id" : "5187b74e66ee9af96c39d3d6",
    "profile" : {
        "name" : {
            "first" : "Joseph"
        }
    }
}

So it's basically overwriting all of profile with req.body.profile, which I guess makes sense. Is there any way around it without having to be more explicit by specifying my fields in the update query instead of req.body?

4

4 回答 4

20

You are correct, Mongoose converts updates to $set for you. But this doesn't solve your issue. Try it out in the mongodb shell and you'll see the same behavior.

Instead, to update a single deeply nested property you need to specify the full path to the deep property in the $set.

User.update({ email: 'joe@foo.com' }, { 'profile.name.first': 'Joseph' }, callback)
于 2013-05-08T16:47:05.567 回答
11

One very easy way to solve this with Moongose 4.1 and the flat package:

var flat = require('flat'),
    Schema = mongoose.Schema,
        schema = new Schema(
            {
                name: {
                    first: {
                        type: String,
                        trim: true
                    },
                    last: {
                        type: String,
                        trim: true
                    }
                }
            }
        );

    schema.pre('findOneAndUpdate', function () {
        this._update = flat(this._update);
    });


    mongoose.model('User', schema);

req.body (for example) can now be:

{
    name: {
        first: 'updatedFirstName'
    }
}

The object will be flattened before the actual query is executed, thus $set will update only the expected properties instead of the entire name object.

于 2015-07-28T17:48:21.283 回答
0

I think you are looking for $set

http://docs.mongodb.org/manual/reference/operator/set/

User.update({email: "joe@foo.com"}, { $set : req.body}, function(err, result){});

Try that

于 2013-05-06T14:45:59.110 回答
0

Maybe it's a good solution - add option to Model.update, that replace nested objects like:

{field1: 1, fields2: {a: 1, b:2 }} => {'field1': 1, 'field2.a': 1, 'field2.b': 2}

  nestedToDotNotation: function(obj, keyPrefix) {
    var result;
    if (keyPrefix == null) {
      keyPrefix = '';
    }
    result = {};
    _.each(obj, function(value, key) {
      var nestedObj, result_key;
      result_key = keyPrefix + key;
      if (!_.isArray(value) && _.isObject(value)) {
        result_key += '.';
        nestedObj = module.exports.nestedToDotNotation(value, result_key);
        return _.extend(result, nestedObj);
      } else {
        return result[result_key] = value;
      }
    });
    return result;
  }

});

need improvements circular reference handling, but this is really useful when working with nested objects

I'm using underscore.js here, but these functions easily can be replaced with other analogs

于 2014-03-20T10:58:28.007 回答