mongoose-paginate sorting by referenced document - node.js

I would like to sort my Group-Model by the name of the departure_country with mongoose-paginate and NodeJS.
My Group-Schema:
var GroupSchema = new Schema({
name: String,
flight_date: Date,
....
departure_country: {type: Schema.ObjectId, ref: 'Country'},
....
});
The Country-Schema:
var CountrySchema = new Schema({
name: String,
code: String,
});
Using the sortBy-option of mongoose-paginate sorts by the country's _ids and I don't know how to tell it to sort by the name.

how about making the country code the _id of Country-Schema?
then departure_country: {type: String, ref: 'Country'}
That might actually be a better design. Country-Schemas _id would be small, and country codes shouldn't have collisions. Also in GroupSchema you'd use less space to store the "foreign key" departure_country.
then you'd be able to sort by departure_country and there's a chance the sort would also be valid for country name.

Related

How do I find a specific item in data array from Jade?

Not sure if this is good/bad practice but I have successfully serverd two sets of data (mongodb collections) to a jade page.
examples of these data mongoose Schemas are the following:
...
var personSchema = new Schema({
firstName: String,
lastName: String,
email: String,
city: String,
state: String,
zip: Number
});
module.exports = mongoose.model('person', personSchema);
and
...
var personQuoteSchema = new Schema({
_person : {
type: Schema.ObjectId,
ref: 'person'
},
quote: Number
});
module.exports = mongoose.model('quote', personQuoteSchema);
I have successfully served both collections to the jade tpls and they work fine. But now i'm confused for how to do what i'm trying to do:
I am building a page for the quotes that pulls in data for the person the quote is for. In the document for a single quote from the quotes collection, it has the ObjectId of the person from the persons collection.
This is what I have so far, but i'm stuck:
each quote in quotes
ul
li #{quote.quote}
li #{quote._person.firstName} #{quote._person.lastName}
Because it doesn't work. I need to find a way to go into the person document referenced in the quote document and get the firstName and lastName.
This is my first time asking for help on stackoverflow, please let me know if i'm missing anything that could help you help me.
The problem is that there's an extra step beyond just adding the ObjectId and ref into the Schema. As pointed out by #Molda, you have to use the .populate() function to make this work.
Once I saw the comment, I looked up the .populate() function, and this page does a great job explaining it.
So when I make the call, it has to look something like:
quote.find({}).populate({
path: '_person',
model: 'person'
}).exec(callback);

Best way to structure my mongoose schema: embedded array , populate, subdocument?

Here is my current Schema
Brand:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var BrandSchema = new mongoose.Schema({
name: { type: String, lowercase: true , unique: true, required: true },
photo: { type: String , trim: true},
email: { type: String , lowercase: true},
year: { type: Number},
timestamp: { type : Date, default: Date.now },
description: { type: String},
location: { },
social: {
website: {type: String},
facebook: {type: String },
twitter: {type: String },
instagram: {type: String }
}
});
Style:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var StyleSchema = new mongoose.Schema({
name: { type: String, lowercase: true , required: true},
});
Product
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ProductSchema = new mongoose.Schema({
name: { type: String, lowercase: true , required: true},
brandId : {type: mongoose.Schema.ObjectId, ref: 'Brand'},
styleId: {type: mongoose.Schema.ObjectId, ref: 'Style'},
year: { type: Number },
avgRating: {type: Number}
});
Post:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var PostSchema = new mongoose.Schema({
rating: { type: Number},
upVote: {type: Number},
brandId : {type: mongoose.Schema.ObjectId, ref: 'Brand'},
comment: {type: String},
productId: {type: mongoose.Schema.ObjectId, ref: 'Style'},
styleId: {type: mongoose.Schema.ObjectId, ref: 'Style'},
photo: {type: String}
});
I'm currently making use of the mongoose populate feature:
exports.productsByBrand = function(req, res){
Product.find({product: req.params.id}).populate('style').exec(function(err, products){
res.send({products:products});
});
};
This works, however, being a noob --- i've started reading about performance issues with the mongoose populate, since it's really just adding an additional query.
For my post , especially, it seems that could be taxing. The intent for the post is to be a live twitter / instagram-like feed. It seems that could be a lot of queries, which could greatly slow my app down.
also, I want to be able to search prodcuts / post / brand by fields at some point.
Should i consider nesting / embedding this data (products nested / embedded in brands)?
What's the most efficient schema design or would my setup be alright -- given what i've specified I want to use it for?
User story:
There will be an Admin User.
The admin will be able to add the Brand with the specific fields in the Brand Schema.
Brands will have associated Products, each Product will have a Style / category.
Search:
Users will be able to search Brands by name and location (i'm looking into doing this with angular filtering / tags).
Users will be able to search Products by fields (name, style, etc).
Users will be able to search Post by Brand Product and Style.
Post:
Users will be able to Post into a feed. When making a Post, they will choose a Brand and a Product to associate the Post with. The Post will display the Brand name, Product name, and Style -- along with newly entered Post fields (photo, comment, and rating).
Other users can click on the Brand name to link to the Brand show page. They can click on the Product name to link to a Product show page.
Product show page:
Will show Product fields from the above Schema -- including associated Style name from Style schema. It will also display Post pertaining to the specific Product.
Brand show page:
Will simply show Brand fields and associated products.
My main worry is the Post, which will have to populate / query for the Brand , Product, and Style within a feed.
Again, I'm contemplating if I should embed the Products within the Brand -- then would I be able to associate the Brand Product and Style with the Post for later queries? Or, possibly $lookup or other aggregate features.
Mongodb itself does not support joins. So, mongoose populate is an attempt at external reference resolution. The thing with mongodb is that you need to design your data so that:
most of you queries need not to refer multiple collections.
after getting data from query, you need not to transform it too much.
Consider the entities involved, and their relations:
Brand is brand. Doesn't depend on anything else.
Every Product belongs to a Brand.
Every Product is associated with a Style.
Every Post is associated with a Product.
Indirectly, every Post is associated to a Brand and Style, via product.
Now about the use cases:
Refer: If you are looking up one entity by id, then fetching 1-2 related entities is not really a big overhead.
List: It is when you have to return a large set of objects and each object needs an additional query to get associated objects. This is a performance issue. This is usually reduced by processing "pages" of result set at a time, say 20 records per request. Lets suppose you query 20 products (using skip and limit). For 20 products you extract two id arrays, one of referred styles, and other of referred brands. You do 2 additional queries using $in:[ids], get brands and styles object and place them in result set. That's 3 queries per page. Users can request next page as they scroll down, and so on.
Search: You want to search for products, but also want to specify brand name and style name. Sadly, product model only holds ids for style and brand. Same issue with searching Posts with brand and product. Popular solution is to maintain a separate "search index", a sort of table, that stores data exactly the way it will be searched for, with all searchable fields (like brand name, style name) at one place. Maintaining such search collections in mongodb manually can be a pain. This is where ElasticSearch comes in. Since you are already using mongoose, you can simply add mongoosastic to your models. ElasticSearch's search capabilities are far greater than a DB Storage engine will offer you.
Extra Speed: There is still some room for speeding things up: Caching. Attach mongoose-redis-cache and have frequent repeated queries served, in-memory from Redis, reducing load on mongodb.
Twitter like Feeds: Now if all Posts are public then listing them up for users in chronological order is a trivial query. However things change when you introduce "social networking" features. Then you need to list "activity feeds" of friends and followers. There's some wisdom about social inboxes and Fan-out lists in mongodb blog.
Moral of the story is that not all use cases have only "db schema query" solutions. Scalability is one of such cases. That's why other tools exist.

Mongoose Schema: Using nested arrays to store data that will be queried for

I am using mongodb and mongoose to store data for a group of students.
They attend a camp session once or twice a year in grades K & 1, and I mainly want to keep track of the skills that the students achieved mastery in, and the dates that they achieved it, as well as some personal, session-specific data.
Here is my schema:
var location = ["DJ", "MLK"];
var days = ["MoWe", "TuTh"];
var grades = ["K", "First"];
var skills = ["FK", "BC", "BK", "IK", "PS", "SLF", "RBC", "RBK", "RIK", "FS", "BS"];
var achievements = ["Red", "Yellow", "Green"];
var studentSchema = new Schema({
name: String,
sessionInfo: [{
sessionName: String,
location: {type: String, enum: locations},
instructor: String,
class: {school: String, teacher: String},
grade: {type: String, enum : grades},
age: {type: Number},
days: {type: String, enum : days}}],
skills: [{skill: {type: String, enum: skills}, date: Date}],
absences: [{date: Date, reason: String}],
achievements: [{achievement: {type: String, enum: achievements}}],
notes: [{instructorName: String, note: String}]
});
Because the students will at most attend 3 or 4 sessions, I thought it made sense to store the session info in it's own array than to try to store each student-session in it's own separate document.
My question is whether or not this will significantly impact the response time for querying the database if I am going to be regularly querying for students based on skills attained, grade, school, and location.
Note that none of the arrays within the session info will be growing without bound.
As mongodb stores indexes in RAM, if the size of your collection is large then performance can be lower in case of creating separate collection for sessionInfo where every session will have its own already indexed ObjectId (Assuming total number of sessions for all the students will be very large).
It is generally faster to query indexed ObjectId than querying for a primary key in an Array of objects. But as you said sessionInfo array won't be having more than 3 to 4 records, it is better to keep sessionInfo an array. Also you can create index on a particular property of session object.

How do I reference ObjectID in another collection in MongoDB & Node?

I'm fairly new to this, so bear with me, however I have 2 collections. One called photos and another called users.
In Node, I am taking the data and putting it into my MongoDB using mongoose. I've got this working fine with my Schema:
var picSchema = new Schema({
uid: String,
pid: String,
oFile: String
});
What I want to do though is for the uid, I want to add the ObjectId for the user uploading the photo. I can pass this as a String, however I thought that I would have had to have the field set as an ObjectId, but seems I cannot do this?
Unless I am missing something, I might as well just add the username in there and use that as a reference?
Use mongoose.Schema.Types.ObjectId to populate the field with an ObjectId. In this case, you would use User (or whatever the name of your User schema is).
var picSchema = new Schema({
uid: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
pid: String,
oFile: String
});
Further to this, you can also use the Mongoose method Populate if you wish to expand the User document within a Pic document. For example:
Pic.find({})
.populate('uid')
.exec(function(err, pic) {
console.log(pic);
// do something
});

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()

Resources