alexmingoia / mongoose-populate-virtuals

Extend Mongoose 4+ population with virtual attributes that can be populated in either direction.
33 stars 3 forks source link

"select" must include foreign key #4

Open Pyo25 opened 8 years ago

Pyo25 commented 8 years ago

Hello,

I'm trying to use the plugin because I'm using an existing dataset that doesn't follow Mongoose conventions. Typically, I have this kind of models:

var task = {
   Status: 'Done',
   UserId: 'bob'
};
var user = {
  Username: 'Bob',
  Email: 'bob@gmail.com'
};

I'd like to use Mongoose to get a task and populate it with the corresponding user using the 'Username' as a key. I tried to use the plugin but the virtual field stays undefined.

Here is the code I wrote to test:

var mongoose = require('mongoose-populate-virtuals')(require('mongoose'));
var Schema = mongoose.Schema;

mongoose.connect('mongodb://....', function (err) {
  if (err) {
    console.error('unable to connect', err);
    return;
  }
  console.info('connected');

  var Task = createTaskModel();
  createUserModel();

  Task.findOne({_id: '5627f2804209631370fe1376'}, {UserId: 1})
    .populate('user')
    .exec(function (err, t) {
      if (err) {
        console.error('unable to find one', err);
        return;
      }
      console.info('Task found: ');
      console.log(t.toJSON());
    });
});

function createTaskModel () {
  var taskSchema = new Schema({
    Status: String,
    UserId: String
  }, {
    collection: 'tasks',
    toJSON: {virtuals: true},
    toObject: {virtuals: true}
  });

  taskSchema.virtual('testVirt').get(function () {
    return 'Test Virtual Field';
  });

  taskSchema.virtual('user', {
    ref: 'User',
    foreignKey: 'Username',
    localKey: 'UserId',
    select: 'Email'
  });

  return mongoose.model('Task', taskSchema);
}

function createUserModel () {

  var userSchema = new Schema({
    _id: String,
    Username: String,
    Email: String
  }, {
    collection: 'users'
  });

  return mongoose.model('User', userSchema);
}

The output is:

Task found:
{ _id: 5627f2804209631370fe1376,
  UserId: 'e.apiyo',
  user: undefined,
  testVirt: 'Test Virtual Field',
  id: '5627f2804209631370fe1376' }

Moreover if I try to "debug" the plugin, I can see that the user is correctly loaded. In the assignVals method, I can output the different parameters and there is this undefined key that looks strange:

path >>  user
docs >>  [ { _id: 5627f2804209631370fe1376,
    UserId: 'e.apiyo',
    user: 'undefined' } ]
subdocs >>  { undefined:
   [ { FirstName: 'Eric',
       Email: 'eric.apiyo@ke.airtel.com',
       _id: '0#te�\u001a�A�fjw\u001d\u0010+�' } ] }
options >> { model:
   { [Function: model]
     hooks: { _pres: {}, _posts: {} },
     base:
      { connections: [Object],
        plugins: [],
        models: [Object],
        modelSchemas: [Object],
        options: [Object] },
     modelName: 'User',
     model: [Function: model],
     db:
      { base: [Object],
        collections: [Object],
        models: [Object],
        config: [Object],
        replica: false,
        hosts: null,
        host: 'localhost',
        port: 27017,
        user: undefined,
        pass: undefined,
        name: 'airtelke-globetrotter-db',
        options: [Object],
        otherDbs: [],
        _readyState: 1,
        _closeCalled: false,
        _hasOpened: true,
        _listening: false,
        _events: {},
        db: [Object] },
     discriminators: undefined,
     schema:
      { paths: [Object],
        subpaths: {},
        virtuals: [Object],
        singleNestedPaths: {},
        nested: {},
        inherits: {},
        callQueue: [Object],
        _indexes: [],
        methods: {},
        statics: {},
        tree: [Object],
        _requiredpaths: [],
        discriminatorMapping: undefined,
        _indexedpaths: undefined,
        s: [Object],
        options: [Object],
        _events: {} },
     collection:
      { collection: [Object],
        opts: [Object],
        name: 'users',
        collectionName: 'users',
        conn: [Object],
        queue: [],
        buffer: false,
        emitter: [Object] } },
  _docs: { '5627f2804209631370fe1376': 'e.apiyo' },
  options: undefined,
  arrayPop: undefined,
  localKey: 'UserId',
  foreignKey: 'Username',
  select: 'Email FirstName',
  match: undefined,
  path: 'user' }
assignmentOpts >> { sort: undefined, excludeId: false }

Am I doing something wrong or is there something broken?

Thank you!

alexmingoia commented 8 years ago

Where you select: 'Email' change to select: 'Email Username'. I think what is happening is the foreignKey field isn't being returned because it's not selected. Just a guess... the code looks correct otherwise.

Pyo25 commented 8 years ago

Indeed, it helped to add the Username in the select: when debugging, I can see that the final promise is resolved with an object correctly populated. (in the Model.populate method)

However in my main script the attribute is still undefined. It's like the property was overridden afterwards.

Do you have any idea what I could investigate?

Btw I'm using Mongoose 4.4.3 (+ MongoDB driver 2.1.7). On which version did you test?

Pyo25 commented 8 years ago

I realized the property was deleted in the init pre middleware line 35 and the virtual getter is not called when I access the attribute. This is why I got an undefined. If I remove the delete line, then I can access the populated field.

I don't really understand why the getter is not called... Any idea?

HoKangInfo commented 8 years ago

"select" must include "foreignKey".
this case {select: 'Email'} change to {select: 'Email Username'} Can add auto append foreignKey as _id?