Schema and subdocs in mongoose.js - node.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 );

Related

How to define an object in NodeJS schema?

I currently have a schema like this:
const postSchema = mongoose.Schema({
title: String,
message: String,
name: String,
creator: String,
tags: [String],
selectedFile: String,
likes: { type: [String], default: [] },
createdAt: {
type: Date,
default: new Date(),
},
})
One of the problem that I anticipate is that as the number of users grow, searching the likes array will become inefficient. Is there a way to store the likes array instead as an Object (key would be userId and value could be true) so that finding someone in the Object would become more efficient.
I am also open to hearing any other ideas that you might have.
Thanks!
I want to suggest populate() for this. From that, you can manage a large no. of user information without a problem. You can create a new schema as likes and add the id of the likes document as an id with the populate. Check the below example.
const likeSchema = mongoose.Schema({
type: [String],
default: [] },
});
const Like = mongoose.model("Like", likeSchema);
Then create the postschema like below.
const postSchema = mongoose.Schema({
title: String,
message: String,
name: String,
creator: String,
tags: [String],
selectedFile: String,
likes: {
type: mongoose.Schema.Types.String,
ref: 'Like',
},
createdAt: {
type: Date,
default: new Date(),
},
})
const Post = mongoose.model("Post", postSchema);
You can easily get all the data inside a likes document by populating when running a query like below.
const posts = await Post.findById({creator_id}).populate("likes");
//Below code will print the data of the first element of the type array of relevant likes document.
console.log(posts.likes.type[0]);
Check the populate and population sections of the mongoose documentation to learn more.

Mongoose require a field based on some enum values of another field

I have a Mongoose schema Employee. In that I want to store a field (phone number) related to office for the employee, only if he/she is eligible for office, which is only for two levels "senior" and "c-level".
The schema:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
var EmployeeSchema = new Schema({
name: String,
designation: String,
level: {
type: String,
enum: ["intern", "junior", "mid-level", "senior", "c-level"],
required: true,
},
phoneNo: { type: String, required: true },
officePhoneNo: { type: String, required: true } // How to require only if the level is senior or c-level?,
});
Appreciate your help.
Thanks
In Mongoose you can pass a function in required that can return true/false depending on some condition.
It's also possible to depend required of a field on other fields, which is level in your case. That is, you can optionally required a field. Here's how:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const levels = ["intern", "junior", "mid-level", "senior", "c-level"];
const levelsEligibleForOffice = ["senior", "c-level"];
var EmployeeSchema = new Schema({
name: String,
designation: String,
level: {type: String, enum: levels, required: true},
phoneNo: {type: String, required: true},
officePhoneNo: {type: String, required: isEligibleForOffice}
});
function isEligibleForOffice(){
if(levelsEligibleForOffice.indexOf(this.level) > -1){ //"this" contains the employee document at the time of required validation
return true;
}
return false;
}

Inheriting Mongoose schemas

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,
});

Mongoose- How to reference embed document element?

I have users.js schema with a embeded document array pets. For each user, a user can have multiple pets(usually no more than 3 I would think).
For each pet, there would be a daily chart. So it would be many daily charts for a pet. I have read with embedded documents that each array element is indexed. In daily.js, how can I reference the pet it would belong to for the populate() function?
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var userSchema = new Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
username: { type: String, required: true, unique: true },
location: String,
pets: [{ name: 'string', animalType: 'string'}], //could have more than one pet
created_at: Date,
updated_at: Date
});
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var dailySchema = new Schema({
tite: String,
_pet: { type: Number, ref: 'User.pet' }, // not sure how to reference name in user.pets[#] array
created_at: Date,
updated_at: Date
});
Quoting
Sorry to disappoint but that is an anti-pattern. Populate can't populate from another collection's subdocs - the reason why you're getting that error is that there's no model for boards.
So it may be not good patten to reference to embedded document. It could be better to separate pet from User as one schema
var PetSchema = new Schema ({
name: 'string',
animalType: 'string'
});
And the UserSchema and DailySchema will be
var userSchema = new Schema({
...
pets: [{ type: Schema.Types.ObjectId, ref: 'Pet' }], //could have more than one pet
});
var dailySchema = new Schema({
_pet: { type: Number, ref: 'Pet' }, // not sure how to reference name in user.pets[#] array
});

Error while defining mongoose schemas

I am new to mongo and mongoose. I am trying to create 3 collections Users, Articles and Comments. I want the users documents should contain articles that users have saved. The articles object should have users and comments as embedded objects and comments should have embedded user objects.
I want this to be done using the ids of the individual objects so that I can reduce the loading time, but could not find a suitable way to do so using mongoose. Please suggest how should I proceed with the Schema implementation.
var UserSchema = new mongoose.Schema({
name: String,
email: String,
profilePicture: String,
password: String,
readingList: [articleSchema]
});
var commentsSchema = new mongoose.Schema({
content: String,
votes:{
up:[UserSchema],
down:[UserSchema]
},
comments:[commentsSchema],
timestamp:Date.now
});
var articleSchema = new mongoose.Schema({
title: String,
content: String,
image: String,
votes:{
up: [UserSchema],
down: [UserSchema]
},
comments:[commentsSchema],
timestamp: Date.now
});
What you have is failing because articleSchema isn't defined when you're using it in the UserSchema. Unfortunately, you can reverse the order of defining the schema because they're dependent on each other.
I haven't actually tried this, but based on some quick googling there is a way to create the Schema first and then add the properties.
var UserSchema = new mongoose.Schema();
var CommentsSchema = new mongoose.Schema();
var ArticleSchema = new mongoose.Schema();
UserSchema.add({
name: String,
email: String,
profilePicture: String,
password: String,
readingList: [ArticleSchema]
});
CommentsSchema.add({
content: String,
votes:{
up:[UserSchema],
down:[UserSchema]
},
comments:[CommentsSchema],
timestamp:Date.now
});
ArticleSchema.add({
title: String,
content: String,
image: String,
votes:{
up: [UserSchema],
down: [UserSchema]
},
comments:[CommentsSchema],
timestamp: Date.now
});

Resources