Mongoose: Base models do not honor select: false for discriminators

Created on 16 Feb 2017  ·  3Comments  ·  Source: Automattic/mongoose

Do you want to request a feature or report a bug?
Bug

What is the current behavior?
When a base model is used to retrieve a discriminator model document, it does not honor select: false.

If the current behavior is a bug, please provide the steps to reproduce.

const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
const co = require('co');
const chalk = require('chalk');

const GITHUB_ISSUE = `test-constructor-emit-bug`;

exec()
  .then(() => {
    console.log(chalk.green(`Successfully ran program`));
    process.exit(0);
  })
  .catch(error => {
    console.log(chalk.red(`Error: ${ error }\n${ error.stack }`));
    process.exit(2);
  });

function exec() {
  return co(function* () {
    const db = mongoose.createConnection(`mongodb://localhost:27017/${ GITHUB_ISSUE }`);
    const { Base, Discriminator } = createModels(db);
    const { baseDoc, discrimDoc } = yield seedDb({ Base, Discriminator });

    const docFromDb = yield Base.findById(discrimDoc._id) // does not honor select: false
    // const docFromDb = yield Discriminator.findById(discrimDoc._id); // honors select: false
    console.log(docFromDb.internal); // should not log `internal.password`
  });
}

function seedDb(models) {
  return co(function*() {
    const { Base, Discriminator } = models;

    yield [Base.remove({}), Discriminator.remove({})];

    const baseDoc = yield Base.create({ internal: { diseases: ['Malaria'] }});
    const discrimDoc = yield Discriminator.create({ internal: {
      diseases: ['MS'],
      password: 'plain_test_password_ftw'
    }});

    return { baseDoc, discrimDoc };
  });
}

function createModels(db) {
  const baseSchema = new mongoose.Schema({
    internal: {
      diseases: [{ type: String }]
    }
  });

  const Base = db.model('Base', baseSchema);
  const discriminatorSchema = new mongoose.Schema({
    internal: {
      password: { type: String, select: false }
    }
  });
  const Discriminator = Base.discriminator('Discriminator', discriminatorSchema);

  return { Base, Discriminator };
}

What is the expected behavior?
We should probably unselect those fields manually after fetching from the database, since mongoose doesn't know ahead of time which discriminators to select for.

Please mention your node.js, mongoose and MongoDB version.
node 6.9.2, mongoose 4.8.3, mongodb 3.4

confirmed-bug

All 3 comments

I seem to be experiencing unexpected side effects from this change:

I have a base model A, with a discriminator model B.
When I query A for documents using a $slice array projection, resulting documents of type A return all properties (excluding those marked as select: false), while resulting documents of type B only return properties _explicitly_ marked as select: true or those with a default set. Those with a default set return the default value, not the value actually stored in the database. If no properties are marked with select: true, only properties with default values are returned (again with the default value, not the store value).

EDIT: Disregard my comment, the issue was that mongoose adds the discriminator key to the query under the hood. The solution was to use the slice function on the Query.

I might have spoken too soon. Testing with both version 4.13.4 and the latest master commit, I am seeing the same behaviour when only using the slice projection.

I have taken the original example in this issue and modified to reproduce the behaviour:

const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
const co = require('co');
const chalk = require('chalk');
mongoose.set('debug', true);
const GITHUB_ISSUE = `default-values-on-discriminator-documents`;

exec()
    .then(() => {
        console.log(chalk.green(`Successfully ran program`));
        process.exit(0);
    })
    .catch(error => {
        console.log(chalk.red(`Error: ${error}\n${error.stack}`));
        process.exit(2);
    });

function exec() {
    return co(function* () {
        const db = mongoose.createConnection(`mongodb://localhost:27017/${GITHUB_ISSUE}`);
        const { Base, Discriminator } = createModels(db);
        const { baseDoc, discrimDoc } = yield seedDb({ Base, Discriminator });

        const baseDocsFromDbWithSlice = yield Base.find().slice('array', 1)
        const baseDocsFromDb = yield Base.find()
        console.log(baseDocsFromDbWithSlice); // Discriminator document returns default value for propA (and not propB), base document returns stored value - unexpected
        console.log(baseDocsFromDb); // Discriminator and base document returns stored value for propA - expected
    });
}

function seedDb(models) {
    return co(function* () {
        const { Base, Discriminator } = models;

        yield [Base.remove({}), Discriminator.remove({})];

        const baseDoc = yield Base.create({
            propA: 'Hi',
            array: ["a", "b"]
        });
        const discrimDoc = yield Discriminator.create({
            propA: 'Hi',
            propB: 'Hello',
            array: ["a", "b"]
        });

        return { baseDoc, discrimDoc };
    });
}

function createModels(db) {
    const baseSchema = new mongoose.Schema({
        propA: { type: String, default: 'default value' },
        array: [{type: String}],
    });

    const Base = db.model('Base', baseSchema);
    const discriminatorSchema = new mongoose.Schema({
        propB: { type: String}
    });
    const Discriminator = Base.discriminator('Discriminator', discriminatorSchema);

    return { Base, Discriminator };
}

This behaviour changed in 4.11.13, which is why I suspected the resolution for this issue to be the cause. If not, I'll be happy to create a new issue

Thanks for reporting @bruun , will investigate asap :+1:

Was this page helpful?
0 / 5 - 0 ratings