Is it possible to populate nested references in Mongoose? - mongodb

I'm new to MongoDB, trying to populate data from another collection into a response. The simplest example would be as follows:
const CartSchema = new Schema({
items: [{
product: { type: Schema.Types.ObjectId, ref: 'product' },
qty: Number
}],
...
});
I'm able to use .populate() when the relationships are at the root level, but in the above example, I have an array of items with their own properties, e.g. qty, plus an _id reference to a product. I would like to populate the product object into each car item, but can't seem to find any examples on what's the "right" way to do it.
Cart.findById(id)
.populate('products') // <-- something like this
.then(record => ... )
.catch(next);
I know that I could probably do a separate .find() on the products collection after locating the cart record and manually extend the initial object, but I was hoping there was a way to populate the data within the original query?

You can try this, it will work for you.
Cart.findById(id)
.populate('items.product')
.then(record => ... )
.catch(next);
.populate('items.product') will populate the product object of all the cart item present in the array.

Related

Create view from multiple collections that contains same data structure

I'm looking for a solution, using MongoDB, to regroup/aggregate/whateverthenameis a specific field present in each collection inside a new collection or view.
It is my first time using MongoDB so I'm not familiar with it. What the project I've joined has, is a MongoDB database with multiple collections that save the same kind of information but from different provider.
Each collection has the field called "legalInformation" that has a name and an identifier. What we actually have in our project is an other collection, called name-id that duplicates informations from the provider's collection legalInformation. The purpose of the name-id collection is to centralize every name-id in the app, regardless of the provider. But I think that we could create a collection/view instead of programmatically duplicates those data.
I don't know what MongoDB can offer to me to achieve this. I would like to have a way to fetch and aggregate all the legalInformation from all the providers inside on collection/view.
As anyone an idea about how I could do this ?
To illustrate, this is a representation of the DB schema:
providerA({
legalInformations: { name: ..., id: ... },
specificDataFromProviderA: { ... }
})
providerB({
legalInformations: { name: ..., id: ... },
specificDataFromProviderB: { ... }
})
providerC({
legalInformations: { name: ..., id: ... },
specificDataFromProviderC: { ... }
})
and I want a simple collection/view called legalInformation that aggregates all legalInformations
legalInformation({
name: ...,
id: ...
})
Thanks !

How should I store multiple nested arrays, populated using Mongoose populate(), in the cache using React Query?

Apologies if this is basic but I'm struggling to get my head around how to set this up.
I'm using MongoDB/Mongoose for my backend which returns a user object with nested arrays:
{
username: {
type: String,
unique: true
},
name: String,
avatar: String,
recommendations: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Media' }],
watchlist: [{
media: { type: mongoose.Schema.Types.ObjectId, ref: 'Media' },
date_added: Date,
}],
}
If a user visits their watchlist or recommendations page, the nested array gets populated, using mongoose populate(), with the referenced recommendations/watchlist items that they've added.
On the frontend I'm using React Query to handle the data returned from the server. Currently visiting either of the pages returns the whole user object, if I were to cache the entire object using the query key ['user'] the nested array not being populated will be stored as an array of reference id's. Instead I was thinking of maybe trying to update the nested arrays using setQueryData, however this doesn't work if the page is refreshed:
function useWatchlist() {
const { user } = useAuth()
const queryClient = useQueryClient()
const result = useQuery({
queryKey: ['user'],
queryFn: () =>
axios.get(`${baseUrl}/${user.profile_id}/watchlist`).then(response => response.data)
},
{onSuccess: (watchlist) => {
queryClient.setQueryData(['user'], oldUser => {
oldUser.watchlist === watchlist
})
}
})
return {...result, profile: result.data }
}
Should the recommendation/watchlist arrays instead be stored separately using different query keys - ['watchlist']/['recommendations'] or should I attempt to keep the user object structure being returned from the backend?
I would say that yes, you should store them separately. Yet, using relative keys (e.g. ['user', 'watchlist'] and ['user', 'recommendations']) as explained here under Structure:
Structure your Query Keys from most generic to most specific, with as many levels of granularity as you see fit in between
So, you can invalidate them both when the user is refetched.
When I store data such as the "watch list", which only changes when the user changes it, I put a staleTime: Infinity and use setQueryData in the onSuccess of the relevant mutation (when a user updates his watch list).
For the "recommendation list", it's different story, as it would be constantly changing by some logic in the backend. So, I would use invalidateQuery whenever the 'user' key is fetched (or expire the cache, if you update the list each certain interval), and populate it again, on the onSuccess for that query.

Mongoose product category design?

I would like to create an eCommerce type of database where I have products and categories for the products using Mongodb and Mongoose. I am thinking of having two collections, one for products and one for categories. After digging online, I think the category should be as such:
var categorySchema = {
_id: { type: String },
parent: {
type: String,
ref: 'Category'
},
ancestors: [{
type: String,
ref: 'Category'
}]
};
I would like to be able to find all the products by category. For example "find all phones." However, the categories may be renamed, updated, etc. What is the best way to implement the product collection? In SQL, a product would contain a foreign key to a category.
A code sample of inserting and finding a document would be much appreciated!
Why not keep it simple and do something like the following?
var product_Schema = {
phones:[{
price:Number,
Name:String,
}],
TV:[{
price:Number,
Name:String
}]
};
Then using projections you could easily return the products for a given key. For example:
db.collection.find({},{TV:1,_id:0},function(err,data){
if (!err) {console.log(data)}
})
Of course the correct schema design will be dependent on how you plan on querying/inserting/updating data, but with mongo keeping things simple usually pays off.

Merging two Mongoose query results, without turning them into JSON

I have two Mongoose model schemas setup so that the child documents reference the parent documents, as opposed to the Parent documents having an array of Children documents. (Its like this due to the 16MB size limit restriction on documents, I didnt want to limit the amount of relationships between Parent/Child docs):
// Parent Model Schema
const parentSchema = new Schema({
name: Schema.Types.String
})
// Child Model Schema
const childSchema = new Schema({
name: Schema.Types.String,
_partition: {
type: Schema.Types.ObjectId,
ref: 'Parent'
}
})
I want to create a static method that I can query for a Parent document, then query for any Child documents that match the parent document, then create a new item in the parent document that will reference the Children array.
Basically if the Parent document is:
{
_id: ObjectId('56ba258a98f0767514d0ee0b'),
name: 'Foo'
}
And the child documents are:
[
{
_id: ObjectId('56b9b6a86ea3a0d012bdd062'),
name: 'Name A',
_partition: ObjectId('56ba258a98f0767514d0ee0b')
},{
_id: ObjectId('56ba7e9820accb40239baedf'),
name: 'Name B',
_partition: ObjectId('56ba258a98f0767514d0ee0b')
}
]
Then id be looking to have something like:
{
_id: ObjectId('56ba258a98f0767514d0ee0b'),
name: 'Foo',
children: [
{
_id: ObjectId('56b9b6a86ea3a0d012bdd062'),
name: 'Name A',
_partition: ObjectId('56ba258a98f0767514d0ee0b')
},{
_id: ObjectId('56ba7e9820accb40239baedf'),
name: 'Name B',
_partition: ObjectId('56ba258a98f0767514d0ee0b')
}
]
}
Also, I want them to remain Mongoose documents, so I can update the Parents and Assets if I need to.
I was able to accomplish this by using toJSON on the Parent, then creating a new item that would hold the Child docs, but then obviously the Parent document isn't a real document..
The error I kept getting when I tried this was that I couldnt create a new element in the Document that wasnt in the schema.
I know I could do something like create a Virtual item that would return the promise that would query the Children, but im looking to have one static method, that returns one response (meaning they dont have to handle the virtual item as a promise or callback)
Let me know if this is possible. Thanks!
FYI, this apparently isn't possible, I've tried a few things and it doesn't seem like it will work.
Use the .populate() method of the schema.
Parent.find(query)
.populate("children")
.exec((err, items) => {
if (err) {
...
}
...
});

Meteorjs - What is the proper way to join collections on backend

I am very new to meteor.js and try to build an application with it. This time I wanted to try it over MEAN stack but at this point I am struggled to understand how to join two collection on server side...
I want very identical behaviour like mongodb populate to fetch some properties of inner document.
Let me tell you about my collection it is something like this
{
name: 'Name',
lastName: 'LastName',
anotherObject: '_id of another object'
}
and another object has some fields
{
neededField1: 'asd',
neededField2: 'zxc',
notNeededField: 'qwe'
}
So whenever I made a REST call to retrieve the first object I want it contains only neededFields of inner object so I need join them at backend but I cannot find a proper way to do it.
So far while searching it I saw some packages here is the list
Meteor Collections Helper
Publish with Relations
Reactive joins in Meteor (article)
Joins in Meteor.js (article)
Meteor Publish Composite
You will find the reywood:publish-composite useful for "joining" related collections even though SQL-like joins are not really practical in Mongo and Meteor. What you'll end up with is the appropriate documents and fields from each collection.
Using myCollection and otherCollection as pseudonyms for your two collections:
Meteor.publishComposite('pseudoJoin', {
find: function() {
return myCollection.find();
},
children: [
{
find: function(doc) {
return otherCollection.find(
{ _id: post.anotherObject },
{ fields: { neededField1: 1, neededField2: 1 } });
}
}
]
});
Note that the _id field of the otherCollection will be included automatically even though it isn't in the list of fields.
Update based on comments
Since you're only looking to return data to a REST call you don't have to worry about cursors or reactivity.
var myArray = myCollection.find().fetch();
var myOtherObject = {};
var joinedArray = myArray.map(function(el){
myOtherObject = otherCollection.findOne({ _id: el.anotherObject });
return {
_id: el._id,
name: el.name,
lastName: el.lastName,
neededField1: myOtherObject.neededField1,
neededField2: myOtherObject.neededField2
}
});
console.log(joinedArray); // These should be the droids you're looking for
This is based on a 1:1 relation. If there are many related objects then you have to repeat the parent object to the number of children.