Populating Mongoose virtual that aggregates two different collections - mongodb

My app currently has a bit of a complicated situation that is causing me all sorts of problems. As part of the app we are building recipes, and the recipes are made of ingredients (here simply called "foods" because they can also be used on their own). We have a collection of static recipes and one of dynamic ("user") recipes that are copied from the static version when associated with a user. Technically these recipes all live in one collection but use a discriminator.
However, for foods, we have a database we purchased from a third party, but we also may need to add our own foods. Because updates from the third party database may need to overwrite that database, we need to separate foods from the third party from foods we create. So here we need two entirely separate collections, which we'll call "foodsTP" and "foodsAdmin". However, because these should function exactly the same way from a user's perspective, we don't want the front end to care which collection the foods are coming from.
When getting foods directly, this isn't a problem. We have a virtual getter on the recipe that combines the foods from both collections:
RecipeBaseSchema.virtual('foods').get(function get() {
return this.foodsTP.concat(this.foodsAdmin);
});
However, once the foods have been added to the recipe, API requests to get the recipe are not correctly populating the food information. I have seen this document about Virtual Populate but it doesn't seem to be what I need here. My virtual here isn't just a reference to one other collection, it's actively combining the references to two other collections. When we pull this combined array of foods, we should be able to get all the food info from both collections. However, this is the error I get in the console:
If you are populating a virtual, you must set the localField and
foreignField options
Is there any way I can do this with my combined virtual array?
EDIT: Here's a simplified Recipe Schema.
const RecipeBaseSchema = new Schema({
name: {
type: String,
required: true,
},
foodsTP: [{
quantity: {
type: Number,
default: 1,
},
measureUnit: String,
food: {
type: Schema.Types.ObjectId,
ref: 'Food',
},
}],
foodsAdmin: [{
quantity: {
type: Number,
default: 1,
},
measureUnit: String,
food: {
type: Schema.Types.ObjectId,
ref: 'FoodAdmin',
},
}],
dateAdded: Date,
dateModified: Date,
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
}); // options
const RecipeUserSchema = new Schema({});
const RecipeAdminSchema = new Schema({});
API query:
export function getRecipeUser(params) {
params.populate = [
{
path: 'foods',
populate: { path: 'food' },
},
];
params.query = {
_id: params.recipeId,
};
return apiCall('get', `/recipeuser`, omit(params, ['recipeId'])).then(
recipesUser => {
console.log(recipesUser);
return recipesUser[0];
}
);
}

Related

How to filter documents using find method in mongoose based on the data from reference in documents?

I am working on e-commerce like app. I have orderItem Schema
const orderItemsSchema = mongoose.Schema(
{
order: {
type: mongoose.Schema.Types.ObjectId,
ref: 'OrderItems',
required: true,
},
product: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Products',
required: true,
},
quantity: {
type: Number,
default: 1,
},
subCost: {
type: Number,
required: true,
},
},
{
timestamps: true,
}
);
Where product schema has a field "owner" which is also a reference.
I am expecting to get orderItems based on owners of the products.
For Example: A owner want to check which products of him has been sold. So he will query orderItems to get his sold items.
I'm not an expert in mongoose, so maybe the syntax is not entirely correct:
// You get all products _id that owner currently sells
const yourOwnerObjectId = mongoose.Types.ObjectId(yourOwnerId); // Create the objectId from a string
const productsOwner = Products.find({owner: yourOwnerObjectId}).select({_id: 1})
// You get all orders that contains any of previous product _id
const orderWithProductsSold = OrderItems.find({_id: {$in: productsOwner}})
I'm not sure about what returns the first query regarding _id. Maybe you have to do some type of casting to ObjectId or whatever to perform the second query, but I think the idea is right.

How can I model my MongoDB database to allow me to easily add in new products & user interactions?

I am looking for the best way to model this scenario:
There is a ProductA model. Users can "like" or "dislike" ProductA documents. The documents are then added to an array in the User model called "likes" & "dislikes."
var UserSchema = new mongoose.Schema({
...,
likes: [{ type: mongoose.Schema.Types.ObjectId, ref: 'ProductA' }],
dislikes: [{ type: mongoose.Schema.Types.ObjectId, ref: 'ProductA' }],
...,
});
Now, I have realized that I want to add in a new Product: "ProductB." How do I restructure this database to keep this scalable and allow me to add new products? I am not sure what the best way to do this would be in MongoDB.
I believe my ideal scenario is the following psuedo-model:
var InteractionSchema= new mongoose.Schema({
product: // anonymous reference to an object
productType: //enum of which product it is
user: // user who made the interaction
interactionType: // like or dislike enum
});
I could not find any reference to how to handle anonymous references in MongoDB however. I would appreciate some advice
If I understand your requirement correctly, you can have three collections at a time:
products (contains all the products)
users (contains user information)
user_product_likes (contains user's like/dislike)
Respective schema can be,
UserInformationSchema :
{
name : {
type: String,
required: false
..
},
..
}
ProductSchema :
{
product_type : {
type: Integer,
},
...
}
InteractionSchema :
{
product_id : {
type: Integer
required: true
},
user_id : {
type: Integer
required: true
},
like : {
type: Boolean
required: false,
default:false
},
dislike : {
type: Booelan,
required: false,
default: false
}
}

Multiple subdocuments in Mongoose

Is it possible to have multiple different subdocuments is Mongoose ? I'm creating app for online tests , and each test will have questions array containing different question types , for example true/false , multiple choice, matching and etc ... I want to create different questions schemas and questions array to contain them all. For example questions: [QuestionSchema1, QuestionSchema2]. Is it possible to do so ?
Schema example with basic question type down below. And what if i want do add different type of question for this test ?
const testSchema = new mongoose.Schema({
name: {
type: String
},
level: {
type: String
},
questions: [
{
id: {
type: String
},
question: {
type: String
},
answers: [
{
id: {
type: String
},
answer: {
type: String
}
}
],
validAnswerId: {
type: String
}
}
]
});
If you really want to do this with subdocuments you can just type the the questions as an object array and put whatever you want inside it:
...
questions: [{
type: Object
}],
...
If you are fine with creating multiple collections you can use mongooses refPath to do this with stricter schemas:
...
questions: [{
question: {
type: ObjectId,
refPath: 'questions.questionType'
},
questionType: {
type: String,
enum: ['MultipleChoice', 'Matching', ...]
},
}]
...
Then you can create all the different schemas you want for your questions and add their models (like 'MultipleChoice' and 'Matching') to the questionType enum. Afterwards when you need to access the questions you just populate them with .populate('questions') on the query object.

Modelling many-to-many relationships in MongoDB

I am having difficulty coming up with schemas for a school app.
In particular, I am trying to model the relationship between the different kinds of users (e.g. instructors, teaching assistants, and students) with the courses and tutorials that they belong to.
Here are my requirements:
each course will have one to many tutorials;
each course will be taught by one to many instructors;
each course will have one to many students;
each tutorial will have one to many teaching assistants;
each instructor will teach one to many courses;
each teaching assistant may have one to many tutorials in one to many course;
each student will be enrolled in one to many courses;
each student may belong to one tutorial in the course that they are enrolled in;
So far, the following are my schemas for the user, course, and tutorial collections.
var CourseSchema = new mongoose.Schema({
name: { type: String, required: true },
code: { type: String, required: true },
tutorials: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Tutorial' }], // 1
instructors: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }], // 2
students: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }] // 3
});
var TutorialSchema = new mongoose.Schema({
number: { type: String, required: true },
teachingAsst: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }] // 4
});
var UserSchema = new mongoose.Schema({
email: { type: String, lowercase: true },
password: String,
name: {
first: { type: String, lowercase: true },
last: { type: String, lowercase: true }
},
roles: [String] // instrutor, teachingAsst, student
};
The problem lies with my requirements 5 to 8 -- which is more so the relationship from the User to the other models. What could be a good way to model these relationships?
One way, I thought of doing it e.g. req 5 was to add a field to the User schema
instructor: {
courses: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Course' }]
}
But the problem happens when I do e.g. req 6. similarly because it will complicate the queries (e.g. "find all the tutorials in a course that the user is a teaching assistant in").
teachingAsst: {
courses: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Course' }]
tutorials: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Tutorial' }]
}
In your case, Design is many to many relations. So you have two approach for your problem.
Reference Document
Embedded Document
Embedded approach will have duplicate data which is difficult to update and delete where as the read operation will be much efficient due to single query.
In case of the Reference Approach, your data will be demoralized. So, update and delete operation will be easy where as the read operation will have multiple hits on the database.
So, based on the your application requirement you should have to decide the appropriate approach.

How to model a pending trade in MongoDB?

I'm wondering what the "Mongo Way" is for modeling a pending trade of an item between two users.
I have a user collection and I have a book collection. In my app, the users will be able to propose trades to one another. Until the trade proposal is accepted, the trade needs to be stored as a pending trade in the database.
It seems to me that the best option is to have a 'trades' property on each book document modeled like this (using Mongoose):
const booksSchema = new Schema({
title: { type: String, required: true },
createdAt: { type: Date, 'default': Date.now },
updatedAt: { type: Date, 'default': Date.now },
author: { type: String, required: false},
imageUrl: { type: String, required: false},
ownerUser: { type: Schema.ObjectId, required: true },
trades: [{
fromUser: { type: Schema.ObjectId, required: true },
bookOffered: { type: Schema.ObjectId, required: true }
}]
});
The problem I see with this is that it will involve updating two documents when the trade is accepted. Assuming that the trade is accepted, the ownerUser on each document will need to be changed and the trades array will need to be cleared out.
It seems that to do this you'd want the changes to be in some sort of "Transaction" so that if one didn't update for some reason, then the other wouldn't either.
Is this a typical way to model this type of situation? What to do about the "Transaction" part of the situation?
There is no way to do a transaction including multiple documents in MongoDB.
You might consider a separate Trade collection with documents like:
{
book: ...,
ownerUser: ...,
buyerUser: ...,
status: 'pending'
dateSold: null
}
When the trade is approved you can change this document first, then update any related documents next. Should something else fail, this document would decide whether the transaction had actually happened.