I created a function to save data into mongoDB with logic below, but I really have difficulty to refactor the code and make it cleaner, there are so many annoying code duplication, how can I have DRY principle?
Logic:
1. pass in a flag to decide either close DB connection or not at end.
2. create different mongoDB models according to the passed in returnedArray and save into DB.
var saveArrayToDB = function(returnedArray, flagToCloseDBConnection) {
var objectToSave,
object,
type = returnedArray[0].type,
arrayToSave = [];
if (type === 'user') {
for (var i = 0; i < returnedArray.length; i++) {
object = returnedArray[i];
objectToSave = new User({
fullName: object['full_name'],
activatedAt: object['activated_at'],
location: object['location'],
timezone: object['timezone'],
imageURL: object['mugshot_url'],
stats: object['stats']
});
arrayToSave.push(objectToSave);
}
User.create(arrayToSave, function(err) {
if (err) {
console.log('err ' + err);
}
if(flagToCloseDBConnection) {
mongoose.connection.close();
}
});
} else if (type === 'group') {
for (var j = 0; j < returnedArray.length; j++) {
object = returnedArray[j];
objectToSave = new Group({
fullName: object['full_name'],
createdAt: object['created_at'],
stats: object['stats'],
url: object['web_url']
});
arrayToSave.push(objectToSave);
}
Group.create(arrayToSave, function(err) {
if (err) {
console.log('err ' + err);
}
if(flagToCloseDBConnection) {
mongoose.connection.close();
}
});
} else {
objectToSave = null;
console.log('ERROR: unrecognized type in data. Not saved.');
}
};
Just to add on to what #JohnnyHK commented on your question, it would be best if you keep your mongoose connection open during your application lifecycle. Other than that you could use some JavaScript functions like map() to initialize the arrays, define common callback functions that you can reuse in both create and map methods:
var saveArrayToDB = function(returnedArray, flagToCloseDBConnection) {
var type = returnedArray[0].type,
arrayToSave = [];
var callback = function(err) {
if (err) { console.log('err ' + err); }
};
var newUser = function(u){
return new User({
fullName: u['full_name'],
activatedAt: u['activated_at'],
location: u['location'],
timezone: u['timezone'],
imageURL: u['mugshot_url'],
stats: u['stats']
});
};
var newGroup = function(g){
return new Group({
fullName: g['full_name'],
createdAt: g['created_at'],
stats: g['stats'],
url: g['web_url']
});
};
if (type === 'user') {
arrayToSave = returnedArray.map(newUser);
User.create(arrayToSave, callback);
} else if (type === 'group') {
arrayToSave = returnedArray.map(newGroup);
Group.create(arrayToSave, callback);
} else {
console.log('ERROR: unrecognized type in data. Not saved.');
}
};
No need for closing the connection. Here's an already much improved version:
var factories = {
'user': {
method: function(object){
return {
fullName: object['full_name'],
activatedAt: object['activated_at'],
location: object['location'],
timezone: object['timezone'],
imageURL: object['mugshot_url'],
stats: object['stats']
};
},
model: User
},
'group': {
method: function(object){
return {
fullName: object['full_name'],
createdAt: object['created_at'],
stats: object['stats'],
url: object['web_url']
};
},
model: Group
}
}
var saveArrayToDB = function(returnedArray) {
var saveQueue=[],
factory = factories[returnedArray[0].type];
if(!factory){
return console.log('ERROR: unrecognized type in data. Not saved.');
}
returnedArray.forEach(function(item){
saveQueue.push(factory.method(item));
});
factory.model.create(saveQueue, function(err){
if(err){
console.log('err ' + err);
}
});
};
(You don't need to pass document instances, plain objects are good enough for Model.create)
Related
I'm getting this array of user emails from the post data. I want to find the _id related to each email. I tried this for loop:
var studentIds = [];
for (var i = studentEmails.length - 1; i >= 0; i--) {
var email = studentEmails[i];
User.findOne({"email": email}, (err, student) => {
if (err) {
console.log("ERROR" + err);
}
if (student) {
var id = student._id;
studentIds.push(id);
console.log("STUDENT: " + student);
}
});
}
// Outside for loop
console.log('END');
However, this logs the following:
END
STUDENT: { _id: 5a11e667d7333203337cd9a4,
name: 'Patrick Jacobs',
email: 'windvaan#live.nl',
password: '$2a$10$CiSw/VH1HCaPtW6Sjz0X4.4avVoLsAH6iyF3FhidorahwLt1WDXoC',
__v: 0 }
STUDENT: { _id: 5a0f7dfb64b5a6000417c662,
name: 'Carlo Jacobs',
email: 'carlojacobs91#gmail.com',
password: '$2a$10$fiIosS4Jo5ehuCp3TfltSOnpypPMWSMvzlb7phRWmNGBtDz5W1rCG',
__v: 0 }
As you can see, the END is being printed first. I don't want that. I'm assuming the for loop is asynchronous? How can I make it synchronous?
Thx in advance!
The solution would be
var studentIds = [];
var loopRecords = function (records, cb) {
if (!records.length) return cb();
var email = records.shift();
User.findOne({"email": email}, (err, student) = > {
if (err) {
console.log("ERROR" + err);
}
if (student) {
var id = student._id;
studentIds.push(id);
console.log("STUDENT: " + student);
}
return loopRecords(records, cb);
}
}
loopRecords(studentEmails,function () {
console.log('END');
})
It is not the for loop, but the User.findOne call that is asynchronous.
Following this answer, you could use streamline.js; try the following to make the call synchronous.
var result = User.findOne({"email": email}, _);
if (result === null) {
console.log("ERROR" + err);
}
var id = result._id;
studentIds.push(id);
console.log("STUDENT: " + result);
I created two collections,one for enterprise and another for employees,their schema is as follows,
var mongoose= require('mongoose');
var Enterprise= new mongoose.Schema({
name:{type:String},
email:{type:String},
sector:{type:String},
employees: {type:Number,default:0}
});
module.exports={
Enterprise:Enterprise
};
var mongoose = require('mongoose');
var employee = new mongoose.Schema({
enterprise:{type: String},
name:{type:String},
email:{type:String},
password:{type:String},
gender:{type:String},
});
module.exports = {
employee:employee
};
my add employee route,
var mongoose = require('mongoose');
var q = require('q');
var employee = mongoose.model('employee');
var enterprise = mongoose.model('enterprise');
var addEmployee = function(req, res) {
newEmployee = new employee();
newEmployee.enterprise = req.params.enterprise;
newEmployee.name = req.params.name;
newEmployee.email = req.params.email;
newEmployee.gender = req.params.gender;
function detailSave() {
var deferred = q.defer();
newEmployee.save(function(err, data) {
if (err) {
res.send(500);
console.log('couldnt save employee details');
deferred.reject({errmessage: 'couldnt save employee details', err: err});
} else {
res.send(200);
deferred.resolve({data: data});
}
});
return deferred.promise;
}
function incrementEmployee(doc) {
var deferred = q.defer();
enterprise.findOneAndUpdate({ 'name': doc.enterprise }, { $inc: { 'employees': 1 } },
function(err, num) {
if (err) {
deferred.reject({errmessage: 'couldnt incrementEmployee', err: err});
res.send(500);
console.log('couldnt incrementEmployee');
} else {
res.send(200);
deferred.resolve({num:num});
}
});
return deferred.promise;
}
detailSave()
.then(incrementEmployee)
.then(function(success) {
console.log('success', success);
res.json(200, success);
})
.fail(function(err) {
res.json(500, err);
})
.done();
};
module.exports = {
addEmployee: addEmployee
};
The problem is when I add an employee, the employees field in enterprise collection doesn't increment
I think your query is not working since doc.enterprise is null
On the basis of your comment.
Try to give your query like this {'name': doc.data.enterprise}
function incrementEmployee(doc) {
var deferred = q.defer();
enterprise.findOneAndUpdate({
'name': doc.data.enterprise
}, {
$inc: {
'employees': 1
}
},
function(err, num) {
if (err) {
deferred.reject({
errmessage: 'couldnt incrementEmployee',
err: err
});
res.send(500);
console.log('couldnt incrementEmployee');
} else {
res.send(200);
deferred.resolve({
num: num
});
}
});
return deferred.promise;
}
Why wont usernametoid function return the acual id? cause im trying to send the result of the userdata as the return. In this case, i want to only send the userdata`s _id attribute. but it seems like it wont work.
console.log(userdata._id); // works
return resolve(userdata._id); // wont work.
output of variable userdata:
{
cash: 7300002,
bank: 0,
xp: 0,
rank: 1,
points: 1,
location: 1,
health: 100,
protection: 1,
attack: 1,
family: '0',
password: 'jKa4qC7pRCgE5jvzD9Vv1pRUNxFlQEM7Jpq/IoJ/sUWOAv1Wx1RI/j/Vu6Zf8zyNkCFcg3QBtdfAC+lmPS8KIA==',
profileImageURL: 'modules/users/client/img/profile/default.png',
roles: [ 'user' ],
created: Sat Aug 27 2016 12:33:55 GMT-0400 (EDT),
__v: 0,
username: 'signature',
provider: 'local',
salt: '4ySlrr9ggESxBB3dR5bx4Q==',
_id: 57c1c0f3b6b20c011242bf22 }
when i do: `return resolve(userdata._id) it would get this error:
/server/factory/user_factory.js:52
return resolve(userdata._id);
^
TypeError: Cannot read property '_id' of null
node.js call:
var articles = require('../controllers/articles.server.controller'),
path = require('path'),
mongoose = require('mongoose'),
Article = mongoose.model('Article'),
Users = mongoose.model('User'),
errorHandler = require(path.resolve('./modules/core/server/controllers/errors.server.controller'));
var userFunc = require('../factory/user_factory.js');
app.post('/api/kill', function (req, res) {
console.log("starting");
var username = "signature";//req.query.username;
var result = ["test service"];
var data = req.user;
userFunc.usernametoid(username).then( function (otherplayerid) {
if (!(otherplayerid)) {
console.log("other player is acually " + otherplayerid);
result.push("denne brukeren finnes ikke! " + otherplayerid);
} else {
userFunc.usernametoid(otherplayerid).then( function (otherplayer) {
if (data.location != otherplayer.location) {
result.push("Du er ikke i samme lokasjon som " + username);
result.push(data.location + " vs " + otherplayer.location);
} else {
userFunc.addCash(req.user._id,100000);
result.push("starter lokasjonisering");
}
});
}
res.json(result);
});
});
user factory:
var articles = require('../controllers/articles.server.controller'),
path = require('path'),
mongoose = require('mongoose'),
Article = mongoose.model('Article'),
Users = mongoose.model('User'),
errorHandler = require(path.resolve('./modules/core/server/controllers/errors.server.controller'));
exports.usernametoid = usernametoid;
function usernametoid(id) {
return new Promise( function (resolve, reject) {
var query = Users.findOne( { username : id } );
// var query = Users.find({_id:id});
query.exec(function(err, userdata) {
if (err){
return reject({err : 'Error while getting user info'});
}
console.log(userdata._id);
return resolve(userdata);
});
}, function (){
return reject({err : 'error while fetching cash'});
});
}
Because you are not passing correctly the fetched user to the query.exec.
You need to do:
var Users = require('../models/users-model.js');
function usernametoid(id) {
return new Promise( function (resolve, reject) {
Users.findOne({ username : id }).then( function(user){
//If you use lodash you can do _.isNull(user)
if(user == null){
return reject({error : 'User not found'});
}
user.exec(function(userdata, error) {
if(userdata){
return resolve(userdata);
}
if(error){
return reject({error : 'Error while executing query'});
}
});
});
});
}
I don't really get why you are importing Users Model like that. I do not think Node will be able to fetch it like that.
And, you should require mongoose in your server.js
To catch the rejection you need the following code:
UserFactory.userNameToId(id).then( function(response){
if(response.error){
console.log('error '+response.error);
}
if(response){
console.log('Got response '+response);
}
});
I m doing a javascript function using mongoose to find a group which contains a list of users emails, that part works perfectly. After I want to find each user of the list and add the new group name instead of the old group name, I don't know why, but it doesn't work, the function returns me the group before doing the for loop, and the users are not modified.
Here is my user model :
var userSchema = mongoose.Schema({
local : {
email : String,
password : String,
}
groups : { type: [String], default: []}
});
And here my Group model :
var groupSchema = mongoose.Schema({
title : String,
creator : String,
listOfUsers : { type: [String], default: []}
});
And here my function :
Group.findOne({ '_id': groupId}, function (error, group){
var oldTitle = group.title;
group.title = req.body.title;
group.save();
if(error){
throw (error)
}
for(var i = 0; i < group.listOfUsers.length;i++){
User.findOne({'local.email': group.listOfUsers[i]}, function(error,user){
if(error){
throw (error)
}
for(var j=0;j<user.groups.length;j++){
if(user.groups[j] == oldTitle){
user.groups[j] = group.title;
user.save();
}
}
});
}
return res.json(group);
you can use async to fix the callback problem
async.waterfall(
[
function (callback) {
Group.findOne({
'_id': groupId
}, function (error, group) {
if (error) {
throw (error)
} else {
callback(null, group);
}
})
},
function (group, callback) {
for (var i = 0; i < group.listOfUsers.length; i++) {
User.findOne({
'local.email': group.listOfUsers[i]
}, function (error, user) {
if (error) {
throw (error)
} else {
callback(null, user, group);
}
});
}
},
function (user, group, callback) {
var oldTitle = group.title;
group.title = req.body.title;
for (var j = 0; j < user.groups.length; j++) {
if (user.groups[j] == oldTitle) {
user.groups[j] = group.title;
user.save();
}
}
callback(null, 'done');
}
],
function (err, result) {
console.info("4");
console.info(err);
console.info(result);
});
forgive me if i made any mistake, it is always hard to write code without data, i hope you will understand how i wanted to solve.
and don't forget
var async = require('async');
at the beginning
You should do user.save() only after for loop is finished. I updated for loops with forEach which I feel more comfortable to use.
Group.findbyId(groupId, function(err, group) {
var oldTitle = group.title;
group.title = req.body.title;
if (group.save()) {
listOfUsers.forEach(function(groupUser) {
User.findOne({'local.email' : groupUser}, function (err, user) {
if(user) {
user.group.forEach(function(userGroup) {
if (userGroup == oldTitle) {
userGroup = group.title;
}
})
user.save();
}
});
});
res.json(group);
} else {
res.status(400).json({messsage: 'Error while saving group!'});
}
});
I understand well the concepts of OOP and prototypal inheritance in JavaScript, but sometimes, I wonder how to make use of these in real world applications.
I'll take as an exemple a simple(istic) contact management web application I pushed on GitHub a couple of months ago.
In the main handler mainly reside functions:
var UserModel = require('../models/userModel.js');
var checkObjectId = new RegExp('^[0-9a-fA-F]{24}$');
var root;
exports.getContacts = function(request, response) {
var id = JSON.parse(request.params.user)[0];
// validate
if (!checkObjectId.test(id)) {
return res.status(400).json({error: 'Not a user id'});
}
UserModel.findById(id, function(err, user) {
if (err) {
return console.log(err);
}
response.send(user.contacts);
});
};
exports.addContact = function(request, response) {
var id = JSON.parse(request.params.user)[0];
// validate
if (!checkObjectId.test(id)) {
return res.status(400).json({error: 'Not a user id'});
}
UserModel.findById(id, function(err, user) {
if (err) {
return console.error(err);
}
var contact = {};
// avoid to save empty info
if (request.body.first.length > 1) {contact.first = request.body.first;}
if (request.body.last.length > 1) {contact.last = request.body.last;}
if (request.body.mobile.length > 1) {contact.mobile = request.body.mobile;}
if (request.body.home.length > 1) {contact.home = request.body.home;}
if (request.body.office.length > 1) {contact.office = request.body.office;}
if (request.body.email.length > 1) {contact.email = request.body.email;}
if (request.body.company.length > 1) {contact.company = request.body.company;}
if (request.body.description.length > 1) {contact.description = request.body.description;}
if (request.body.keywords.length > 1) {contact.keywords = request.body.keywords;}
user.contacts.push(contact);
user.save(function(err) {
if (err) {
return console.error(err);
}
console.log('contact saved');
response.send(user.contacts);
});
});
};
exports.updateContact = function(request, response) {
var id = JSON.parse(request.params.user)[0];
// validate
if (!checkObjectId.test(id)) {
return res.status(400).json({error: 'Not a user id'});
}
var contact = {
_id: request.body._id,
first: request.body.first,
last: request.body.last,
mobile: request.body.mobile,
home: request.body.home,
office: request.body.office,
email: request.body.email,
company: request.body.company,
description: request.body.description,
keywords: request.body.keywords
};
UserModel.update({_id: id, "contacts._id": request.body._id}, {$set: {"contacts.$": contact}}, function(err, user) {
if (err) {
return console.error(err);
}
response.sendStatus(user);
});
};
exports.deleteContact = function(request, response) {
var id = JSON.parse(request.params.user)[0];
// validate
if (!checkObjectId.test(id)) {
return res.status(400).json({error: 'Not a user id'});
}
return UserModel.update({_id: id}, {$pull: {contacts: {_id: request.params.id}}}, function(err, user) {
if (err) {
return console.error(err);
}
console.log('contact removed');
console.log(user);
response.sendStatus(user);
});
};
It doesn't do much: fetch data from DB and return them or take data from user and save them to DB.
If it was a bit more complexe, I would surely place some logic in separate functions to reuse them and break down complexity.
Nevertheless, this code looks rather procedural, so does the hypothetical more complex version with separate functions. How would it be organized in a OOP way and how would I gain from it?
For instance, would I benefit from a User constructor?
I think the first thing you could do is nest the instance of your constructor inside an initializing function so you wouldn't have to repeat your validation code.
var connection = (function() {
var UserModel = require('../models/userModel.js');
var notAUser = {error: 'Not a user id'};
function init(request, response) {
var status = validate(JSON.parse(request.params.user)[0]);
if (!status.id) return response.status(400).json(status);
return new Connect(request, response, status.id);
}
function Connect(request, response, id) {
this.request = request;
this.response = response;
this.id = id;
this.info = { _id: id, "contacts._id": request.body._id };
}
function validate(id) {
if (!/^[0-9a-fA-F]{24}$/.test(id)) return notAUser;
else return {id: id};
}
Connect.prototype.getContact = function() {}
//etc...
return init;
})();
module.exports = connection;
Then in your actual application
var connection = require("./connection.js");
someAsync(data, function(req, res) {
var query = connection(req, res); //returned instance of constructor
query.getContact(someData, callback);
});
I would start by encapsulating the request and response since every method needs those. Like:
var contact = function (request, response) {
return {
add: add
}
function add() {
// add() gets access request and response for free
}
};
OR, if you are keen on the new operator:
function Contact(request, response) {
this.request = request;
this.response = response;
}
Contact.prototype.add = function () {
this.request;
}
Then move repeated code and callbacks to private methods you can reuse inside the object.