NodeJS Mongo - Mongoose - Dynamic collection name - node.js

So, I want to create a client side based paritioning schema, where I set the collection name as function(), my pseudo code is something like that:
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
var ConvForUserSchema = new Schema({
user_id: Number,
conv_hash: String,
archived: Boolean,
unread: Boolean
}, function CollectionName() {
return (this.user_id % 10000);
});
Is this in any way possible through moongose such that both read and writes will work as expected?

Hello you just need to declare schema model with your dinamically name, like this:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
// our schema
function dynamicSchema(prefix){
var addressSchema = new Schema({
dir : {type : String, required : true}, //los 2 nombres delimitados por coma (,) ej. Alberto,Andres
city : {type : String, required: true}, //la misma estructura que para los nombres ej. Acosta, Arteta
postal : {type : Number, required : true},
_home_type : {type : Schema.Types.ObjectId, required : true, ref : prefix + '.home_type'},
state : {type : String, required : true},
telefono : String,
registered : {type : Date, default: Date.now }
});
return mongoose.model(prefix + '.address', addressSchema);
}
//no we export dynamicSchema function
module.exports = dynamicSchema;
so in your code anywhere you can do this:
var userAdress = require('address.js')(id_user);
var usrAdrs1 = new userAddress({...});
userAdrs1.save();
Now go to your mongo shell & list collections (use mydb then show collections), you will see a new collection for address with uid prefix. In this way mongoose will create a new one collection address for each different user uid.

Use the function to get the model dynamically.
/*
* Define Schemas as you used to
*/
const ConvForUserSchema = new Schema({
user_id: Number,
conv_hash: String,
archived: Boolean,
unread: Boolean
},{
versionKey : false,
strict: false
});
/*
* Define the dynamic function
*/
const models = {};
const getModel = (collectionName) => {
if( !(collectionName in models) ){
models[collectionName] = connection.model(
collectionName, ConvForUserSchema, collectionName
);
}
return models[collectionName];
};
Then get the dynamic model using the function
const result = getModel("YourCollectionName").findOne({})

Collection name logic is hard coded all over the Moongose codebase such that client side partitioning is just not possible as things stands now.
My solution was to work directly with the mongo driver -
https://github.com/mongodb/node-mongodb-native
This proved great, the flexibility working with the driver directly allows for everything required and the Moongose overhead does not seem to add much in any case.

Implemented:
//Require Mongoose
const mongoose = require('mongoose');
const moment = require('moment');
//Define a schema
const Schema = mongoose.Schema;
const EntranceModelSchema = new Schema({
name: String,
birthday: Date,
gender: String,
phoneNumber: {type: String, require: true},
email: String,
address: String,
addressReference: String,
addressLatitude: {type: Number, require: true},
addressLongitude: {type: Number, require: true},
vehicleReference: String,
date: Date
});
//Export function to create "SomeModel" model class
module.exports = function(){
let dateSuffix = moment().format('MMMDoYYYY');
const collectionName = `Entrance${dateSuffix}`;
return mongoose.model(collectionName, EntranceModelSchema);
};

Gomosoft's solution works. It needs some amends but the idea works nicely.
The above solution works only the first time. If you are trying to send a second request to the same collection, it will throw an error for trying to overwrite the model that is already defined. So I had to tweak it as follows:
var Rating = require('./app/models/rating');
var myRating;
router.route('/ratings/:user_id')
.post(function(req,res){
var user_id = req.params.user_id;
if(myRating == undefined){
myRating = Rating(user_id);
}
...
rating.save(...);
});
Because I'm checking if myRating is undefined, it will create this reference only once. So no errors will occur.
Hope this helps.

I am adding to the answer by Javier Gomez, to give a solution to Exis Zang's "OverwriteModelError: Cannot overwrite xxx model once compiled" problem. The Schema model file can store an array of the dynamic models based on the Singleton pattern. If that model already exists return it, otherwise create it with new, store it and return it:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
const Addresses = {}
// our schema
function DynamicSchema(prefix){
var addressSchema = new Schema({
dir : {type : String, required : true}, //los 2 nombres delimitados por coma (,) ej. Alberto,Andres
city : {type : String, required: true}, //la misma estructura que para los nombres ej. Acosta, Arteta
postal : {type : Number, required : true},
_home_type : {type : Schema.Types.ObjectId, required : true, ref : prefix + '.home_type'},
state : {type : String, required : true},
telefono : String,
registered : {type : Date, default: Date.now }
});
return mongoose.model(prefix + '.address', addressSchema);
}
// this function will store the model in the Addresses object
// on subsequent calls, if it exists, it will return it from the array
function getAddressModel(prefix) {
if (!Addresses[prefix]) {
Addresses[prefix] = new DynamicSchema(prefix)
}
return Addresses[prefix]
}
//now we export getAddressModel function
module.exports = getAddressModel;

To Create a dynamic collection follow the below steps,
const mongoose = require('mongoose');
function createCompanyDynamicSchema(prefix) {
let collectionName = prefix + '_company';
companySchema = new mongoose.Schema(
{
name: { type: String },
enabled: { type: Number, default: 1 },
},
{ timestamps: true },
{ versionKey: false },
{ strict: false }
);
collectionName = mongoose.model(collectionName, companySchema);
return collectionName;
}
module.exports = { createCompanyDynamicSchema };
To call this method from any file,
let companySchema = require('./schema');
_this.createCompany = function () {
return new Promise(async (resolve, reject) => {
let companyCollection = companySchema.createCompanyDynamicSchema('IO');
let addr = new companyCollection({ first_name: 'test' });
addr.save();
});
};
To query from dynamically created collection,
_this.getCompany = function () {
return new Promise(async (resolve, reject) => {
let companyCollection = companySchema.createCompanyDynamicSchema('IO');
let data = await companyCollection.model('IO_users').find();
console.log(data);
});
};

Related

"Item" schema is not found even i required that model.(mongoose,Node.js)

I am trying to run hook of 'remove' in label schema."Item.find is not a function" this is the error which I got.
But 'Item' schema is not found.This "require" works fine on other models.Here it is not working.
That error might be occurs in var Item = require('./Item');
This is "Label" schema.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var Item = require('./Item');
var List = require('./List');
// created schema for Item
var labelSchema = new Schema({
label_title: {type: String},
color_id:{type: String, required: true},
created_at: Date,
updated_at: Date,
marked : { Boolean ,default:false },
_board: {type: Schema.Types.ObjectId, ref:'Board'}
});
//-----------------Hook--
labelSchema.post('remove',function(doc,next){
Label.remove({_id:this._id}).then(function(data){
console.log("successfuly removed label");
}).catch(function(error){console.log(error);
})
//-----remove label from all items
// console.log("ITEM", Item);
Item.find({},function(error,items){
items.forEach(function(item){
Item.findByIdAndUpdate(item._id,
{ "$pullAll": { "label" : [{ "label_id" : this._id }]} },function(error,result){
if(result)console.log("successfully removed");
else console.log("not removed");
})
})
})
});
var Label = mongoose.model('Label', labelSchema);
module.exports = Label;
What should I do?

Mongoose Populate Cast Error

I have the follwing schemas:
// online.js
var mongoose = require('mongoose');
// Online Friends
var onlineSchema = mongoose.Schema({
userid:{ type: Number, ref:'Player' }
},{timestamps : true});
// Export
module.exports = mongoose.model('Online', onlineSchema);
//player.js
var mongoose = require('mongoose');
// define the schema for player data
var playerSchema = mongoose.Schema({
userid : { type: Number, required: true,unique:true, default: 0 },
firstname : { type: String },
nick : {type: String, required: true},
lastname : {type: String},
lastupdate: {type: Date, default: Date.now}
},{timestamps : true});
// create the model and expose it to our app
module.exports = mongoose.model('Player', playerSchema);
//main.js
...
const Online = require('./config/models/online.js');
const Player = require('./config/models/player.js');
Online.find({userid:3441341}).populate('userid').exec(function(err,result){
if (err) {
console.log(err);
} else {
console.log(result);
}
});
...
I want to search Online for a userid and then print the name of the user, not exactly sure what I am doing wrong.
This is the error I'm getting:
MongooseError: Cast to ObjectId failed for value "3441341" at path "_id"
What is the proper way to achieve this ?
Change the userid type from Number to mongoose.Schema.ObjectId and it should work. It basically says you are trying to cast a number to Object Id. Pass the ids as strings.
In NoSQL DBS your documents must have _id field. Do you have a User model or do you use Player for the job? If you use Player as User model so change user_id to _id

TypeError object is not a function

I'm developing an application in node.js with MongoDB
I have to files:
product.js and displaycost.js
this is product .js:
var mongoose = require('mongoose');
var Category = require('./category');
var productSchema = {
name:{type: String, require: true},
//Pictures mus start with "http://"
pictures:[{type:String, match: /^http:\/\//i}],
price:{
amount:{type: Number, required: true},
//ONly 3 supported currencies for now
currency:{
type: String,
enum:['USD','EUR','GBP'],
required: true
}
},
category: Category.categorySchema
};
var schema = new mongoose.Schema(productSchema);
var currencySymbols ={
'USD': '$',
'EUR':'€',
'GBP':'£'
};
/*
*
*/
schema.virtual('displayPrice').get(function(){
return currencySymbols[this.price.currency] +
'' + this.price.amount;
});
schema.set('toObject', {virtuals:true});
schema.set('toJSON', {virtuals:true});
module.exports = schema;
What I need is create a record with "productSchema"
I tried with this:
var Product = require('./product.js');
var p = new Product({
name: 'test',
price:{
amount : 5,
currency: 'USD'
},
category: {
name: 'test'
}
});
console.log(p.displayPrice); // "$5"
p.price.amount = 20;
console.log(p.displayPrice); //" $20"
//{... "displayPrice: "$20",...}
console.log(JSON.stringify(p));
var obj = p.toObject();
But when I run the displaycost.js throw me an error at the word "new"
and writes "TypeError: object is not a function"
I don't know why is happening that. Thank you.
you missing export mongoose model with model name.
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
Category = require('./category');
var productSchema = new Schema({
name: {type: String, require: true},
pictures: [{type: String, match: /^http:\/\//i}],
price: {
amount: {type: Number, required: true},
currency: {
type: String,
enum: ['USD', 'EUR', 'GBP'],
required: true
}
},
category: Category
});
var currencySymbols = {
'USD': '$',
'EUR': '€',
'GBP': '£'
};
productSchema.virtual('displayPrice').get(function () {
return currencySymbols[this.price.currency] +
'' + this.price.amount;
});
productSchema.set('toObject', {virtuals: true});
productSchema.set('toJSON', {virtuals: true});
module.exports = mongoose.model('Product', productSchema);
"new" can only be called on a function. not an object.
In program.js you are creating a new instance of the mongoose schema.
var schema = new mongoose.Schema(productSchema);
... more code ...
return schema;
In your other js file you require product. At this point in time product.js has returned an object to you. A new mongoose schema. It is an object which you are refering to as Product.
You then try to create a new instance of it by calling new on the product OBJECT. New cannot be called on an object literal. It can only be called on a function.
I don't believe you need to make a new instance of schema within product.js, just return the function that creates the schema. Then call new on it when you require it like you are in your second file.

Make all fields required in Mongoose

Mongoose seems to default to make all fields not required. Is there any way to make all the fields required without changing each of:
Dimension = mongoose.Schema(
name: String
value: String
)
to
Dimension = mongoose.Schema(
name:
type: String
required: true
value:
type: String
required: true
)
It'll get really ugly since I have a lot of these.
You could do something like:
var schema = {
name: { type: String},
value: { type: String}
};
var requiredAttrs = ['name', 'value'];
for (attr in requiredAttrs) { schema[attr].required = true; }
var Dimension = mongoose.schema(schema);
or for all attrs (using underscore, which is awesome):
var schema = {
name: { type: String},
value: { type: String}
};
_.each(_.keys(schema), function (attr) { schema[attr].required = true; });
var Dimension = mongoose.schema(schema);
I ended up doing this:
r_string =
type: String
required: true
r_number =
type: Number
required: true
and on for the other data types.
All fields properties are in schema.paths[attribute] or schema.path(attribute);
One proper way to go : define when a field is NOT required,
Schema = mongoose.Schema;
var Myschema = new Schema({
name : { type:String },
type : { type:String, required:false }
})
and make them all required by default :
function AllFieldsRequiredByDefautlt(schema) {
for (var i in schema.paths) {
var attribute = schema.paths[i]
if (attribute.isRequired == undefined) {
attribute.required(true);
}
}
}
AllFieldsRequiredByDefautlt(Myschema)
The underscore way :
_=require('underscore')
_.each(_.keys(schema.paths), function (attr) {
if (schema.path(attr).isRequired == undefined) {
schema.path(attr).required(true);
}
})
Test it :
MyTable = mongoose.model('Myschema', Myschema);
t = new MyTable()
t.save()
Well you could write a mongoose schema plugin function that walked the schema object and adjusted it to make each field required. Then you'd just need 1 line per schema: Dimension.plugin(allRequired).
Mongoose didn't provide the method of setting all fields, but you could do it recursively.
Like Peter mentioned, you could pluginize it in order to reuse the code.
Recursively setting:
// game.model.js
var fields = require('./fields');
var Game = new Schema({ ... });
for(var p in Game.paths){
Game.path(p).required(true);
}
Pluginized:
// fields.js
module.exports = function (schema, options) {
if (options && options.required) {
for(var p in schema.paths){
schema.path(p).required(true);
}
}
}
// game.model.js
var fields = require('./fields');
var Game = new Schema({ ... });
Game.plugin(fields, { required: true });
I'm not sure if there's an easier way to do it in Mongoose, but I would do the following in your IDE/editor:
List out your fields as you would normally:
Dimension = mongoose.Schema(
name: String
value: String
)
Then do a find and replace on String and replace it with {type: String, required: true}, Giving you:
Dimension = mongoose.Schema(
name: {type: String, required: true},
value: {type: String, required: true},
)
Then do the same for Number and other types.
Building on the previous answers, the module below will make fields required by default. The previous answers did not recurse nested objects/arrays.
Usage:
const rSchema = require("rschema");
var mySchema = new rSchema({
request:{
key:String,
value:String
},
responses:[{
key:String,
value:String
}]
});
Node module:
const Schema = require("mongoose").Schema;
//Extends Mongoose Schema to require all fields by default
module.exports = function(data){
//Recursive
var makeRequired = function(schema){
for (var i in schema.paths) {
var attribute = schema.paths[i];
if (attribute.isRequired == undefined) {
attribute.required(true);
}
if (attribute.schema){
makeRequired(attribute.schema);
}
}
};
var schema = new Schema(data);
makeRequired(schema);
return schema;
};

How to set ObjectId as a data type in mongoose

Using node.js, mongodb on mongoHQ and mongoose. I'm setting a schema for Categories. I would like to use the document ObjectId as my categoryId.
var mongoose = require('mongoose');
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var Schema_Category = new Schema({
categoryId : ObjectId,
title : String,
sortIndex : String
});
I then run
var Category = mongoose.model('Schema_Category');
var category = new Category();
category.title = "Bicycles";
category.sortIndex = "3";
category.save(function(err) {
if (err) { throw err; }
console.log('saved');
mongoose.disconnect();
});
Notice that I don't provide a value for categoryId. I assumed mongoose will use the schema to generate it but the document has the usual "_id" and not "categoryId". What am I doing wrong?
Unlike traditional RBDMs, mongoDB doesn't allow you to define any random field as the primary key, the _id field MUST exist for all standard documents.
For this reason, it doesn't make sense to create a separate uuid field.
In mongoose, the ObjectId type is used not to create a new uuid, rather it is mostly used to reference other documents.
Here is an example:
var mongoose = require('mongoose');
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var Schema_Product = new Schema({
categoryId : ObjectId, // a product references a category _id with type ObjectId
title : String,
price : Number
});
As you can see, it wouldn't make much sense to populate categoryId with a ObjectId.
However, if you do want a nicely named uuid field, mongoose provides virtual properties that allow you to proxy (reference) a field.
Check it out:
var mongoose = require('mongoose');
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var Schema_Category = new Schema({
title : String,
sortIndex : String
});
Schema_Category.virtual('categoryId').get(function() {
return this._id;
});
So now, whenever you call category.categoryId, mongoose just returns the _id instead.
You can also create a "set" method so that you can set virtual properties, check out this link
for more info
I was looking for a different answer for the question title, so maybe other people will be too.
To set type as an ObjectId (so you may reference author as the author of book, for example), you may do like:
const Book = mongoose.model('Book', {
author: {
type: mongoose.Schema.Types.ObjectId, // here you set the author ID
// from the Author colection,
// so you can reference it
required: true
},
title: {
type: String,
required: true
}
});
My solution on using ObjectId
// usermodel.js
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const ObjectId = Schema.Types.ObjectId
let UserSchema = new Schema({
username: {
type: String
},
events: [{
type: ObjectId,
ref: 'Event' // Reference to some EventSchema
}]
})
UserSchema.set('autoIndex', true)
module.exports = mongoose.model('User', UserSchema)
Using mongoose's populate method
// controller.js
const mongoose = require('mongoose')
const User = require('./usermodel.js')
let query = User.findOne({ name: "Person" })
query.exec((err, user) => {
if (err) {
console.log(err)
}
user.events = events
// user.events is now an array of events
})
The solution provided by #dex worked for me. But I want to add something else that also worked for me: Use
let UserSchema = new Schema({
username: {
type: String
},
events: [{
type: ObjectId,
ref: 'Event' // Reference to some EventSchema
}]
})
if what you want to create is an Array reference. But if what you want is an Object reference, which is what I think you might be looking for anyway, remove the brackets from the value prop, like this:
let UserSchema = new Schema({
username: {
type: String
},
events: {
type: ObjectId,
ref: 'Event' // Reference to some EventSchema
}
})
Look at the 2 snippets well. In the second case, the value prop of key events does not have brackets over the object def.
You can directly define the ObjectId
var Schema = new mongoose.Schema({
categoryId : mongoose.Schema.Types.ObjectId,
title : String,
sortIndex : String
})
Note: You need to import the mongoose module
Another possible way is to transform your _id to something you like.
Here's an example with a Page-Document that I implemented for a project:
interface PageAttrs {
label: string
// ...
}
const pageSchema = new mongoose.Schema<PageDoc>(
{
label: {
type: String,
required: true
}
// ...
},
{
toJSON: {
transform(doc, ret) {
// modify ret directly
ret.id = ret._id
delete ret._id
}
}
}
)
pageSchema.statics.build = (attrs: PageAttrs) => {
return new Page({
label: attrs.label,
// ...
})
}
const Page = mongoose.model<PageDoc, PageModel>('Page', pageSchema)
Now you can directly access the property 'id', e.g. in a unit test like so:
it('implements optimistic concurrency', async () => {
const page = Page.build({
label: 'Root Page'
// ...
})
await page.save()
const firstInstance = await Page.findById(page.id)
const secondInstance = await Page.findById(page.id)
firstInstance!.set({ label: 'Main Page' })
secondInstance!.set({ label: 'Home Page' })
await firstInstance!.save()
try {
await secondInstance!.save()
} catch (err) {
console.error('Error:', err)
return
}
throw new Error('Should not reach this point')
})

Resources