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

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 });
});

Related

How to develop nested condition query in mongoDB

I am pretty new to mongoDb and want to apply nested query.
I have a business schema like this:
const businessSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
},
businessType: {
type: Schema.Types.ObjectId,
ref: "businessCategory",
required: true,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
select: false,
},
review: {
type: [reviewSchema],
},
isDeleted: {
type: Boolean,
default: false,
},
},
{ timestamps: true }
);
Business has a review where user can do the review and reviewSchema is
const reviewSchema = new mongoose.Schema(
{
user: {
type: Schema.Types.ObjectId,
ref: "users",
required: true,
},
rating: {
type: Number,
enum: [1, 2, 3, 4, 5],
},
reviewArray: {
type: [singleReviewSchema],
},
},
{ timestamps: true }
);
One user can do many reviews, and it has reviewArray.
ReviewArray schema is
const singleReviewSchema = new mongoose.Schema(
{
title: {
type: String,
},
description: {
type: String,
},
isDeleted: {
type: Boolean,
default: false,
},
},
{ timestamps: true }
);
How to fetch the business with a condition business: isDeleted:false and its reviews with singleReviewSchema: isDeleted:false
I dont know your model names, so please replace path with correct names
but it might look like:
businnesModel.find({isDeleted: false})
.populate({
path: 'reviewModelName',
model: 'review',
populate: {
path: 'reviewArray',
model: 'singleReviewModelName',
match: {
isDeleted : false
}
}
})
It should provide you array of businessModel documents - even when their singleReviews array will be empty (because all of reviews are deleted, or there was zero reviews). So you have to filter it out in JS.
To avoid filtering in JS, and to do it a bit more efficient way for mongodb, you can go with aggregate instead.

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?

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

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.

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 }}});

Mongoose: How to query objects inside populate? [duplicate]

This question already has answers here:
Querying after populate in Mongoose
(6 answers)
Closed 6 years ago.
I have this route for searching partial user input for orderId.
ordersAdminRouter.route('/searchorder/:term')
.get(function(req, res){
term = req.params.term;
console.log(term);
Orders.find({orderId: new RegExp(term)})
.populate({ path: 'userPurchased products.product', select: '-username -password' })
.exec(function(err, orders){
if (err) throw err;
console.log(orders);
res.json(orders);
});
});
Here is my schema
var orderSchema = new Schema({
orderId: { type: String },
userPurchased: { type: Schema.Types.ObjectId, ref: 'users' },
products: [
{
product: { type: Schema.Types.ObjectId, ref: 'products' },
size: { type: String, required: true },
quantity: { type: Number, required: true },
subTotal: { type: Number, required: true }
}
],
totalQuantity: { type: Number },
totalPrice: { type: Number },
modeOfPayment: { type: String },
shippingAd: { type: String },
refNumber: { type: String },
isDone: { type: Boolean, default: false },
orderStatus: { type: String, default: 'Pending' },
dateOrdered: { type: Date, default: Date.now() },
fromNow: { type: String }
});
Now I need to search for the firstname and lastname inside userPurchased which I only get when I populate. How can search it?
I could not understand what kind of information you are saving in the 'userPurchased' field. I assume the users are in a different collection and you're only persisting the id of the user in the userPurchased. If you are just placing the id, you can search using the following query:
orderSchema.find({userPurchased : 'id'})
If you're putting an object with fields, there's no mystery either. Just use the . operator. For example:
orderSchema.find({userPurchased.firstName : 'name'})
Hope it helped you