Creating methods to update & save documents with mongoose? - node.js

After checking out the official documentation, I am still not sure on how to create methods for use within mongoose to create & update documents.
So how can I do this?
I have something like this in mind:
mySchema.statics.insertSomething = function insertSomething () {
return this.insert(() ?
}

From inside a static method, you can also create a new document by doing :
schema.statics.createUser = function(callback) {
var user = new this();
user.phone_number = "jgkdlajgkldas";
user.save(callback);
};

Methods are used to to interact with the current instance of the model. Example:
var AnimalSchema = new Schema({
name: String
, type: String
});
// we want to use this on an instance of Animal
AnimalSchema.methods.findSimilarType = function findSimilarType (cb) {
return this.find({ type: this.type }, cb);
};
var Animal = mongoose.model('Animal', AnimalSchema);
var dog = new Animal({ name: 'Rover', type: 'dog' });
// dog is an instance of Animal
dog.findSimilarType(function (err, dogs) {
if (err) return ...
dogs.forEach(..);
})
Statics are used when you don't want to interact with an instance, but do model-related stuff (for example search for all Animals named 'Rover').
If you want to insert / update an instance of a model (into the db), then methods are the way to go. If you just need to save/update stuff you can use the save function (already existent into Mongoose). Example:
var Animal = mongoose.model('Animal', AnimalSchema);
var dog = new Animal({ name: 'Rover', type: 'dog' });
dog.save(function(err) {
// we've saved the dog into the db here
if (err) throw err;
dog.name = "Spike";
dog.save(function(err) {
// we've updated the dog into the db here
if (err) throw err;
});
});

Don't think you need to create a function that calls .save(). Anything that you need to do before the model is saved can be done using .pre()
If you want the check if the model is being created or updated do a check for this.isNew()

Related

Not able to use .save() while updating (put request) to a mongoose model (with strict set to false)

So I am building yet another CRUD app using the MERN stack (let's use foo as the example). Problem is that in this project I am trying to support an unstructured data stream, so I am setting the Schema to strict:false in the Data Model. All my requests work (get, delete, & post) but when I started working on editing the properties of a foo and sending it to Mongo I get this error.
TypeError: foo.save is not a function
Here's what my Routes look like for my API
router.route('/foos/:foo_id')
.put(function(req, res){
foo.findById(req.params.foo_id, function(err, foo) {
if (err)
res.send(err);
foo = req.body;
foo.save(function(err) {
if(err)
res.send(err);
res.json({message: 'foo Updated'});
});
})
});
Here's what my data model looks like:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var FooSchema = new Schema({
bar: String,
foobar: String,
}, { strict: false })
module.exports = mongoose.model('foo', FooSchema);
Here's my reducer:
case 'SAVE_FOO':
var id = action.foo._id;
var fooEdits = action.foo;
//serialize data to send to Mongo
function serialize(obj) {
var str = [];
for(var p in obj)
if (obj.hasOwnProperty(p)) {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
}
return str.join("&");
}
fetch('http://localhost:7770/api/foos/' + id, {
method: 'put',
headers: {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
},
body: serialize(fooEdits)
})
return state;
I am guess this is because i am overwriting the original schema i set for that foo. It works if I pass values defined in my schema, so if I change foo = req.body to foo.bar = req.body.bar it works.
The thing is I want to be able to pass whatever properties into this model and save to mongo, without defining it in the Schema. Is this possible or am I creating the most vulnerable CRUD app known to mankind?
I was thinking I could update my schema to have a customObj:{} then pass all custom properties in there. Then i could do foo.customObj = req.body.customObj, but that seems wrong...
Any help would be greatly appreciated, also this is my first post on Stack Overflow, so please let me know how I could better phrase my question or examples. Thanks!
foo = req.body;
change to
Object.assign(foo,req.body);

New document from mongoose method

I want to extend model with new method 'create'. It will check requirements for document creation, create additional documents and so on.
Usually I call
var user = new User({});
But how can I create document from mongoose method itself? I.e.
User.methods.create = function(userObject,callback){
//some checks
var doc = ???;
doc.save(function(err){
if(err) return callback(err);
//saving done
callback(null,doc);
});
}
UPD:
Thx to #chridam's answer my final code now looks like this:
User.statics.create = function(userObject,callback){
//some checks
var doc = this.model('User')(userObject);
doc.save(function(err){
if(err) return callback(err);
//saving done
callback(null,doc);
});
}
Statics will allow for defining functions that exist directly on your Model, so instead of an instance model (like you have tried), define a static method on the User class. Example:
var userSchema = new Schema({ firstname: String, lastname: String });
// assign a function to the "statics" object of our userSchema
userSchema.statics.create = function (userObject) {
console.log('Creating user');
// use Function.prototype.call() to call the Model.create() function with the model you need
return mongoose.Model.create.call(this.model('User'), userObject);
};
I know this is not answering your question exactly, but it may be what you are looking for.
check out the mongoose middleware
http://mongoosejs.com/docs/middleware.html
There is a pre-save hook where you can do some validation and other things such as document creation.

Mongoose Schema default value for ObjectId

I have a collection of warehouse upgrades. It is predefined "template" collection containing for example max_capacity, level and price. Then I have warehouse_levels collection, this contains different indexes to warehouse_upgrades for different stored resources. But I can't create warehouse_level model, because I need to load _ids of warehouse_upgrades
WarehouseUpgrade = mongoose.model("warehouse_upgrade");
// find wheat upgrade containing level 0
WarehouseUpgrade.find({ type: "wheat", level: 0 }).exec(function (err, wheat) {
var warehouseLevelSchema = Schema({
wheat: {
type: Schema.Types.ObjectId,
ref: "warehouse_upgrade",
default: wheat._id
},
... more resources
};
var WarehouseLevel = mongoose.model("warehouse_level", warehouseLevelSchema);
}
When I want to call var WarehouseLevel = mongoose.model("warehouse_level"); interpreting this code throws error:
MissingSchemaError: Schema hasn't been registered for model "warehouse_level"
If I extract out schema definition from WarehouseUpgrade.find, then code works, but I can't set up default values for resource warehouses.
How can I set default value for ObjectId from different collection when I don't want to hardcode this values?
EDIT:
I load all schema definitions in file named mongoose.js:
var mongoose = require("mongoose"),
Animal = require("../models/Animal");
Warehouse_upgrade = require("../models/Warehouse_upgrade"),
Warehouse_level = require("../models/Warehouse_level"),
User = require("../models/User"),
...
module.exports = function(config) {
mongoose.connect(config.db);
var db = mongoose.connection;
// And now I call methods for creating my "templates"
Warehouse_upgrade.createUpgrades();
Animal.createAnimals();
User.createDefaultUser();
}
MissingSchemaError occurs in model/User(username, hashed_password, email, warehouse_level,...) - every user has reference to his own document in warehouse_level.
// User
var mongoose = require("mongoose"),
Schema = mongoose.Schema,
Warehouse_level = mongoose.model("warehouse_level");
// There are no users in DB, we need create default ones
// But first, we need to create collection document for warehouse_level
// and warehouse (not shown in this code snippet)
Warehouse_level.create({}, function (err, warehouseLevel) {
if (err) { console.error(err); return; }
// warehouse_level document is created, let's create user
User.create({ username: ..., warehouse_level: warehouseLevel._id });
});
One possible way to achieve this is to create a method like "setDefaultIndexes"
var warehouseLevelSchema = mongoose.Schema({..});
warehouseLevelSchema.methods = {
setDefaultUpgrades: function() {
var self = this;
WarehouseUpgrade.find({ level: 0 }).exec(function (err, collection) {
for (var i = 0; i < collection.length; i++) {
var upgrade = collection[i];
self[upgrade.type] = upgrade._id;
}
self.save();
});
}
};
var Warehouse_level = mongoose.model("warehouse_level", warehouseLevelSchema);
And call it after creation of new warehouse_level:
WarehouseLevel.create({}, function (err, warehouseLevel) {
if (err) { console.error(err); return; }
warehouseLevel.setDefaultUpgrades();
});

Initializing a ref array in Mongoose

Say I have the following schemas:
var promoGroupSchema = new Schema({
title: String,
offers: [{Schema.Types.ObjectId, ref: 'Offer']
});
and
var offerSchema = new Schema({
type: String
});
How do you initialize a promoGroup with new offers? The following won't work since save() is asynchronous. Now, I know I could put a function as a parameter of the save function, but that gets ugly with more offers.
var offer1 = new offerSchema({
type: "free bananas!"
});
var offer2 = new offerSchema({
type: "free apples!"
});
offer1.save();
offer2.save();
var newPromoGroup = new promoGroupSchema({
title: "Some title here",
offers: [offer1._id, offer2._id]
});
From what I read, Mongoose gives the object an _id as soon as you create them, can I rely on those?
You should access _id in the save callback. If you have a lot of offers to group, using a library like async will make your life easier.
var myOffers = [...]; // An array with offers you want to group together
// Array of functions you want async to execute
var saves = myOffers.map(function(offer) {
return function(callback) {
offer.save(callback);
}
}
// Run maximum 5 save operations in parallel
async.parallelLimit(saves, 5, function(err, res) {
if(err) {
console.log('One of the saves produced an error:', err);
}
else {
console.log('All saves succeeded');
var newPromoGroup = new promoGroupSchema({
title: "Some title here",
offers: _.pluck(myOffers, '_id') // pluck: see underscore library
});
}
});
You could also try to use Promises.

Dynamically create collection with Mongoose

I want to give users the ability to create collections in my Node app. I have really only seen example of hard coding in collections with mongoose. Anyone know if its possible to create collections dynamically with mongoose? If so an example would be very helpful.
Basically I want to be able to store data for different 'events' in different collections.
I.E.
Events:
event1,
event2,
...
eventN
Users can create there own custom event and store data in that collection. In the end each event might have hundreds/thousands of rows. I would like to give users the ability to perform CRUD operations on their events. Rather than store in one big collection I would like to store each events data in a different collection.
I don't really have an example of what I have tried as I have only created 'hard coded' collections with mongoose. I am not even sure I can create a new collection in mongoose that is dynamic based on a user request.
var mongoose = require('mongoose');
mongoose.connect('localhost', 'events');
var schema = mongoose.Schema({ name: 'string' });
var Event1 = mongoose.model('Event1', schema);
var event1= new Event1({ name: 'something' });
event1.save(function (err) {
if (err) // ...
console.log('meow');
});
Above works great if I hard code 'Event1' as a collection. Not sure I create a dynamic collection.
var mongoose = require('mongoose');
mongoose.connect('localhost', 'events');
...
var userDefinedEvent = //get this from a client side request
...
var schema = mongoose.Schema({ name: 'string' });
var userDefinedEvent = mongoose.model(userDefinedEvent, schema);
Can you do that?
I believe that this is a terrible idea to implement, but a question deserves an answer. You need to define a schema with a dynamic name that allows information of 'Any' type in it. A function to do this may be a little similar to this function:
var establishedModels = {};
function createModelForName(name) {
if (!(name in establishedModels)) {
var Any = new Schema({ any: Schema.Types.Mixed });
establishedModels[name] = mongoose.model(name, Any);
}
return establishedModels[name];
}
Now you can create models that allow information without any kind of restriction, including the name. I'm going to assume an object defined like this, {name: 'hello', content: {x: 1}}, which is provided by the 'user'. To save this, I can run the following code:
var stuff = {name: 'hello', content: {x: 1}}; // Define info.
var Model = createModelForName(name); // Create the model.
var model = Model(stuff.content); // Create a model instance.
model.save(function (err) { // Save
if (err) {
console.log(err);
}
});
Queries are very similar, fetch the model and then do a query:
var stuff = {name: 'hello', query: {x: {'$gt': 0}}}; // Define info.
var Model = createModelForName(name); // Create the model.
model.find(stuff.query, function (err, entries) {
// Do something with the matched entries.
});
You will have to implement code to protect your queries. You don't want the user to blow up your db.
From mongo docs here: data modeling
In certain situations, you might choose to store information in
several collections rather than in a single collection.
Consider a sample collection logs that stores log documents for
various environment and applications. The logs collection contains
documents of the following form:
{ log: "dev", ts: ..., info: ... } { log: "debug", ts: ..., info: ...}
If the total number of documents is low you may group documents into
collection by type. For logs, consider maintaining distinct log
collections, such as logs.dev and logs.debug. The logs.dev collection
would contain only the documents related to the dev environment.
Generally, having large number of collections has no significant
performance penalty and results in very good performance. Distinct
collections are very important for high-throughput batch processing.
Say I have 20 different events. Each event has 1 million entries... As such if this is all in one collection I will have to filter the collection by event for every CRUD op.
I would suggest you keep all events in the same collection, especially if event names depend on client code and are thus subject to change. Instead, index the name and user reference.
mongoose.Schema({
name: { type: String, index: true },
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', index: true }
});
Furthermore I think you came at the problem a bit backwards (but I might be mistaken). Are you finding events within the context of a user, or finding users within the context of an event name? I have a feeling it's the former, and you should be partitioning on user reference, not the event name in the first place.
If you do not need to find all events for a user and just need to deal with user and event name together you could go with a compound index:
schema.index({ user: 1, name: 1 });
If you are dealing with millions of documents, make sure to turn off auto index:
schema.set('autoIndex', false);
This post has interesting stuff about naming collections and using a specific schema as well:
How to access a preexisting collection with Mongoose?
You could try the following:
var createDB = function(name) {
var connection = mongoose.createConnection(
'mongodb://localhost:27017/' + name);
connection.on('open', function() {
connection.db.collectionNames(function(error) {
if (error) {
return console.log("error", error)
}
});
});
connection.on('error', function(error) {
return console.log("error", error)
});
}
It is important that you get the collections names with connection.db.collectionNames, otherwise the Database won't be created.
This method works best for me , This example creates dynamic collection for each users , each collection will hold only corresponding users information (login details), first declare the function dynamicModel in separate file : example model.js
/* model.js */
'use strict';
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
function dynamicModel(suffix) {
var addressSchema = new Schema(
{
"name" : {type: String, default: '',trim: true},
  "login_time" : {type: Date},
"location" : {type: String, default: '',trim: true},
}
);
return mongoose.model('user_' + suffix, addressSchema);
}
module.exports = dynamicModel;
In controller File example user.js,first function to create dynamic collection and second function to save data to a particular collection
/* user.js */
var mongoose = require('mongoose'),
function CreateModel(user_name){//function to create collection , user_name argument contains collection name
var Model = require(path.resolve('./model.js'))(user_name);
}
function save_user_info(user_name,data){//function to save user info , data argument contains user info
var UserModel = mongoose.model(user_name) ;
var usermodel = UserModel(data);
usermodel.save(function (err) {
if (err) {
console.log(err);
} else {
console.log("\nSaved");
}
});
}
yes we can do that .I have tried it and its working.
REFERENCE CODE:
app.post("/",function(req,res){
var Cat=req.body.catg;
const link= req.body.link;
const rating=req.body.rating;
Cat=mongoose.model(Cat,schema);
const item=new Cat({
name:link,
age:rating
});
item.save();
res.render("\index");
});
I tried Magesh varan Reference Code ,
and this code works for me
router.post("/auto-create-collection", (req, res) => {
var reqData = req.body; // {"username":"123","password":"321","collectionName":"user_data"}
let userName = reqData.username;
let passWord = reqData.password;
let collectionName = reqData.collectionName;
// create schema
var mySchema = new mongoose.Schema({
userName: String,
passWord: String,
});
// create model
var myModel = mongoose.model(collectionName, mySchema);
const storeData = new myModel({
userName: userName,
passWord: passWord,
});
storeData.save();
res.json(storeData);
});
Create a dynamic.model.ts access from some where to achieve this feature.
import mongoose, { Schema } from "mongoose";
export default function dynamicModelName(collectionName: any) {
var dynamicSchema = new Schema({ any: Schema.Types.Mixed }, { strict: false });
return mongoose.model(collectionName, dynamicSchema);
}
Create dynamic model
import dynamicModelName from "../models/dynamic.model"
var stuff = { name: 'hello', content: { x: 1 } };
var Model = await dynamicModelName('test2')
let response = await new Model(stuff).save();
return res.send(response);
Get the value from the dynamic model
var Model = dynamicModelName('test2');
let response = await Model.find();
return res.send(response);

Resources