Is it possible to separate nested populate calls in different methods? - mongodb

Suppose I have the following schemas:
const QuestionSchema = new mongoose.Schema({
number: String,
question: { type: String, required: true },
submitter: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
});
const QuizSchema = new mongoose.Schema({
name: { type: String, required: true },
questions: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Question' }]
});
For a particular quiz, I know that if I want to populate the questions, including their submitters, I would do this:
quiz.populate({
path: 'questions',
model: mongoose.model('Question'),
options: {
sort: { number: 1 }
},
populate: {
path: 'questions.submitter',
model: mongoose.model('User')
}
}).exec(cb)
Now is it possible to separate the populate calls within different methods?
For example, I want to do something like
QuizSchema.methods.withQuestions = function () {
return this.populate({
path: 'questions',
model: this.model('Question'),
options: {
sort: { number: 1 }
}
});
};
QuizSchema.methods.withSubmitter = function () {
return this.populate({
path: 'questions.submitter',
model: this.model('User')
});
};
in order to do
quiz.withQuestions().withSubmitter().execPopulate().then(cb)
Unfortunately, when I try doing it this way, submitter does not get populated.

Related

Create ref to sub document's array for each property of subdocument

Model A looks like
{ a : {
step1: [{
type: { type: String },
category: { type: String },
}],
step2: [{
type: { type: String },
category: { type: String },
}]
} }
Model B which I wanted to create should contain a prop which will ref to Model A.step1 or A.step2 , trying to acheive this by following
{ progress : [
{
step: {
type: Schema.Types.ObjectId,
ref: "A.a.{What should be here}",
required: true,
},
details: { type: Schema.Types.Mixed },
}
]
}
I think you need to create Schemas for all of them :)
I would just separate the three completely - not sure if this is viable to you as the whole idea behind this is a bit mysterious, but would something like this work for you?
const stepSchema = new Schema({
type: String,
category: String,
});
const Step = mongoose.model("steps", stepSchema);
const aSchema = new Schema({
step1: [stepSchema],
step2: [stepSchema],
});
const A = mongoose.model("as", aSchema);
const progressSchema = new Schema({
a: { type: aSchema, required: true, ref: "as"},
progress: [{ type: stepSchema, required: true, ref: "steps" }],
details: Schema.Types.Mixed,
});
const Progress = mongoose.model("progresses", aSchema);

mongoose, match using upper model field in deep population

I'm a newbie in MongoDB and mongoose. and I'm trying to create a simple messenger using MongoDB.
Subscription model:
const SubscriptionSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User',
index: { unique: true }
},
conversations: [
{
conversation: {
type: Schema.Types.ObjectId,
ref: 'Conversation'
},
lastSeenMessage: {
type: Schema.Types.ObjectId,
ref: 'Message'
}
}
]
});
Conversation model:
const ConversationSchema = new Schema(
...,
{ timestamps: true, toJSON: { virtuals: true } }
);
ConversationSchema.virtual('messages', {
ref: 'Message',
localField: '_id',
foreignField: 'conversation'
});
Message model:
const MessageSchema = new Schema(
{
content: {
type: String,
maxlength: [1440, 'Text too long.']
},
type: {
type: String
},
conversation: {
type: Schema.Types.ObjectId,
ref: 'Conversation'
},
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
}
)
I need to retrieve m latest conversations of user and n messages within each conversation when they are written after last seen message. So I used mongoose like this:
let subscription = await Subscription.findOne(
{ user }
//{ ObjectArray: { $slice: [(page - 1) * limit, (page - 1) * limit + limit] } }
).populate({
path: 'conversations.conversation',
model: 'Conversation',
populate: {
path: 'messages',
match: { _id: { $gt: 'conversations.lastSeenMessage' } },
options: {
skip: 0,
limit: 50,
sort: { updatedAt: -1 }
}
}
});
the match condition doesn't works in above query. how can use upper model in match condition in the deep population ?
Is it possible or I must try another way?

How to work with many to few mongodb relationship with feathersjs?

I have two models, with a many-to-few relationship, that I'm modelling as follows:
// Portfolio
const portfoliosSchema = new Schema({
name: { type: String, required: true },
description: { type: String },
user: { type: Schema.Types.ObjectId, ref: 'User', required: true },
positions: [{
stock: { type: Schema.Types.ObjectId, ref: 'Stock', required: true },
cost: number,
amount: number,
}]
});
// Stock
const stocksSchema = new Schema({
exchange: { type: String, required: true },
symbol: { type: String, required: true },
company: { type: String },
description: { type: String }
});
Without writing a custom service / in a feathers friendly way, how would I:
Query portfolios and populate the relevant records from the stocks
collection
Simplify insert/updates to the nested positions within the portfolio schema (ie without updating the entire record)
Is this supported / should I write a custom service and/or normalize the relationship?
Edit - Solved #1 the first issue of getting extra data using a before hook:
function(hook) {
const query = hook.params.query;
hook.params.query = Object.assign({},query,{
$populate: {
path: 'positions.stock',
select: 'exchange symbol'
}
});
}
Sample of populate code, adjust to your needs:
Portfolio.find({ some: 'params' })
.populate({ path: 'stock', select: 'name -_id' })
.exec((err, portfolios) => {
res.json({ portfolio: portfolios });
});
Updating a nested array item:
Position.update({ 'position._id': 'h43q9h94945d948jh098j6h0' }, {
'$set': { 'position.$.whatever': 'your-updated-stuff' }
}, err => {
if (err) return console.log(err));
res.json({ success: true });
});

Mongoose populate nested not working

I have this user model:
const userSchema = new mongoose.Schema({
...,
instances: {
type: Array,
default: [{
role: 'user'
}],
role: {
type: String,
enum: ['admin', 'user']
},
company: {
type: mongoose.Schema.ObjectId,
ref: 'Company'
},
},
},
{
timestamps: true
}
);
And company model:
const companySchema = new mongoose.Schema({
name: {
type: String,
required: true
},
logo: {
type: mongoose.Schema.ObjectId,
ref: 'File',
required: true
}
}, { timestamps: true });
And this pre find method:
userSchema.pre('findOne', function () {
this.populate({
path: 'instances.company',
populate: {
path: 'logo',
model: 'File'
}
});
});
I want to populate the instances companies on each find, and populate the logo of the company.
The thing is, that the company is populated, but not the logo path.
What am I doing wrong?

Ordering two reference arrays together

Suppose I have the following schemas:
var QuizSchema = new mongoose.Schema({
name: { type: String, required: true },
questions: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Question' }],
questionGroups: [{ type: mongoose.Schema.Types.ObjectId, ref: 'QuestionGroup' }]
});
var QuestionSchema = new mongoose.Schema({
number: { type: String, required: true }, // e.g. 1, a, i, anything
question: { type: String, required: true },
type: { type: String, enum: ['multiple choice', 'multiple select', 'short answer'] },
choices: [String],
answers: [String]
});
var QuestionGroupSchema = new mongoose.Schema({
number: { type: String, required: true }, // e.g. 1, a, i, anything
prompt: { type: String },
questions: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Question' }]
});
I am trying to design a way that will allow me to order questions and question groups together.
I was thinking maybe of adding a new field order
var QuizSchema = new mongoose.Schema({
// ...
order: [
{
type: { type: String, enum: ['Question', 'QuestionGroup'] },
id: mongoose.Schema.Types.ObjectId // reference
}
]
});
such that in the database, the field would contain something such as
[
{ type: 'Question', id: ObjectId('57867a34567g67790') },
{ type: 'Question', id: ObjectId('57867a34567g67765') },
{ type: 'QuestionGroup', id: ObjectId('69864b64765y45645') },
{ type: 'Question', id: ObjectId('57867a34567g67770') },
{ type: 'QuestionGroup', id: ObjectId('69864b64767y45647') }
]
This may mean that I would need to "populate" the ordered list of questions and question groups as
quiz.populate('questions questionGroups').exec(function (err, quiz) {
// sort questions and groups by the order
quiz.order = quiz.order.map(function (o) {
if (o.type === 'QuestionGroup') {
return quiz.questionGroups.id(o.id);
}
return quiz.questions.id(o.id);
});
});
So my question: is there a better way to design this?
Virtuals can come in handy here; without persisting order field in db and doing calculations on client each time:
var QuizSchema = new mongoose.Schema({
name: { type: String, required: true },
questions: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Question' }],
questionGroups: [{ type: mongoose.Schema.Types.ObjectId, ref: 'QuestionGroup' }]
},
{
toObject: {
virtuals: true
},
toJSON: {
virtuals: true
}
}
);
QuizSchema
.virtual('order')
.get(function() {
return this.questions.concat(this.questionGroups); //questions followed by questionGroups
});
Sort on createdAt is of course optional, but for that you need to have this field in Question and QuestionGroup:
Quiz.find({}, function (err, quiz) {
//...
})
.populate({path : 'questions', options: {sort: { 'createdAt': 1 }}})
.populate({path : 'questionGroups', options: {sort: { 'createdAt': 1 }}});