How can I query by parent's field in Mongoose? - node.js

What I need is, querying the products by its' categories' locale.
Product
.find({
'category.locale' : "en"})
But it is not possible. Where am I wrong?
My Product schema:
var ProductSchema = new Schema({
code: {type : String, default : '', trim : true},
category: {type : Schema.ObjectId, ref : 'Category'}
})

In your Product schema, category contains a reference in the form of an object id.
It's just an _id, so no locale is stored with the category in the product.
There are no joins in MongoDB.
One solution could be to find all categories separately that match locale='en', then use them as a filter on the products query.
This docs page has more details: http://mongoosejs.com/docs/populate.html

Related

Am I better off splitting an elaborate mongoose model?

Assuming the case of a /login API, where, for a matching set of credentials, a user object from the collection should be returned, which approach would be more performant:
1) One model with projection queries:
var UserSchema = new Schema({
name : String,
email : String,
dob : Number,
phone : Number,
gender : String,
location : Object, // GeoJSON format {type: 'Point', geometry: { lat: '23.00', lng: '102.23' }}
followers : [UserSchema],
following : [UserSchema],
posts : [PostSchema],
groups : [GroupSchema]
// ... and so on
});
2) Split models:
var UserMinimalSchema = new Schema({
name : String,
email : String,
phone : Number,
location : Object,
});
var UserDetailSchema = new Schema({
dob : Number,
gender : String,
followers : [UserSchema],
following : [UserSchema],
posts : [PostSchema],
groups : [GroupSchema]
// ... and so on
});
Let's say:
For a logged-in user, only id, name, email, phone and location are to be returned.
The first model will use a projection query to return the properties in (1).
In the second case, only the UserMinimalSchema would be used to query the entire document.
Essentially both queries return exactly the same amount of data as mentioned in (1).
Assume that average user object is ~16MB limit and there are 1 Million records.
If someone performed such a test/links to documentation, it would be of great help to see how much it will matter to split or not.
I would not use split models:
You'll have to perform a population query everytime you want to look all of the user's data
You're increasing your data storage (you now will have to reference the user in your user details schema.
When Mongo will go do a lookup, it will find references to model instances and only extract data that you've specified in your projection query anyways. It will not load the entire object into memory unless you specify that in your query.

Mongoose findOne embedded document by _id

I am trying to push menus to the embedded document. But I am getting not defined findOne in restaurant. I just want to push some documents into the restaurant's menu categories. As you can see in the schema:
var RestaurantSchema = new mongoose.Schema({
contactTelphone : String,
address : String,
branchID : String,
email : String,
restaurantName : String,
userID : String,
menuCategory : [MenuCategorySchema]
});
var MenuCategorySchema = new mongoose.Schema({
menuCategory : String,
menuCategoryAlt : String,
sequence : Number,
menus : [MenuSchema],
restaurantInfo : { type: Schema.Types.ObjectId, ref: 'Restaurant' },
});
var MenuSchema = new mongoose.Schema({
foodName : String,
foodNameAlt : String,
picName : String,
price : String,
rating : Number,
menuSequence : Number,
category : { type: Schema.Types.ObjectId, ref: 'MenuCategory' },
});
exports.menuForMenuCategory = function(newData, callback)
{
console.log(newData);
var menuCategoryId = newData.menuCategoryId;
var restaurantId = newData.restaurantId;
var newMenu = new Menu({
foodName : newData.foodName,
foodNameAlt : newData.foodNameAlt,
picName : newData.picName,
price : newData.price,
cookingCategory : newCookingCategory,
dishSpecial : newDishSpeical
});
Restaurant.findOne( {'_id' : restaurantId }, function(err, restaurant){
if (!err) {
//Is it how to do this? It says "findOne not defined"
restaurant.findOne( "_id" : menuCategoryId, function(err, category){
category.push(newMenu);
});
}
});
}
Subdocuments have an .id() method so you can do this:
myModel.findById(myDocumentId, function (err, myDocument) {
var subDocument = myDocument.mySubdocuments.id(mySubDocumentId);
});
See http://mongoosejs.com/docs/subdocs.html for reference.
restaurant is just an object containing the result to your query. It does not have a findOne method, only Restaurant does.
Since, MenuCategory is just a sub-document of Restaurant, this will come pre-populated whenever you retrieve a restaurant. E.g.
Restaurant.findById(restaurantId, function(err, restaurant){
console.log(restaurant.menuCategory);
// Will show your array of Menu Categories
// No further queries required
});
Adding a new Menu Category is a matter of pushing a new MenuCategory instance to the menuCategory array and saving the restaurant. This means the new menu category is saved with the restaurant and not in a separate collection. For example:
Restaurant.findById(restaurantId, function(err, restaurant){
// I'm assuming your Menu Category model is just MenuCategory
var anotherMenuCategory = new MenuCategory({
menuCategory: "The name",
menuCategoryAlt: "Alternative name",
sequence: 42,
menus: []
});
restaurant.menuCategory.push(anotherMenuCategory);
restaurant.save(); // This will save the new Menu Category in your restaurant
});
Saving a menu to a menu category follows the same procedure since, according to your schema, Menus are embedded sub-documents within each MenuCategory. But note that you need to save the restaurant as its the restaurant collection that is storing all your menus and menu categories as sub-documents
Having answered your question (I hope) I should also point out your schema design should be rethought. Having sub-documents nested within sub-documents is arguably not a good idea. I think I can see where you're coming from - you're trying to implement a SQL-like many-to-one association within your schemas. But this isn't necessary with NoSQL databases - the thinking is somewhat different. Here are some links to some SO questions about efficient schema design with NoSQL databases:
MongoDB Schema Design - Many small documents or fewer large
documents?
How should I implement this schema in MongoDB?
MongoDB relationships: embed or reference?

Is it possible to condense arrays in a mongoose query?

I have the following schemas
var book_s = new Schema({
//_id: automatically generated,
pageCount: Number,
titles: [{ type: Schema.Types.ObjectId, ref: 'BookTitle' }]
});
var bookTitle_s= new Schema({
//_id: automatically generated,
language: String,
title: String
});
If I use a query like the following: Book.find({}).populate('titles').exec()
I will get a list of all titles for each book.
Is there a way to alter the query so that I can pass in a language paramater (say, English), and populate only the English titles into the Book model?
You can use a match field in your populate call to only populate those models that pass its conditions:
Book.find({}).populate({path: 'titles', {match: {language: 'English'}}}).exec()

self referencing schema in mongoose

I've been through several tutorials. I'm still wondering what the best approach for my problem would be. I got the following Schema:
var userSchema = new Schema({
_id : Number,
first_name : String,
last_name : String,
friends : [ Number ],
messages : [{
from: Number,
body : String,
date : { type : Date, default: Date.now}
}]
}, { collection : "user"});
In friends I want to store the ids of user's friends in an array. In message.from I want to store the sender's id of a message.
Ideally I want those ids in friends and message.from to be only ids of valid user entries.
Unfortunately mongodb doesn't enforce referential integrity.
This functionality must be provided by your application.
So in your case: when a user is deleted your application must also remove references to that user in all other user's friends arrays and message fields.

How can I check if the user already rated the item in NodeJS & Mongoose Application?

I have following schemas in Mongoose:
UserSchema = new Schema({
ratings = [{type : Schema.ObjectId, ref : 'Rating'}] })
ItemSchema = new Schema({
ratings = [{type : Schema.ObjectId, ref : 'Rating'}] })
Rating = new Schema({
user = [{type : Schema.ObjectId, ref : 'User'}],
venue = [{type : Schema.ObjectId, ref : 'Venue'}]
})
Are they right? I should query ratings by users, ratings for items. Also I want to check if the user has already rated an item.
Here are two of the following options you can go with.
You can maintain a separate collection Rating quite similar to what you would have done in SQL.
User: voter (reference to User object),
Item: item_voted (reference to item object),
Rating: what so ever user rated
Time: time_rated,
other fields as per your requirements...
Now maintain index over User and Item to boost up queries to check if user already rated for an item or not.
OR you could maintain an array in User collection for items rated by that user, and index over that array. Here it is what you can have your data model for User like.
items_rated: [item1, item2, item3]
other fields of User as per your requirements...
This second approach has a limitation that it fails if your BSON records exceeds 16MB limit, but in practical usage it very very less probable that you actually would hit that limit. Though nothing can be said. If your Users turn out to be maniac like some top stackoverflow users you will hit that 16MB wall :P
The way you can check if item has been rated or not (if you opt for second choice is)
if (db.user.count({item_rated: item_k, _id:'user-id-1'}) == 0) { ... }

Resources