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

Allow overwriting localField and foreignField when populating virtuals #6963

Closed
lcw0622 opened this issue Sep 3, 2018 · 10 comments · Fixed by #12657
Closed

Allow overwriting localField and foreignField when populating virtuals #6963

lcw0622 opened this issue Sep 3, 2018 · 10 comments · Fixed by #12657
Assignees
Labels
enhancement This issue is a user-facing general improvement that doesn't fix a bug or add a new feature
Milestone

Comments

@lcw0622
Copy link

lcw0622 commented Sep 3, 2018

Project.virtual('userInfo', {  
  ref: 'User',  
  localField: 'username',  
  foreignField: 'username',  
  justOne: true  
})  
projectModel.find().populate('userInfo')  

It return without userInfo field, but if I change foreignField to _id, it will report an error like:
CastError: Cast to ObjectId failed for value "username" at path "_id" for model "User".
Mongoose: 4.12.3
MongoDB: 2.2.34
NodeJS: 8.11.2

@lineus
Copy link
Collaborator

lineus commented Sep 3, 2018

@lcw0622 The following complete example shows that population works. Can you edit it to show the error you are seeing?

6963.js

#!/usr/bin/env node
'use strict';

const assert = require('assert');
const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
mongoose.connect('mongodb://localhost:27017/test', { useMongoClient: true});
const conn = mongoose.connection;
const Schema = mongoose.Schema;

const userSchema = new Schema({
  name: String,
  username: String
});

const projectSchema = new Schema({
  name: String,
  username: String
});

projectSchema.virtual('userInfo', {
  ref: 'user',
  localField: 'username',
  foreignField: 'username',
  justOne: true
});

const User = mongoose.model('user', userSchema);
const Project = mongoose.model('project', projectSchema);

const user = new User({ name: 'Billy', username: 'quickdraw' });
const project = new Project({ name: 'Outlaw', username: user.username });

async function run() {
  assert.strictEqual(mongoose.version, '4.12.3');
  await conn.dropDatabase();
  await user.save();
  await project.save();
  let doc = await Project.findOne({}).populate('userInfo');
  assert.strictEqual(doc.userInfo.name, 'Billy');
  console.log('Assertions passed.');
  return conn.close();
}

run();

Output:

issues: ./6963.js
Assertions passed.
issues:

@lineus lineus added the can't reproduce Mongoose devs have been unable to reproduce this issue. Close after 14 days of inactivity. label Sep 3, 2018
@lineus
Copy link
Collaborator

lineus commented Sep 4, 2018

did you also change the creation of the project doc to reflect the new field erp instead of username?

Here is the same code as above but with the Project path erp instead of username.

6963.js

#!/usr/bin/env node
'use strict';

const assert = require('assert');
const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
mongoose.connect('mongodb://localhost:27017/test', { useMongoClient: true});
const conn = mongoose.connection;
const Schema = mongoose.Schema;

const userSchema = new Schema({
  name: String,
  username: String
});

const projectSchema = new Schema({
  name: String,
  erp: String // changed
});

projectSchema.virtual('userInfo', {
  ref: 'user',
  localField: 'erp', // changed
  foreignField: 'username',
  justOne: true
});

const User = mongoose.model('user', userSchema);
const Project = mongoose.model('project', projectSchema);

const user = new User({ name: 'Billy', username: 'quickdraw' });
const project = new Project({ name: 'Outlaw', erp: user.username }); // changed

async function run() {
  assert.strictEqual(mongoose.version, '4.12.3');
  await conn.dropDatabase();
  await user.save();
  await project.save();
  let doc = await Project.findOne({}).populate('userInfo');
  assert.strictEqual(doc.userInfo.name, 'Billy');
  console.log('Assertions passed.');
  return conn.close();
}

run();

Output:

issues: ./6963.js
Assertions passed.
issues:

@lcw0622
Copy link
Author

lcw0622 commented Sep 10, 2018

Thanks, In fact I use toObject to output result document, and then I found the model.toObject function seems doesn't handle the virtual populate field.

#!/usr/bin/env node
'use strict';

const assert = require('assert');
const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
mongoose.connect('mongodb://localhost:27017/test', { useMongoClient: true});
const conn = mongoose.connection;
const Schema = mongoose.Schema;

const userSchema = new Schema({
  name: String,
  username: String
});

const projectSchema = new Schema({
  name: String,
  username: String
});

projectSchema.virtual('userInfo', {
  ref: 'user',
  localField: 'username',
  foreignField: 'username',
  justOne: true
});

const User = mongoose.model('user', userSchema);
const Project = mongoose.model('project', projectSchema);

const user = new User({ name: 'Billy', username: 'quickdraw' });
const project = new Project({ name: 'Outlaw', username: user.username });

async function run() {
  assert.strictEqual(mongoose.version, '4.12.3');
  await conn.dropDatabase();
  await user.save();
  await project.save();
  let doc = await Project.findOne({}).populate('userInfo');
  let plainObj = doc.toObject()
  assert.strictEqual(plainObj.userInfo.name, 'Billy');
  console.log('Assertions passed.');
  return conn.close();
}

run();

Output:

(node:76898) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'name' of undefined
    at run (/Users/liuchengwei/Work/work/talos-backend/mongo.js:41:40)
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:188:7)
(node:76898) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:76898) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

@lineus
Copy link
Collaborator

lineus commented Sep 10, 2018

@lcw0622 you need to set the toObject virtuals option on the project schema to true in order to get virtual fields in the output of toObject().

const projectSchema = new Schema({
  name: String,
  username: String
}, { toObject: { virtuals: true } });

or

const projectSchema = new Schema({
  name: String,
  username: String
});

projectSchema.set('toObject', { virtuals: true });

@lcw0622
Copy link
Author

lcw0622 commented Sep 10, 2018

@lineus It take effect! Thanks a lot~ Maybe I need ready API docs more careful T_T

@lcw0622 lcw0622 closed this as completed Sep 10, 2018
@lcw0622
Copy link
Author

lcw0622 commented Sep 11, 2018

@lineus By the way, may I ask for why not add foreignFiled for Schema reference definition or in populate option? thanks

var personSchema = Schema({
  _id: Schema.Types.ObjectId,
  name: String,
  age: Number,
  stories: [{ type: String, ref: 'Story', foreignField: 'storyName' }]
});

or

Person.
  findOne({ name: 'Ian Fleming' }).
  populate({
path: 'stories',
foreignField: 'storyName'
}).

@lineus
Copy link
Collaborator

lineus commented Sep 12, 2018

@vkarpov15 I'll let you answer the question. Why do only virtuals accept the local and foreign field options as opposed to being able to set them in the schema or the populate object.

@vkarpov15
Copy link
Collaborator

Re: setting foreignField in the schema and in populate(), that's an interesting idea, we'll consider that for a future release.

@vkarpov15 vkarpov15 reopened this Sep 16, 2018
@vkarpov15 vkarpov15 added enhancement This issue is a user-facing general improvement that doesn't fix a bug or add a new feature and removed can't reproduce Mongoose devs have been unable to reproduce this issue. Close after 14 days of inactivity. labels Sep 16, 2018
@vkarpov15 vkarpov15 added this to the 5.x Unprioritized milestone Sep 16, 2018
@lcw0622
Copy link
Author

lcw0622 commented Sep 17, 2018

Thanks ,@lineus and @vkarpov15 ~
I think it will be very helpful for SQL users translate such sql:

SELECT
  FIELD_A, 
  (SELECT T_B.FIELD_B FROM T_B WHERE T_B.FIELD_A = T_A.FIELD_A) AS FIELD_B
FROM T_A
WHERE T_A='SOME OTHER CONDITIONS'

@vkarpov15
Copy link
Collaborator

@lcw0622 unlikely to happen anytime soon, but thanks for the suggestion

@vkarpov15 vkarpov15 changed the title populate by 'virtual' dosen't work Allow overwriting localField and foreignField when populating virtuals Nov 2, 2022
@vkarpov15 vkarpov15 modified the milestones: 6.x Unprioritized, 6.8 Nov 2, 2022
@IslandRhythms IslandRhythms linked a pull request Nov 4, 2022 that will close this issue
vkarpov15 added a commit that referenced this issue Nov 27, 2022
Island rhythms/gh 6963 Can overwrite foreignField
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement This issue is a user-facing general improvement that doesn't fix a bug or add a new feature
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants