Mongoose Schema default value for ObjectId - node.js

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

Related

Mongoose, geospatial query for users

I'm currently working with nodeJS, using express and mongoDB and mongoose for an ORM. When I create a User and save them to the database I would like to query their location and save it. This is what I am currently doing, I have a UserSchema and a location Schema.
My userSchema just has the location stored as a string and in the location Schema itself I have
var locationSchema = new mongoose.Schema({
name: String,
loc: {
type: [Number],
index: '2d'
}
});
mongoose.model('Location', LocationSchema);
And then in my controller, I have the following
import json from '../helpers/json;
import mongoose from 'mongoose';
var User = mongoose.model('User);
module.exports = function() {
var obj = {};
obj.create = function (req, res) {
var user = new User(req.body);
user.roles = ['authenticated']
user.location = getLocation(req);
user.save(function (err) {
if (err) {
return json.bad(err, res);
}
json.good({
record: user,
});
});
};
return obj;
function getLocation (req) {
var limit = req.query.limit || 10;
var maxDistance = req.query.distance || 1;
maxDistance /= 6371;
var coords = [];
coords[0] = req.query.longitude;
coords[1] = req.query.lattitude;
Location.findOne({
loc: {
$near: coords,
$maxDistance: maxDistance
}
}).limit(limit).exec(function (err, location) {
if (err) {
console.log(err);
}
return location.name;
});
}
};
I have also tried using location.find instead of findOne and returning locations[0].name.
The error is thrown says cast to the number failed for value undefined at loc.
Do I need to send the location data to the server from the client side? If so, is there a best method to implement this? I have heard of the HTML5 Geolocation API, but I have never utilized it.
Thank you!
!!! -- UPDATE --- !!
I have started using the Geolocation API on the client side to send this data to the server in the request. I am using angular on the client side like so
(function() {
'use strict';
angular.module('opinionated.authentication')
.controller('SignupController', SignupController);
/* #ngInject */
function SignupController ($state, appUsers, appToast) {
var vm = this;
vm.reset = reset;
vm.create = create;
vm.user = {
name: '',
username: '',
email: '',
password: ''
};
vm.location = {
lattitude: '',
longitude: ''
};
function create = (isValid) {
if (isValid) {
var user = new appUsers.single({
name: vm.user.name,
username: vm.user.username,
email: vm.user.email,
password: vm.user.password,
lattitude: vm.location.lattitude,
longitutde: vm.location.longitude
});
user.$save(function (response) {
if (response.success) {
appToast('Welcome to Opinionated, ' + response.res.record.name);
$state.go('authentication.wizard')
} else {
appToast(response.res.messsage);
}
});
} else {
appToast('Hmm... Something seems to be missing');
}
}
function getPosition() {
navigator.geolocation.getPosition(updatePosition);
}
function updatePosition (position) {
vm.location.lattitude = position.coords.lattitude;
vm.location.longitutde = position.coords.longitude;
}
getPosition();
....
I think it has something to do with how I am getting the coordinates now. My browser prompts me for permission to use my location, so I am at least requesting the data. However, I changed my User Schema to save the lat and long and neither of these values are being saved upon success.
I found my error. I did need to include the Geolocation API to get the users coordinates. I then just saved the coordinates to the database and am using mongo's geo service from there! Everything works fine now.

How to perform Update and Delete operations in a bucket using Couchbase and Nodejs sdk

I am moving from mongodb to Couchbase using Node.js. I want to perform the CRUD operations. Insert(create) and Get are working fine, but when I want to perform Update and Delete getting some error messages (Here update purpose using 'upsert','replace' are used) like:
TypeError: Cannot read property 'replace' of undefined
Here is code:
db.js
// Instantiate Couchbase and Ottoman
var couchbase=require('couchbase');
var ottoman=require('ottoman');
// Build my cluster object and open a new cluster
var myCluster = new couchbase.Cluster('localhost:8091');
var myBucket = myCluster.openBucket('default');
ottoman.bucket=myBucket;
require('./model/user');
ottoman.ensureIndices(function(){});
user.js
var db = require('./../db.js').myBucket;
var ottoman = require('ottoman');
var userMdl = ottoman.model('User', {
firstName: {type:'string'},
lastName: {type:'string'},
created: {type: 'Date', default:function(){return new Date()}},
email:'string',
phone: 'string'
},{
index: {
findByID: {
by: '_id'
},
}
})
module.exports = userMdl;
routes.js
var bodyParser = require('body-parser');
var db = require('../schema/db').myBucket;
var user=require('../schema/model/user');
var jsonParser = bodyParser.json();
var urlencodedParser = bodyParser.urlencoded({ extended: false });
module.exports = function (app) {
// Delete a record
app.post("/api/delete/:_id", function(req, res) {
console.log("_id:"+req.params._id)
if(!req.params._id) {
return res.status(400).send({"status": "error", "message": "A document id is required"});
}
db.delete({_id:req.params._id}, function(error, result) {
if(error) {
return res.status(400).send(error);
}
res.send(result);
});
});
app.post('/api/user/update/:id',function(req,res){
db.replace(req.params.id,{firstName:"Mahesh"},function(err,result){
if (err) {
res.status = 400;
res.send(err);
return;
}
else {
res.status = 202;
res.send(result);
}
})
})
}
I am stuck here from last two days.
You missed one argument although it can be optional.
From Couchbase Node.js SDK document, it have 4 arguments, but you have only 3.
db.replace(req.params.id,{firstName:"Mahesh"},function(err,result){
=>
db.replace(req.params.id,{firstName:"Mahesh"}, {}, function(err,result){
With 3rd argument of empty map may work properly, but notice that Couchbase uses optimistic locking, so you require "CAS" value for original document when you modify the original to get data integrity.
the line in db.js var ottoman = require('ottoman');it's a constructor itself. Then you have two instances, and the error comes in user.js when you try to define a model, because node-ottoman needs a reference to the bucket.
You should assign the bucket in the user.js or reuse the ottoman object that you left in the db.js
model.js
// Instantiate Couchbase and Ottoman
var couchbase = require('couchbase');
var ottoman = require('ottoman');
// Build my cluster object and open a new cluster
var myCluster = new couchbase.Cluster('localhost:8091');
var myBucket = myCluster.openBucket('default');
ottoman.bucket = myBucket;
var userMdl = ottoman.model('User', {
firstName: {type:'string'},
lastName: {type:'string'},
created: {type: 'Date', default:function(){return new Date()}},
email:'string',
phone: 'string'
},{
index: {
findByID: {
by: '_id'
},
}
}) ;
// this line needs to be after you define the model
ottoman.ensureIndices(function(){});
module.exports = userMdl;
model.exports = mybucket;
You can update Couchbase document using 2 ways 1st by upsert method and second by N1qlQuery
bucket.upsert('user', {'name': 'Jay'}, {'expiry': 1}, function(err){
bucket.get('user', function(err, result) {
console.log('Have item: %j', result.value);
})
});
let query = N1qlQuery.fromString("UPDATE `"+BUCKETNAME+"` SET name='"+data.name+"' where _id ='"+id+"'");
bucket.query(query,(error,result)=>{
if(error){
console.log(error);
}
console.log(result);
});
You can delete Couchbase document using 2 ways 1st is removed method and second by N1qlQuery
bucket.remove(id, function(error, result) {
console.log("Deleted");
});
let query = N1qlQuery.fromString("DELETE FROM `"+BUCKETNAME+"` WHERE _id = '"+id+"'");
bucket.query(query,(error,result)=>{
if(error){
console.log(error);
}
console.log(result);
})

How to search in sub doc

I have an angular-fullstack app generated from angular-fullstack yeoman generator and I have a Query Model as follows:
'use strict';
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
/**
* Discussion Question Schema
*/
var QuerySchema = new Schema({
date: {
type: Date,
default: Date
},
tags: [{
type: Schema.ObjectId,
ref: 'Tag'
}]
});
module.exports = mongoose.model('Query', QuerySchema);
var deepPopulate = require('mongoose-deep-populate')(mongoose);
and Tag has a field text. Now in my query controller I have to deep populate some other fields and paginate them so, I am trying something like this in the controller function:
exports.index = function(req, res) {
var escapeRegExpChars = function (text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
};
require('paginate-for-mongoose');
var limit,page;
if(req.query.limit != undefined) limit = req.query.limit;
else limit = 10;
if(req.query.page != undefined) page = req.query.page;
else page = 1;
var queryObj = {};
if(req.query.searchText != undefined && req.query.searchText != '')
queryObj['title']= new RegExp(escapeRegExpChars(req.query.searchText), 'i');
var options = {
perPage:limit,
delta:2,
page:page
};
if(req.query.fold !=undefined && req.query.fold != '') queryObj["tags.text"] = req.query.fold;
var query = Query.find(queryObj).populate('tags','text').deepPopulate('user class user.class');
query.paginate(options,function(err, resp){
if(err) { return handleError(res, err); }
if(req.query.fold) console.log(resp.results);
return res.status(200).json(resp.results);
});
};
How do I search queries with tags.text value exactly as the req.query.fold value?
MongoDB doesn't support joins so to search on a linked doc you have to do it in two steps:
// First look up the _id of the tag
Tags.findOne({text: req.query.fold}, function(err, tag) {
if (tag) {
// Add a match in the doc's tags array to the tag's _id
queryObj.tags = tag._id;
var query = Query.find(queryObj)...
...
}
});

Objects returned by Mongoose queries have no attributes

I currently have 3 MongoDB databases to which I connect from a Node.js app using mongoose.createConnection(...). For each db, I define schemas and models for all collections in the db. The problem I have is that when I query a collection, the results returned do not have any attributes set. However, using node-inspector, I can see that the attributes are loaded correctly from the db because they are present in the _doc attribute.
Example (some code is omitted):
var mongoose = require('mongoose');
// Connect to a db
var db = mongoose.createConnection();
var options = { auto_reconnect: true };
db.open(args.host, args.db, args.port, options, function(error, connection) {
var buildModel = require('../models/' + dbName + '/schema.js');
buildModel(db);
}
// Define schemas and models (in schema.js). This is the `buildModel` function from above.
module.exports = function(mongoose) {
var Group = new Schema({
name: { type: String, required: true },
companyId: { type: ObjectId, required: true }
});
mongoose.model("Group", Group, 'groups');
};
// Querying
var Group = getDb('db1').model('Group');
Group.find({}, function(error, groups) {
// Here I get all documents in the groups collection, but the attributes
// name and companyId are not set.
groups.forEach(function(group) {
// name and companyId are undefined
console.log('undefined' == typeof group.name); // true
console.log('undefined' == typeof group.companyId); // true
// _doc attribute is populated
console.log(group._doc.name); // 'Group 1'
});
});
The question is, am I forgetting to do something when I connect? I have also tried to specify the attributes to fetch using populate after calling find, but with no success.
I am using MongoDB 2.4.3, Node.js 0.10.6 and Mongoose 3.6.11.

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