2

I'm trying to define a folder schema in Mongoose. For each folder, I'd like to store a reference to the parent folder, as well as an array of child subfolders (to make traversal in either direction easy):

var folderSchema = new mongoose.Schema({
    name: String,
    parent: { type: Schema.Types.ObjectId, ref: 'Folder' },
    subfolders: [{ type: Schema.Types.ObjectId, ref: 'Folder' }],
});

When creating a folder, I want to be able to only specify the parent folder, and then have a pre-save hook take care of adding the subfolder to the parent folder's subfolders array. This part works fine:

folderSchema.pre('save', function (next) {
    var self = this;

    // If creating a subfolder, then add the subfolder to
    // the parent folder's "subfolders" array

    if (this.parent) {
        Folder.findById(this.parent, function (err, parent) {
            if (err) return next(err);
            if (parent) {
                parent.subfolders.push(self);
                return parent.save(next);
            }
            return next();
        });
    } else {
        next();
    }
});

Also, when deleting a subfolder, I would like to use a pre-remove hook to automatically remove the subfolder from the parent folder's subfolders array. Unfortunately, I can't get this part to work:

folderSchema.pre('remove', function (next) {
    var self = this;

    // If deleting a subfolder, then remove it from the parent
    // folder's "subfolders" array
    //
    // (Note: it would also be a good idea to recursively remove
    // all subfolders here, but I'm not attempting this yet.)

    if (this.parent) {
        Folder.findById(this.parent, function (err, parent) {
            if (err) return next(err);
            if (parent) {
                parent.subfolders.id(self._id).remove();    // TypeError: Object 53a64349741d1ae82274c9a2 has no method 'id'
                return parent.save(next);
            }
            return next();
        });
    } else {
        next();
    }
});

The line that's erroring out is this one:

parent.subfolders.id(self._id).remove();

It results in this error:

TypeError: Object 53a64349741d1ae82274c9a2 has no method 'id'
    at Promise.<anonymous> (C:\Users\serg\temp\mongoose\folders\index.js:53:35)
    at Promise.<anonymous> (C:\Users\serg\temp\mongoose\folders\node_modules\mongoose\node_modules\mpromise\lib\promise.js:177:8)
    at Promise.EventEmitter.emit (events.js:95:17)
    at Promise.emit (C:\Users\serg\temp\mongoose\folders\node_modules\mongoose\node_modules\mpromise\lib\promise.js:84:38)
    at Promise.fulfill (C:\Users\serg\temp\mongoose\folders\node_modules\mongoose\node_modules\mpromise\lib\promise.js:97:20)
    at C:\Users\serg\temp\mongoose\folders\node_modules\mongoose\lib\query.js:1393:13
    at model.Document.init (C:\Users\serg\temp\mongoose\folders\node_modules\mongoose\lib\document.js:250:11)
    at completeOne (C:\Users\serg\temp\mongoose\folders\node_modules\mongoose\lib\query.js:1391:10)
    at Object.cb (C:\Users\serg\temp\mongoose\folders\node_modules\mongoose\lib\query.js:1150:11)
    at Object._onImmediate (C:\Users\serg\temp\mongoose\folders\node_modules\mongoose\node_modules\mquery\lib\utils.js:137:16)

What I was trying to do is use the MongooseDocumentArray#id method to remove the sub document, as shown in the example on the Sub Docs page of Mongoose's documentation. However, it looks like that method is not available, and I'm not sure why that is. Did I define the schema incorrectly? Is the id method not available from within middleware? Or am I simply not using it correctly?

1 Answer 1

8

The difference here is that you're using an embedded reference to the object in your array, rather than a direct sub-document. In the mongoose example of sub-document, it shows:

var childSchema = new Schema({ name: 'string' });

var parentSchema = new Schema({
  children: [childSchema]
});

So in this example, each "child" is actually stored directly within the "parent" (so the child lives within the parent collection). In your scenario, you have a reference to your subfolders ID rather than the direct storing of the object:

subfolders: [{ type: Schema.Types.ObjectId, ref: 'Folder' }],

So when you perform your query, the result in subfolders is actually an array of IDs, one of which is 53a64349741d1ae82274c9a2. This has no method id which is causing your problem. However, you could use populate to populate the objects for those IDs. But even doing that wouldn't give you the DocumentArray function you need, since mongoose treats these as simply an Array, rather than a DocumentArray (which would have the id function you are looking for). You can see the logic which it uses to determine how to cast these objects here. In the end, you'll see that when you load these objects mongoose casts them as an Array type:

return new Types.Array(path, cast || Types.Mixed, obj);

So if you want this id function you can change your schema, or simply do what it does in place. The logic isn't that much, so you might consider just searching the array for the one that matches that ID manually.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for that wonderfully clear explanation - I understand now why it wasn't working before, and it led me to what I think is a good solution: I simply replaced the line that errored out with parent.subfolders.pull(self._id); (It uses MongooseArray#pull) So far, it appears to do what I need and is just as simple. Let me know if there might be a better way. (I'm not sure I can change my schema to use sub docs directly given that the schema references itself.)
Thanks! Based on this, I'm using _.some(array, function(_id) {return _id.equals(id);}) to tell whether an embedded reference array contains a given id.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.