Skip to content

Setting fields on nested schema arrays causes entire array to be marked as modified. #2432

Description

@AshHeskes

e.g. for the following example setup.

var user_schema = new Schema ( {
    name : String,
    sessions : [
        new Schema ( { latest : Boolean, created : { type : Date, default : Date.now } } );
    ]
} );

var User = mongoose. model ( user_schema  ),
    ash = new User();

ash.name = 'ashheskes';
ash.sessions.push ( [ { latest : false }, { latest : true } ] );

// We need to save the user first because the sessions array has only just been created anyway.
ash.save ( function ( error, ash ) {

    User.findOne ( ash.id, function ( error ) {

        /* Add a new item to the array */
        var new_session_index = ash sessions.push ( { latest : true } );

        /* Loop through all existing sessions and unmark them as latest */
        ash.sessions.forEach ( function ( session, i ) {

            if ( i === new_session_index )  return;
            else                            session.latest = false;
        } );

        /* Causes the entire array to be marked modified instead of the individual paths. */
        console.log ( ash.isModified ( 'sessions' ) ); // > true
    } );
} );

In most cases this wouldn't be a problem because the entire array of nested schemas has been returned.
However for following example using a $slice projector on the array. It causes only the content returned to be saved, discarding all other data stored in the array in the DB.

/* Find `ash` but only return the last session in the array. */
User. findOne ( ash. id, { sessions : { $slice : -1 } }, function ( error, ash ) {

    var new_session_index = ash.sessions.push ( { latest : true } ) - 1;

    /* Loop through all existing sessions and unmark them as latest */
    ash.sessions.forEach ( function ( session, i ) {

        if ( i === new_session_index )  return;
        else                            session.latest = false;
    } );

    ash.save ( function ( error, ash ) {

        /*
            sessions should be 3, but the entire array in the DB 
            has been overwritten by the array on the client.
        */
        console.log ( ash.sessions.length ); // > 2
    } ); 
} );

I've tried using a different approach with user.set(). However this seems to completely overwrite the document in the array with a basic Object and causes an error when saving.

/* Find `ash` but only return the last session in the array. */
User. findOne ( ash. id, { sessions : { $slice : -1 } }, function ( error, ash ) {

    var new_session_index = ash.sessions.push ( { latest : true } ) - 1;

    /* Loop through all existing sessions and unmark them as latest */
    ash.sessions.forEach ( function ( session, i ) {

        if ( i === new_session_index )  return;
        else                            ash.set ( 'sessions.' + i + '.latest', true );
    } );

    /* Subdocuments converted to `Objects` */
    console.log ( ash.sessions )        // > [Object, Object, subdocument]

    /* All other data from the subdoc's is lost. */
    console.log ( ash.sessions [ 0 ] )  // > { latest : false }

    /*
        An internal error occurs when mongoose tries to call save 
        on subdocs that got converted to standard `Objects`.
    */
    ash.save(); // > Error : #Object has no method save.
} );

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementThis issue is a user-facing general improvement that doesn't fix a bug or add a new feature

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions