Inheriting Mongoose schemas - node.js

I wanted to make a base 'Entity Schema', and other model entities would inherit from it.
I did it, kinda, but then strange thing happened.
Those are my schemas:
AbstractEntitySchema
MessageSchema
UserSchema
RoomSchema
File: https://github.com/mihaelamj/nodechat/blob/master/models/db/mongo/schemas.js
But in MongoDB, they are all saved in the same document store: 'entity models' not separate ones, like Messages, Users..
Did I get what was supposed to happen, but not what I wanted, separate stores?
If so I will just make a basic JSON/object as entity and append the appropriate properties for each entity. Or is there a better way?
Thanks.

Discriminators are a schema inheritance mechanism. They enable you to have multiple models with overlapping schemas on top of the same underlying MongoDB collection. rather than different documents. It seems that you misunderstand the discriminators of mongoose. Here is one article could help you to catch it correctly.
Guide to mongoose discriminators
Here are some codes sample to meet your requirement, to save the derived schema as separated documents
function AbstractEntitySchema() {
//call super
Schema.apply(this, arguments);
//add
this.add({
entityName: {type: String, required: false},
timestamp: {type: Date, default: Date.now},
index: {type: Number, required: false},
objectID: {type: String},
id: {type: String}
});
};
util.inherits(AbstractEntitySchema, Schema);
//Message Schema
var MessageSchema = new AbstractEntitySchema();
MessageSchema.add({
text: {type: String, required: true},
author: {type: String, required: true},
type: {type: String, required: false}
});
//Room Schema
var RoomSchema = new AbstractEntitySchema();
RoomSchema.add({
name: {type: String, required: true},
author: {type: String, required: false},
messages : [MessageSchema],
});
var Message = mongoose.model('Message', MessageSchema);
var Room = mongoose.model('Room', RoomSchema);
// save data to Message and Room
var aMessage = new Message({
entityName: 'message',
text: 'Hello',
author: 'mmj',
type: 'article'
});
var aRoom = new Room({
entityName: 'room',
name: 'Room1',
author: 'mmj',
type: 'article'
});
aRoom.save(function(err, myRoom) {
if (err)
console.log(err);
else
console.log("room is saved");
});
aMessage.save(function(err) {
if (err)
console.log(err);
else
console.log('user is saved');
});

If you want multiple overlapping models with different MongoDB collections, then you use this approach:
function extendSchema (Schema, definition, options) {
return new mongoose.Schema(
Object.assign({}, Schema.obj, definition),
options
);
}
Example
const extendSchema = require('mongoose-extend-schema');
const UserSchema = new mongoose.Schema({
firstname: {type: String},
lastname: {type: String}
});
const ClientSchema = extendSchema(UserSchema, {
phone: {type: String, required: true}
});
You simply extend the original object the schema was created with and recreate a new schema on its basis. This is some sort of abstract schema which you inherit from.
Check this npm module: https://www.npmjs.com/package/mongoose-extend-schema

Since ES6 this works as well:
var ImageSchema: Schema = new Schema({
...CommonMetadataSchema.obj,
src: String,
description: String,
});

Related

How to get a Mongoose model by collection name?

Collection is chats.
Model name is Chat.
import {model, Schema} from 'mongoose';
const ChatSchema = new Schema(
{
username: {type: String, required: true, index: true},
message: {type: String, required: true},
}
);
export default model('Chat', ChatSchema);
I can get the model via mongoose.model('Chat') just fine.
But, is there a way to get the Chat model by the collection name chats?
You need to set the collection name in schema too:
const ChatSchema = new Schema(
{
username: {type: String, required: true, index: true},
message: {type: String, required: true},
},
{ collection: 'chats' } // <-- here
);
Reference

Mongoose: query one to many relationship, but inverse

I am doing an app with MongoDB/Mongoose/Node.js RESTapi.
I have two models: Books and Authors.
An Author can write many books. Only a book can be written by an author.
The models are defined like this.
author model
var authorSchema = new Schema({
name: {type: String, required: true},
surname: {type: String, required: true},
book model
var bookSchema = new Schema({
isbn: {type: String, required: true},
title: {type: String, required: true},
author: {type: Schema.Types.ObjectId, ref: 'Author'},
description: {type: String}
What I am trying to do, is get the books that were written by an author. This is my API endpoint. IdAuthor is the id that mongodb gives.
on books-routes.js
localhost:3000/api/book/author/:idAuthor
router.get('/author/:idAuthor', (req, res, next) => {
let idAuthor = req.params.idAuthor;
Book.find({ author: idAuthor })
.select('isbn title author description')
.exec()
.then(bookCollection => {
const response = {
count: bookCollection.length,
books: bookCollection
}
if (response.count) {
res.status(200).json(bookCollection);
} else {
res.status(200).json({
message: 'This author does not have any book'
});
}
})
.catch(err => {
res.status(500).json({
error: err
});
});
});
Sadly, it is allways returning an empty array of books. I am sure it has something to do about "author" attribute referencing to the authors model.
I have also tried doing this:
Book.find({ author: ObjectId(idAuthor) })
without luck.
Thank you for your help!

Mongoose referring to Comment and to it child Users

I'm working on my personal project which is just simple blog and I got stuck with this problem:
I have 3 Mongoose Schemas:
Blog:
var blogSchema = new mongoose.Schema({
title: String,
image: String,
description: String,
body: String,
created: { type: Date, default: Date.now() },
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Comment"
}
]
});
Comment:
var commentSchema = new mongoose.Schema({
text: String,
author_id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
}
});
User:
var userSchema = new mongoose.Schema({
username: String,
password: String,
avatar_url: String,
email: String
});
And in each blog post I'm trying to display comments from array which is working correctly but then I don't know how to access to User model to display usernames and avatar_urls
app.get("/blogs/:id",function(req,res){
Blog.findById(req.params.id).populate("comments").populate("author_id").exec(function(err,findBlog){
if(err){
res.redirect("back");
console.log(err);
}else{
res.render("show" , {blog: findBlog});
}
})
})
you have to declare user information like username in the blog schema then only you can send request to userschema for avatar_url

how can I rewrite my mongoose query after splitting data from one model into two?

In my application I store comments. Previously my model for that looked like this:
var CommentsSchema = new Schema({
username: {type: String},
display_name: {type: String},
facebook_username: {type: String},
text_content: {type: String},
photo_content_url: {type: String},
hashtags: {type: [String]},
device_id: {type: String},
comment_date: {type: Date, default: Date.now},
friends_only: {type: Boolean, default: false}
}
Each comment - besides storing its details - had also details about the author, e.g. username, facebook_username, device_id from which the comment was added and display_name. There was also a bool flag friends_only based on which I was deciding whether that comment should be visible only to user's facebook friends or to everyone.
Construction of the node.js/mongoose query for fetching all comments looked like this:
commentsRoutes.post('/friends', function (req, res) {
var friends = req.body.friends;
var publicComments = req.body.publicComments;
var hashtagsInput = req.body.hashtags;
var startDate = req.body.startDate;
var endDate = req.body.endDate;
var query= {};
query.$and = [];
// and condition on start date
if(startDate != undefined) {
var startDate = new Date(req.param('startDate'));
var endDate = new Date(req.param('endDate'));
query.$and.push({"comment_date":{$gte: startDate}});
query.$and.push({"comment_date":{$lte: endDate}});
}
// and condition on hastags
if (hashtagsInput != undefined) {
var hashtags = hashtagsInput.split(",");
query.$and.push({"hashtags":{$in: hashtags}});
}
// creating a OR condition for facebook friends and public flag
var friend_query = {};
friend_query.$or = [];
if (friends != undefined) {
var friendsSplit = friends.split(",");
friend_query.$or.push({"facebook_username":{$in: friendsSplit}});
}
if (publicComments != undefined && publicComments === "true") {
friend_query.$or.push({friends_only: false});
}
//Merging facebook friend condition with other condition with AND operator.
query.$and.push(friend_query);
var finalQuery = Comment.find(query)
With the code above user could fetch content posted by his friends (that was set either to public or private) and all other public content (from everyone else).
I've decided to change all of that and split the data into two models. After changing it I have:
var CommentsSchema = new Schema({
user_id: {type: String, required: true, ref: 'users' },
text_content: {type: String},
photo_content_url: {type: String},
hashtags: {type: [String]},
comment_date: {type: Date, default: Date.now},
friends_only: {type: Boolean, default: false},
device_id: {type: String}
}
and
var UsersSchema = new Schema({
username: {type: String},
facebook_username: {type: String},
display_name: {type: String}
}
Now, when I want to keep the old functionality, I need to modify the code responsible for creating the query.
I could merge two queries with async, or the other way is to use mongoose .populate option. I decided to go with the second choice, so now I need to move the code responsible for creating or query to the match part of populate function:
...
var finalQuery = Comment.find(query)
finalQuery.populate({path: 'user_id',
select: 'facebook_username display_name username',
match: {
}});
I don't know how to do it. Can you help me with that?
First, i suggest you that go with a populate query, if you feel that populate won't give you a data that you need that you can run two queries and merge those results.
for populate, i found the solution from the official doc of mongoose. you can do like this.
var mongoose = require('mongoose')
, Schema = mongoose.Schema
var personSchema = Schema({
_id : Number,
name : String,
age : Number,
stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
var storySchema = Schema({
_creator : { type: Number, ref: 'Person' },
title : String,
fans : [{ type: Number, ref: 'Person' }]
});
var Story = mongoose.model('Story', storySchema);
var Person = mongoose.model('Person', personSchema);
Story
.findOne({ title: 'Once upon a timex.' })
.populate('_creator')
.exec(function (err, story) {
if (err) return handleError(err);
console.log('The creator is %s', story._creator.name);
// prints "The creator is Aaron"
});
here is doc link: http://mongoosejs.com/docs/populate.html

Schema and subdocs in mongoose.js

Learning how to use mongoose, and am trying to design reliably-variable schemas. The app would post to different services (e.g. Twitter, Tumblr) and store them in one collection ("Posts"). There would be some commonalities (e.g. when it was published, or a short summary) but other fields (like post contents, a blog posts's accompanying scripts) would vary.
What's a good way to approach this? Is there a good way to bind together different collections to avoid this in the first place? References/subschemas? Use Schema.Types.Mixed, and reinforce consistency by extending the default methods with safety checks?
// Example pseudo-functioning schemas
const tweetSchema = new mongoose.Schema({
tweetUrl: {type: string, trim: true}
length: Number
});
const blogSchema = new mongoose.Schema({
title: String,
edits: [Date],
slug: { type: String, trim: true},
body: String
});
const postSchema = new mongoose.Schema({
published: Date,
summary: String,
type: String,
contents: blogSchema || tweetSchema
});
Maybe the discriminators could be better option for your case.
Discriminators are a schema inheritance mechanism. They enable you to have multiple models with overlapping schemas on top of the same underlying MongoDB collection.
Sample codes as below
var options = {discriminatorKey: 'contents'};
const postSchema = new mongoose.Schema({
published: Date,
summary: String,
type: String,
}, options);
var Post = mongoose.model('Post', postSchema);
const tweetSchema = new mongoose.Schema({
tweetUrl: {type: string, trim: true}
length: Number
}, options);
var Tweet = Post.discriminator('Tweet', tweetSchema);
const blogSchema = new mongoose.Schema({
title: String,
edits: [Date],
slug: { type: String, trim: true},
body: String
}, options);
var Blog = Post.discriminator('Blog', blogSchema );

Resources