Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

default values are not saved when added to existing documents, when schema is #12905

Closed
2 tasks done
NanoDylan opened this issue Jan 12, 2023 · 4 comments · Fixed by #13002
Closed
2 tasks done

default values are not saved when added to existing documents, when schema is #12905

NanoDylan opened this issue Jan 12, 2023 · 4 comments · Fixed by #13002
Labels
confirmed-bug We've confirmed this is a bug in Mongoose and will fix it.
Milestone

Comments

@NanoDylan
Copy link

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Mongoose version

6.8.3

Node.js version

16.18.1

MongoDB server version

5.0.14

Typescript version (if applicable)

No response

Description

I have a complex schema which has a few subdocuments. I've added a new property to the subdocument with a default value. Mongoose is providing the default value, but the default value is not actually getting saved to the MongoDB document when doing a .save on the parent model. It seems like the subdocument variant of this issue #2558.

Steps to Reproduce

test(`Subdocument Schema update`, async () => {

  const subDocument = new mongoose.Schema ({
    refOriginal: String
  });

  const modifiedSubDocument = new mongoose.Schema ({
    refOriginal: String,
    refAnother: {
      type: String,
      default: 'goodbye'
    }
  });

  const Test = new mongoose.Schema({
    original: String,
    referenced: subDocument
  }, { collection: 'test' });

  const ModifiedTest = new mongoose.Schema({
    original: String,
    another: {
      type: String,
      default: 'hello'
    },
    referenced: {
      type: modifiedSubDocument,
      default: () => ({})
    }
  }, { collection: 'test' });

  const TestModel = mongoose.model('TestModel', Test);
  const ModifiedTestModel = mongoose.model('ModifiedTestModel', ModifiedTest);

  // Create a TestModel document which is saved to the database.
  const testMongooseDoc = new TestModel(
    {
      original: 'original value',
      referenced: {
        refOriginal: 'ref original value'
      }
    });
  await testMongooseDoc.save();

  // Circumvent Mongoose and fetch a raw Mongo document.
  const testMongoDoc = await TestModel.collection.findOne({"_id": testMongooseDoc._id});

  // Fetch the document with the Mongoose ModifiedTestModel model.
  const modifiedTestModelMongooseFindOneDoc = await ModifiedTestModel.findOne({"_id": testMongooseDoc._id});

  // Circumvent Mongoose and fetch a raw Mongo document. No expectation that there will be any changes.
  const modifiedTestMongoDocAfterFindOne = await ModifiedTestModel.collection.findOne({"_id": testMongooseDoc._id});

  // Expectation is that the 'another' property AND the 'referenced.refAnother' property will be saved to the
  // database document when executing .save().
  const modifiedTestMongooseDocAfterSave = await modifiedTestModelMongooseFindOneDoc.save();

  // Circumvent Mongoose and fetch the raw Mongo document after .save().
  const modifiedTestMongoDocAfterSave = await ModifiedTestModel.collection.findOne({"_id": testMongooseDoc._id});

  // Asserts which validate the original Test model document has the expected properties and nothing from the
  // ModifiedTest model
  expect(testMongooseDoc.original).toStrictEqual('original value');
  expect(testMongooseDoc.referenced.refOriginal).toStrictEqual('ref original value');
  expect(testMongooseDoc.another).toBeUndefined();
  expect(testMongooseDoc.referenced.refAnother).toBeUndefined();

  // Asserts which validate the original Test model document has the expected properties and nothing from the
  // ModifiedTest model, outside of Mongoose.
  expect(testMongoDoc.original).toStrictEqual('original value');
  expect(testMongoDoc.referenced.refOriginal).toStrictEqual('ref original value');
  expect(testMongoDoc.another).toBeUndefined();
  expect(testMongoDoc.referenced.refAnother).toBeUndefined();

  // Even though the MongoDB document does not have 'another' or 'referenced.refAnother' the ModifiedTest model will
  // fill in the defaults for us.
  expect(modifiedTestModelMongooseFindOneDoc.original).toStrictEqual('original value');
  expect(modifiedTestModelMongooseFindOneDoc.referenced.refOriginal).toStrictEqual('ref original value');
  expect(modifiedTestModelMongooseFindOneDoc.another).toStrictEqual('hello');
  expect(modifiedTestModelMongooseFindOneDoc.referenced.refAnother).toStrictEqual('goodbye');

  // Confirm that outside of Mongoose, the stored MongoDB document does not have the ModifiedTest model properties.
  expect(modifiedTestMongoDocAfterFindOne.original).toStrictEqual('original value');
  expect(modifiedTestMongoDocAfterFindOne.referenced.refOriginal).toStrictEqual('ref original value');
  expect(modifiedTestMongoDocAfterFindOne.another).toBeUndefined();
  expect(modifiedTestMongoDocAfterFindOne.referenced.refAnother).toBeUndefined();

  // The document returned from the Mongoose Document .save() has all the ModifiedTest model properties.
  expect(modifiedTestMongooseDocAfterSave.original).toStrictEqual('original value');
  expect(modifiedTestMongooseDocAfterSave.referenced.refOriginal).toStrictEqual('ref original value');
  expect(modifiedTestMongooseDocAfterSave.another).toStrictEqual('hello');
  expect(modifiedTestMongooseDocAfterSave.referenced.refAnother).toStrictEqual('goodbye');

  // Verify that the ModifiedTest model properties have been updated in the MongoDB database.
  expect(modifiedTestMongoDocAfterSave.original).toStrictEqual('original value');
  expect(modifiedTestMongoDocAfterSave.referenced.refOriginal).toStrictEqual('ref original value');
  expect(modifiedTestMongoDocAfterSave.another).toStrictEqual('hello');
  // In mongoose@6.8.3, the referenced Schema property 'refAnother' as not been updated in the MongoDB database and
  // causes this assert to fail.
  expect(modifiedTestMongoDocAfterSave.referenced.refAnother).toStrictEqual('goodbye');

});

Expected Behavior

When executing Model.save(), any properties with defaults in the parent model or subdocuments that do not exist in the database document will get updated in the database document with those defaults.

@IslandRhythms IslandRhythms added the confirmed-bug We've confirmed this is a bug in Mongoose and will fix it. label Jan 18, 2023
@IslandRhythms
Copy link
Collaborator

const mongoose = require('mongoose');
const assert = require('assert');

const subDocument = new mongoose.Schema ({
  refOriginal: String
});

const modifiedSubDocument = new mongoose.Schema ({
  refOriginal: String,
  refAnother: {
    type: String,
    default: 'goodbye'
  }
});

const Test = new mongoose.Schema({
  original: String,
  referenced: subDocument
}, { collection: 'test' });

const ModifiedTest = new mongoose.Schema({
  original: String,
  another: {
    type: String,
    default: 'hello'
  },
  referenced: {
    type: modifiedSubDocument,
    default: () => ({})
  }
}, { collection: 'test' });

const TestModel = mongoose.model('TestModel', Test);
const ModifiedTestModel = mongoose.model('ModifiedTestModel', ModifiedTest);

async function run() {
  await mongoose.connect('mongodb://localhost:27017');
  await mongoose.connection.dropDatabase();
  // Create a TestModel document which is saved to the database.
  const testMongooseDoc = await TestModel.create({
    original: 'original value',
    referenced: {
      refOriginal: 'ref original value'
    }
  });
  // Circumvent Mongoose and fetch a raw Mongo document.
  const testMongoDoc = await TestModel.collection.findOne({"_id": testMongooseDoc._id});

  // Fetch the document with the Mongoose ModifiedTestModel model.
  const modifiedTestModelMongooseFindOneDoc = await ModifiedTestModel.findOne({"_id": testMongooseDoc._id});

  // Circumvent Mongoose and fetch a raw Mongo document. No expectation that there will be any changes.
  const modifiedTestMongoDocAfterFindOne = await ModifiedTestModel.collection.findOne({"_id": testMongooseDoc._id});

  // Expectation is that the 'another' property AND the 'referenced.refAnother' property will be saved to the
  // database document when executing .save().
  const modifiedTestMongooseDocAfterSave = await modifiedTestModelMongooseFindOneDoc.save();

  // Circumvent Mongoose and fetch the raw Mongo document after .save().
  const modifiedTestMongoDocAfterSave = await ModifiedTestModel.collection.findOne({"_id": testMongooseDoc._id});

  // Asserts which validate the original Test model document has the expected properties and nothing from the
  // ModifiedTest model
  assert.strictEqual(testMongooseDoc.original, 'original value');
  assert.strictEqual(testMongooseDoc.referenced.refOriginal, 'ref original value');
  assert.equal(testMongooseDoc.another, undefined);
  assert.equal(testMongooseDoc.referenced.refAnother, undefined);

  // Asserts which validate the original Test model document has the expected properties and nothing from the
  // ModifiedTest model, outside of Mongoose.

  assert.strictEqual(testMongoDoc.original, 'original value');
  assert.strictEqual(testMongoDoc.referenced.refOriginal, 'ref original value');
  assert.equal(testMongoDoc.another, undefined);
  assert.equal(testMongoDoc.referenced.refAnother, undefined);

  // Even though the MongoDB document does not have 'another' or 'referenced.refAnother' the ModifiedTest model will
  // fill in the defaults for us.

  assert.strictEqual(modifiedTestModelMongooseFindOneDoc.original, 'original value');
  assert.strictEqual(modifiedTestModelMongooseFindOneDoc.referenced.refOriginal, 'ref original value');
  assert.strictEqual(modifiedTestModelMongooseFindOneDoc.another, 'hello');
  assert.strictEqual(modifiedTestModelMongooseFindOneDoc.referenced.refAnother, 'goodbye');
  // Confirm that outside of Mongoose, the stored MongoDB document does not have the ModifiedTest model properties.
  assert.strictEqual(modifiedTestMongoDocAfterFindOne.original, 'original value');
  assert.strictEqual(modifiedTestMongoDocAfterFindOne.referenced.refOriginal, 'ref original value');
  assert.equal(modifiedTestMongoDocAfterFindOne.another, undefined);
  assert.equal(modifiedTestMongoDocAfterFindOne.referenced.refAnother, undefined);

  // The document returned from the Mongoose Document .save() has all the ModifiedTest model properties.

  assert.strictEqual(modifiedTestMongooseDocAfterSave.original, 'original value');
  assert.strictEqual(modifiedTestMongooseDocAfterSave.referenced.refOriginal, 'ref original value');
  assert.strictEqual(modifiedTestMongooseDocAfterSave.another, 'hello');
  assert.strictEqual(modifiedTestMongooseDocAfterSave.referenced.refAnother, 'goodbye');

  // Verify that the ModifiedTest model properties have been updated in the MongoDB database.
  assert.strictEqual(modifiedTestMongoDocAfterSave.original, 'original value');
  assert.strictEqual(modifiedTestMongoDocAfterSave.referenced.refOriginal, 'ref original value');
  assert.strictEqual(modifiedTestMongoDocAfterSave.another, 'hello');
  // In mongoose@6.8.3, the referenced Schema property 'refAnother' as not been updated in the MongoDB database and
  // causes this assert to fail.
  assert.strictEqual(modifiedTestMongoDocAfterSave.referenced.refAnother, 'goodbye');

}

run();

@sibelius
Copy link
Contributor

I have a situation that is similar, not sure if the bug is the same

this happened in the recent versions, not sure where yet ( it is breaking on 6.8.2, it works on 6.0.0)

nested paths are not working well when using just .findOne, but works well with lean

example

this breaks

const user = await User.findOne();

user.my.nested.path // this is undefined

this do not breaks

const user = await User.findOne().lean()

user.my.nested.path // not undefined

@IslandRhythms
Copy link
Collaborator

Please open a new issue

@sibelius
Copy link
Contributor

done #12926

@vkarpov15 vkarpov15 added this to the 6.9.1 milestone Feb 4, 2023
vkarpov15 added a commit that referenced this issue Feb 6, 2023
fix(document): save newly set defaults underneath single nested subdocuments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
confirmed-bug We've confirmed this is a bug in Mongoose and will fix it.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants