Visa Kopu
Visa Kopu

Reputation: 723

How to remove document and all embedded documents in MongoDB?

I have the following schema defined in Mongoose:

var postSchema = mongoose.Schema({
  title: String,
  body: String,
  created: Date,
  photos: Array
});

var Post = mongoose.model('Post', postSchema);

var photoSchema = mongoose.Schema({
  filename: String,
  mimetype: String,
  data: Buffer,
  created: Date
});

var Photo = mongoose.model('Photo', photoSchema);

If I remove a Post, I'd like all the related Photos to be removed as well (like cascading delete in SQL).

If I do Post.remove({ _id: MY_POST_ID }), only the Post gets removed and I have orphan Photos left in the database.

Also, should I somehow define the post ID in the Photo schema?

Upvotes: 0

Views: 1269

Answers (2)

Neil Lunn
Neil Lunn

Reputation: 151072

The only way you get a truly atomic operation is to actually use an embedded model like so:

var photoSchema = mongoose.Schema({
  filename: String,
  mimetype: String,
  data: Buffer,
  created: Date
});

var postSchema = mongoose.Schema({
  title: String,
  body: String,
  created: Date,
  photos: [photoSchema]
});

var Post = mongoose.model('Post', postSchema);

Then you can actually remove everything with one simple statement since it is all in the same collection and indeed the same document:

Post.remove({ "_id": postId },function(err) {
   // handling in here
});

With your current schema you would need to remove all documents individually:

var async = require("async");

var photoSchema = mongoose.Schema({
  filename: String,
  mimetype: String,
  data: Buffer,
  created: Date
});

var Photo = mongoose.model('Photo', photoSchema);

var postSchema = mongoose.Schema({
  title: String,
  body: String,
  created: Date,
  photos: [{ "type": Schema.types.ObjectId, "ref": "Photo" }]
});

var Post = mongoose.model('Post', postSchema);

// later

async.waterfall(
    [
        function(callback) {
            Post.findById(postId,callback);
        },

        function(post,callback) {
            Photo.remove({ "_id": { "$in": post.photos } },function(err) {
               if (err) callback(err);
               callback();
            });
        },

        function(callback) {
            Post.remove(photoId,callback);
        }
    ],
    function(err) {
       if (err); // do something
       // Job done
    }
)

If you want to avoid reading the document first then you

var photoSchema = mongoose.Schema({
  postId: Schema.types.ObjectId,
  filename: String,
  mimetype: String,
  data: Buffer,
  created: Date
});

Then to remove all "photos" related to a "post" then you issue:

Photo.remove({ "postId": postId },function(err) {
   // removed or err
});

Generally speaking, if you always want this behavior and your "post" document cannot grow beyond 16MB with all the embedded "photo" information then the embedding option makes the most sense since you then don't require that a "photo" is actually used anywhere else than as a child of a single parent.

Upvotes: 1

vladzam
vladzam

Reputation: 5908

If added photos only belong to one Post, you can embed all the photos in the Post schema. You can do so by pushing photo objects to the photos array in the Post schema. Then, your Post documents would have the following structure:

{
    title: 'My-first-post',
    body: 'Lorem ipsum',
    created: '01-01-1900',
    photos: [
        { filename: 'file1', mimetype: 'type', data: 238947289347239874, created: '01-02-1900' },
        { filename: 'file2', mimetype: 'type', data: 238947284321225671, created: '02-02-1900' }
    ]
}

Upvotes: 0

Related Questions