I am designing a follow feature for my online books reading which had about 100k books. So I wonder which is a better way to use MongoDB.
Push all books followed into array and store as 1 document. When reach max document we split into another document
When the user followed a book we create a need documents
Please help me point out the Pros and Cons of each way or a new way better than both above way to reach this situation
const mongoose = require('mongoose');
const {comicConnection} = require('../db');
const Schema = mongoose.Schema;
const UserFollowsSchema = new Schema(
{
userId: { type: Schema.Types.ObjectId, ref: 'Users' },
follow: [{ type: Schema.Types.ObjectId, ref: 'Books' }],
},
{
timestamps: true,
}
);
module.exports = comicConnection.model(
'UserFollows',
UserFollowsSchema,
'UserFollows'
);
It is better to use the second method, you have better access and you can do calculations more easily.
Whenever a user follows a book, create a document that the user follows the book, and so on.
Then you can use this to find out how many books you have followed:
User.countDocuments({user:{USER-ID}});
Related
I am completely new to Node.js and Mongoose and I came up to this problem of having
Event and User and a relationship where the User can go to any Event and Event can have many participating users.
Any time the user would want to attend an Event I could add the Event id to User's array of events and simultaneously add the User to the Event's array of users but I think that I want to avoid this approach because I would store the same information twice.
On the other hand I could only store the User to the Event's array of users and use virtual mapping to retrieve all the User's events but I find this approach quite time consuming. It seems to me that I would have to go through each Event and then through all of its participating users to find out if the user really is participating or not. Anyways if there is a way to do this using the virtual function, i don't know how to map the localField: userId to the array of the Event's foreignField: participants.
What would be the correct approach to this problem?
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const eventSchema = Schema({
name: String,
user: [{ type: Schema.Types.ObjectId, ref: 'User' }]
});
const userSchema = Schema({
name: String,
event: [{ type: Schema.Types.ObjectId, ref: 'Event' }]
});
const Event = mongoose.model('Event', eventSchema);
const User = mongoose.model('User', userSchema);
use can read more here https://mongoosejs.com/docs/populate.html
I am looking for a way to populate documents beyond a document or the ref parameter.
const LibarySchema = new Schema({
books:[{
book: { type: Schema.Types.ObjectId},
bookType: { type: String }
}, { _id: false}]
});
const BookType1Schema = new Schema({
bookType: {
type: String,
default: 'lecture'
},
});
const BookType2Schema = new Schema({
bookType: {
type: String,
default: 'assingment'
},
});
This is an example of a similar project model, I am working on with 3 models. The first model hold all the information of the library books while the other models are the type of books. So during insertion of new book to the library array the bookType is added to know the collection to look for.
So my question is, is there anyway during population to check for the bookType and choose the right collection to find the book.
Also i am open for suggestion on the model.
you can just simply have a single schema for the thing you asks. Don't make unnecessary schema and make you collections bulky.. It will not be good when querying with tons of data when the system grows ...
Don't use separate schemas for storing just the types just make a single library book schema and use that to find the book you wan to find
This question is similar to: this question
Working with Mongoose, I have something like the following code. (I've prefixed the files with numbers, so I can load them in the order they appear in the 'models' directory, but still control the loading order.)
In 100-employee.server.model.js:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var EmployeeSchema = new Schema({
name: {
type: String,
default: ''
},
company: {
type: Schema.ObjectId,
ref: 'Company'
},
subordinates: [EmployeeSchema],
});
mongoose.model('Employee', EmplyeeSchema);
Then, in 200-company-server.js, I have:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var CompanySchema = new Schema({
name: {
type: String,
default: ''
},
CEO: {
type: Schema.ObjectId,
ref: 'Employee'
}
});
mongoose.model('Company', CompanySchema);
Obviously, this doesn't work, since Company is referenced before it is registered. And, loading these in the opposite order doesn't work for the same reason. What I need, is a logical data structure like:
{
name: 'Acme, Inc',
CEO: {
name: 'Karen',
subordinates: [{
name: 'Bob',
subordinates: [{
name: 'Lisa',
subordinates: []
},
name: 'Jerry',
subordinates: []
}]
}]
}
}
(I think I got all of my brackets in place. I just typed that JSON to illustrate the need.)
I could just use an ObjectId for 'company' in EmployeeSchema, but it doesn't fix the problem. I still get a complaint that Company hasn't been registered.
Someone will ask for the use case, so here it is:
I have a bunch of companies.I have a hierarchy of employees of a company. And, I've got a bunch of companies. For each company, I need to know the CEO, without having to search all of my employees for the one with no parent, that has an ObjectId ref Company, but that still runs into the same problem.
Any suggestions?
OK, I've come up with an answer, or at least a workaround. Doesn't feel very elegant. In fact I actively dislike it, but it works. Would love to see a better solution. In 100-company-server.js, define the parent entity first, like so:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var CompanySchema = new Schema({
name: {
type: String,
default: ''
},
CEO: {
name: { // This is the company name, so probably a bad example
type: String,
default: ''
}, {
shoeSize: Number
}, etc ...
}
});
mongoose.model('Company', CompanySchema);
The key thing to notice here is that instead of making CEO an ObjectId ref 'Employee' I supplied an object that uses the same properties as my Employee schema. Depending on how you want to use it, you may have to coerce that into an Employee object in the controller. Or, there might be a clever way to use a virtual to do the same thing (http://mongoosejs.com/docs/guide.html) But, the virtual would just have to be defined AFTER the EmployeeSchema and model.
In 200-employee.server.js, something like the following:
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
CompanySchema = mongoose.model('Company').schema; // so you can use this
// after your Employee
// is defined
var EmployeeSchema = new Schema({
name: {
type: String,
default: ''
},
company: {
type: Schema.ObjectId,
ref: 'Company'
},
subordinates: [], // Just an empty array. You fill it with Employees in the controller. You could use a virtual to do the same thing, probably.
});
mongoose.model('Employee', EmployeeSchema);
So, it's kind of ugly, but it works.
Key points:
In defining CompanySchema, you don't use an ObjectId ref 'Employee'.
Instead, you define an object that looks like a Company object.
Since this object doesn't have an _id, you have to work around that in the
controller. In my case, this works fine. But, for many/most use cases, this
probably would not. You'd have to work around this with clever virtuals
and/or the controller.
Use a simple array, without type, to store your children (suborinates in
this example.
The array works just fine, and returns objects just as if you used an array
of ObjectId ref Employee.
Making the Company refer to an Employee (which hasn't been defined yet), you
have to convert a generic object into an Employee object. But, there are
many ways to do that.
There is, undoubtedly, a better way to do this.
At the moment I am looking at mongoDB. I try to implement a simple one-to-many relation using nodejs and mongoose:
Model/Schema User:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var UserSchema = new Schema({
name: String
});
module.exports = mongoose.model('User', UserSchema);
Model/Schema Article:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var ArticleSchema = new Schema({
name: {
type: String,
required: true
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
module.exports = mongoose.model('Article', ArticleSchema);
So my question, now:
How can I get all Users including its Articles?
Do I really have to add a ref to my UserScheme, too? What is the best-practice in an one-to-many relation like this? Is there something like a join in mongodb?
One User has many Articles - an Article belongs to one User.
Calling something like /user/:user_id , I want to receive the user with _id=user_id containing all of his articles.
That is the most horrible idea, for various reasons.
First, there is a 16MB BSON document size limit. You simply can not put more into a document. Embedding documents is rather suited for "One-to-(VERY-)Few" relationships than for a "One-to-Many".
As for the use case: What is your question here? It is
For a given user, what are the articles?
REST wise, you should only return the articles when /users/:id/articles is GETed and the articles (and only the articles) should be returned as a JSON array.
So, your model seems to be natural. As for the user:
{
_id: theObjectId,
username: someString
…
}
and an article should look like this:
{
_id: articleIdOrSlugOrWhatever,
authors: [theObjectId],
// or author: theObjectId
retention: someISODate,
published: someOtherISODate
}
So when your REST service is called for /users/:id you'd simply look up
var user = db.users.findOne({_id:id})
And when /users/:id/articles is called, you'd lookup
var articles = db.articles.find({author:id})
Problem solved in a scalable way, adhering to REST principles.
Using node.js, mongoose (3.5+), mongodb. Have got two collections in the DB:
var AuthorSchema = new mongoose.Schema({
name: { type: String },
});
var StorySchema = new mongoose.Schema({
title: { type: String },
author: { type: type: Schema.Types.ObjectId },
});
What I would like to do is retrieve an author and populate it with a subcollection (say, "stories") that is looked up from Story and match the author. Yes, much like a SQL join.
All of the examples out there work on the AuthorSchema having an array of objectids that reference StorySchema objects - that works fine. But I want to go the opposite direction; partly due to minimizing insert/updates. If I follow the example, adding a new store requires adding a new Story document and updating the Author. I want to just insert a new Story that references the Author.
I suspect that populate() is the right way to go, but can't get it to work. I'm doing something like this:
Author.find({name: 'Asimov').populate({
path: 'stories',
model: 'Story',
match: {'author': this['_id']},
}).exec(function(err, authors) {
console.log(authors);
})
But this doesn't return any stories member in the returned authors. Is this not a populate() solution? Do I really need to structure the schemas differently? Or is there some other feature of mongoose/mongo that would do what I'm looking for.
In the story schema, do this:
author: { type: type: Schema.Types.ObjectId, ref:'Author' }, //or whatever the model name is
then you can run
Story.find({}).populate('author').exec(function(err,stories) {...});