Storing form data in MongoDB (Design Question) - node.js

Trying to design a Form Entry web app and i've rarely used MongoDB before.
Wondering if this is the best practice for storing form (document) data inside a collection.
const mongoose = require('mongoose');
// Create Schema and Model
const documentSchema = mongoose.Schema({
nps: [{ // New Promotion Submission
documentId: Number,
orgid: Number,
documentFields: [{ // Form Fields
id: Number,
dateTimeSubmitted: Date,
title: String,
productDescription: String,
productUnitSize: Number,
productCartonQty: Number
}]
}]
})
const documents = mongoose.model('documents', documentSchema);
module.exports = documents;

This is absolutely fine design, couple of things to look at:
Make sure you introduce validation on your schema fields, mirror the same validation pattern on the frontend form fields also.
Be consistent with your naming: if you use camelCase in documentId make sure to also origId
Convention says you name a model in singular form, i.e. "Document" not "documents".
If you're going to re-use the documentFields schema anywhere else in other models, make sure to store it as a separate schema and import as needed.

Related

Mongoose: ref custom field name

I am configuring Mongoose to work on an existing MongoDB, that has these two collections:
Users - with fields:
_id: ObjectId
name: String
org_id: ObjectId
Organizations - with fields:
_id: ObjectId
name: String
I want to be able to populate a User document by Organization data.
So I've created these two Models:
const userSchema = new Schema({
name: String,
org_id: {
type: Schema.Types.ObjectId,
ref: 'Organization',
},
});
const User = mongoose.model('User', userSchema);
const organizationSchema = new Schema({
name: String,
code: String,
});
const Organization = mongoose.model('Organization', organizationSchema);
Since historically the ref field from User to Organization is called org_id (instead of just organization) the population of a user by the organization code is:
const user = await User.findById('5b213a69acef4ac0f886cdbc')
.populate('org_id')
.exec();
where user.org_id will be populated by Organization data. Of course I would be happier to have organization instead of org_id in both - populate method and the path (i.e. user.organizationd).
What is the proper way to achieve it without changing the existing documents?
I could create my Schema methods (instead of populate) and aliases, but I am looking for a more generic and elegant solution.
I understood that you don't want to change the existent documents, but for me, if this name of field doesn't make more sense you need to refactor.
Change the name of the field, organization instead of org_id.
For this you can use the $rename command: MongoDB $rename
db.getCollection('users').updateMany({},{$rename: { "org_id": "organization" }});
After this you will can call .populate('organization').
If it is impossible, I believe that you will not find a solution better than aliases.
Mongoose Documentation: Aliases
I will follow along your code.looks like you applied this: mongoose.Schema=Schema
you embedded Organization model into User. first lets extract organization details for each user.
//import User and Organization models
const main=async ()=>{
const user=await User.findById("placeUserId")//we get the user
const populated=await user.populate("org_id").execPopulate()//we populated organization with all properties
console.log(populated.org_id) }
in the above code, org_id was already referenced in the userSchema. we just reached org_id property and extracted. this was simple. next without changing any code in userSchema and organizationSchema i will find which user is in which organization with virtual property.
virtual property allows us to create virtual fields in the database. it is called virtual because we do not change anything. it is just a way that to see how two models are related.
for this we are gonna add a little code on the page where you defined you defined your organizationSchema file which i assume in models/organization.js. this code will describe the virtual field. it is kinda schema of the virtual field.
//models/organization.js
organizationSchema.virtual('anyNameForField',{
ref:"User", //Organization is in relation with User
localField:"_id"//field that Organization holds as proof of relation
foreignField:"org_id"//field that User holds as proof of relation
})
now time to write the function to find the user inside the organization.
const reverse=async ()=>{
const organization=await Organization.findById("")
const populated=await organization.populate("anyNameForField").execPopulate()
console.log(populated.anyNameForField) //i gave a stupid name to bring your attention.
}
very simple and elegant!

Data modeling in mongoDB - Social-media app

I'm trying to build some sort of a social media app using node.js and mongoDB.
I have a mongoose schema for 'User', and when i render some user page on the app, it needs to also show all of his posts/images/list of friends and etc...
right now i have a mongoose schema for 'UserPost' and also for 'Image', and when i save an image for example, it has a field which keeps the username of the user who uploaded it, so when i render the user page it finds all of his images.
It is the first time i'm dealing with db's so i heard that i might have to use a reference data instead of embedded data.
can someone explain to how should i organize the data model for my app?
It's very handful to use mongoose population for handling db references
Define your schemas like these:
var mongoose = require('mongoose')
, Schema = mongoose.Schema
var userSchema = Schema({
name : String,
posts : [{ type: Schema.Types.ObjectId, ref: 'Post' }]
});
var postSchema = Schema({
title : String,
images : [{ url: String, filename: String }]
});
var User = mongoose.model('User', userSchema);
var Post = mongoose.model('Post', postSchema);
According to mongoose population docs, you can get all your data:
User.findOne().populate('posts').exec(function(error, user) {
console.log(user.posts) // there are populated posts objects inside array
})
Not sure, is it a good idea to use separated collection for image uploads, it's simpier to embed it inside Post (or User), but you may always add { type: Schema.Types.ObjectId, ref: 'Image' } for images
MongoDB is a NoSql DBMS. It means, you schould not create references between data fields, because the performance coming from NoSql will be killed: https://en.wikipedia.org/wiki/NoSQL
But, if you are really thinking you need references, checkout this: http://docs.mongodb.org/master/reference/database-references/
You can reference to a data document in the mongoDB by the _id ("Page"=>"createdBy" = "User"=>"_id" for example).
It depends of what kind of data you want to embed. MongoDB has object size limits according to the storage engine you use. Thus you should predict or estimate the the size of the object you want to embed.
See more about limits here: http://docs.mongodb.org/master/reference/limits/
See more about references here: http://docs.mongodb.org/master/reference/database-references/

Easy way to reference Documents in Mongoose

In my application I have a User Collection. Many of my other collections have an Author (an author contains ONLY the user._id and the user.name), for example my Post Collection. Since I normally only need the _id and the name to display e.g. my posts on the UI.
This works fine, and seems like a good approach, since now everytime I deal with posts I don`t have to load the whole user Object from the database - I can only load my post.author.userId/post.author.name.
Now my problem: A user changes his or her name. Obviously all my Author Objects scattered around in my database still have the old author.
Questions:
is my approuch solid, or should I only reference the userId everywhere I need it?
If I'd go for this solution I'd remove my Author Model and would need to make a User database call everytime I want to display the current Users`s name.
If I leave my Author as is, what would be a good way to implement a solution for situations like the user.name change?
I could write a service which checks every model which has Authors of the current user._id and updates them of course, but this sounds very tedious. Although I'm not sure there's a better solution.
Any pro tipps on how I should deal with problems like this in the future?
Yes, sometime database are good to recorded at modular style. But You shouldn't do separating collection for user/author such as
At that time if you use mongoose as driver you can use populate to get user schema data.
Example, I modeling user, author, post that.
var UserSchema = new mongoose.Schema({
type: { type: String, default: "user", enum: ["user", "author"], required: true },
name: { type: String },
// Author specific values
joinedAt: { type: Date }
});
var User = mongoose.model("User", UserSchema);
var PostSchema = new mongoose.Schema({
author: { type: mongoose.Scheam.Types.ObjectId, ref: "User" },
content: { type: String }
});
var Post = mongoose.model("Post", PostSchema);
In this style, Post are separated model and have to save like that. Something like if you want to query a post including author's name, you can use populate at mongoose.
Post.findOne().populate("author").exce(function(err, post) {
if(err)
// do error handling
if(post){
console.log(post.author.type) // author
}
});
One solution is save only id in Author collection, using Ref on the User collection, and populate each time to get user's name from the User collection.
var User = {
name: String,
//other fields
}
var Author = {
userId: {
type: String,
ref: "User"
}
}
Another solution is when updating name in User collection, update all names in Author collection.
I think first solution will be better.

MongoDB private fields

I have a product model, it has many fields. Some of them are dedicated to front-end application, ex:
var GameSchema = new Schema({
likes: {
type: [{
type: Schema.ObjectId,
ref: 'User'
}]
},
likes_count: {
type: Number
}
});
I don't need likes_count field in Db, but controller returns only fields that model have, so i add likes_count field to db model
exports.some_method = function(req, res){
var game = req.game;
game.likes_count = game.likes.length
res.json(game);
}
Is there a way to add extra data to db model when sending request without having them in db?
Please note, problem is not in likes_count field itself, i have different models, but the point is having extra data on db model.
For those who still interested, mongo_db mongoose(#robertklep) has virtual fields, that can be used as temporary data field, that doesn't exist in database
GameSchema.virtual('likes_count').get(function () {
return this.likes.length;
});
And note, your model must have permission for virtuals like this, so that you can use it inside controllers
var UserSchema = new Schema({
username: {
type: String
}
}, {
toObject: { virtuals: true },
toJSON: { virtuals: true }
});
"Is there a way to add extra data to db model when sending request without having them in db?"
You may be able to do so from a driver's perspective and I'll leave that to those who know abut such things. Check out the following post Mapping a private backing field with MongoDB C#.
I can answer from the MongoDB engine & server processes aspect; if you are looking for a way to flag a field in the JSON document to make it private when sent to the actual CRUD request the MongoDB engine receives then no.
However, you could intercept the JSON prior to the actual CRUD request and transform it. The JSON you are generating is not inserted until you execute one of the INSERT, Modify, or Update statements. The pseudo steps would be to generate a JSON document, send it to a broker\wrapper etc in front of MongoDB, and then transform it by removing the fields in question, then send the new object as a CRUD request to the MongoDB engine.

Mongoose Populate Returning null due to Schema design?

I'm stuck with mongoose populate returning null. I have a very similar situation to another question where it seems to we working just fine, perhaps with one important difference:
The model I'm referencing only exist as a subdocument to another model.
Example:
// The model i want to populate
Currency = new Schema({
code: String,
rate: Number
});
// The set of currencies are defined for each Tenant
// A currency belongs to one tenant, one tenant can have multiple currencies
Tenant = new Schema({
name: String,
currencies: [Currency]
});
Product = new Schema({
Name: String,
_currency: {type: ObjectId, ref: 'Currency'},
});
Customer = new Schema({
tenant: {type: ObjectId, ref: 'Tenant'},
products: [ Product ]
});
Then I export the models and use them in one of my routes where what I would like to do is something like
CustomerModel.find({}).populate('products._currency').exec(function(err, docs){
// docs[0].products[0]._currency is null (but has ObjectId if not usinn populate)
})
Which is returning null for any given product._currency but if I don't populate i get the correct ObjectId ref, which corresponds to an objectId of a currency embedded in a tenant.
I'm suspecting I need currencies to be stand-alone schema for this to work., Ie not just embedded in tenant, but that would mean I get a lot of schemas referencing each other.
Do you know if this is the case, or should my set-up work?
If this is the case, I guess I just have to bite the bullet and have multitude of collections referencing each other?
Any help or guidance appreciated!

Resources