Thinking OOP in JavaScript / Node.js - javascript

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.

Related

Method name is not a function

I have problem in calling the method in my module.
There is an errorTypeError: usr.User.getAddress is not a function
I don't know how to fix this I think I have problem in my module code. I want to get the address or the result.
in my main.js
var mysql = require('mysql');
var usr = require('./user');
var useraddress = usr.User.getAddress (id,pool); //this is how I access the method
in my user.js
exports.User = function () {
return {
getAddress: function (userid, pool){
pool.getConnection(function (err, connection) {
var options = {
sql: " select address from user where id = ?
};
var querypos = connection.query(options, [userid], function (err, results) {
if (err) throw err;
});
});
}
};
};
You are exporting User as a factory function which returns an object with getAddress method on it. So you need to invoke (instantiate) User first:
var useraddress = usr.User().getAddress(id, pool);
Another important problem. connection.query request is asynchronous, which means that assigning getAddress result to var useraddress doesn't make sense. Instead you need to either pass callback to getAddress or use Promise pattern (check this post for great deal of details on the topic: How do I return the response from an asynchronous call?).
In your case I think something like this would be a simplest working approach:
exports.User = function () {
return {
getAddress: function (userid, pool){
pool.getConnection(function (err, connection) {
var options = {
sql: "select address from user where id = ?"
};
var querypos = connection.query(options, [userid], function (err, results, callback, errCallback) {
if (err) {
errCallback(err);
}
callback(results);
});
});
}
};
};
and usage:
usr.User().getAddress(id, pool, function(result) {
console.log('Loaded', result);
});
This is because usr.User does not have .getAddress property on it.
To use .getAddress as a property, you need to export User as an object instead.
exports.User = {
getAddress: function (userid, pool){
pool.getConnection(function (err, connection) {
var options = {
sql: " select address from user where id = ?
};
var querypos = connection.query(options, [userid], function (err, results) {
if (err) throw err;
});
});
}
};
};
Now it does.

Node.js how to use callback to send data to web interface

I have two functions that query the twitter api. The query is done through a web interface and I call each function when a checkbox is on. My problem now is after all the querying has been done, I want to be able to store the data and send it back to the web interface. How do I do this ?
if (string.checktweet1 == 'on') {
tweets(string.teamname)
}
if (string.checkmentions1 == 'on'){
mentions(string.teamname)
}
if (string.checktweet2 == 'on'){
tweets(string.playername)
}
if (string.checkmentions2 == 'on'){
mentions(string.playername)
}
function mentions(x){
client.get('search/tweets', {q:x, count:1},
function (err,data,){
for(var index in data.statuses){
var tweet = data.statuses[index];
console.log(tweet.text);
}
})
}
My code is only sending the data for the function "tweets"
json = {};
function tweets(y){
client.get('statuses/user_timeline', {screen_name:y, count:1},
function(err,data) {
for(var index in data){
var tweet = data[index];
console.log(tweet.text);
}
json[index] = tweet
res.end(JSON.stringify(json));
})
}
As I understand you are not looking to save the data, but just collect the results of multiple asynchronous calls and once all are completed deliver the data to your client? If so, you could use async or promises.
There are already examples of this in Stack Overflow, eg. this Node.js: Best way to perform multiple async operations, then do something else? but here anyways simplified implementations for both.
Using async
var async = require('async');
// ...
var tweets = function(y) {
return function(cb) {
client.get('statuses/user_timeline', {screen_name: y, count: 1},
function(err, data) {
// Process the data ...
cb(null, processedTweets);
}
);
}
};
var mentions = function(x) {
return function(cb) {
client.get('search/tweets', {q: x , count: 1},
function(err, data) {
// Process the data ...
cb(null, processedMentions);
}
);
}
};
app.get('/mytweetsapi', function(req, res) {
var tasks = [];
if (string.checktweet1 == 'on') {
tasks.push(tweets(string.teamname));
}
if (string.checkmentions1 == 'on'){
tasks.push(mentions(string.teamname));
}
if (string.checktweet2 == 'on'){
tasks.put(tweets(string.playername));
}
if (string.checkmentions2 == 'on'){
tasks.put(mentions(string.playername));
}
async.parallel(tasks, function(err, results) {
// Process the results array ...
res.json(processedResults);
});
});
Using promises
var Promise = require('bluebird');
// ...
var tweets = function(y) {
return new Promise(function(resolve, reject) {
client.get('statuses/user_timeline', {screen_name: y, count: 1},
function(err, data) {
// Process the data ...
resolve(processedTweets);
}
);
});
};
var mentions = function(x) {
return new Promise(function(resolve, reject) {
client.get('search/tweets', {q: x , count: 1},
function(err, data) {
// Process the data ...
resolve(processedMentions);
}
);
});
};
app.get('/mytweetsapi', function(req, res) {
var tasks = [];
// Same if this then tasks.push as in async example here
Promse.all(tasks).then(function(results) {
// Process the results array ...
res.json(processedResults);
});
});
I don't know what HTTP client you are using, but you can maybe use var client = Promise.promisifyAll(require('your-client-lib')); to convert the methods to return promises, and then you could convert the tweets and mentions functions to
var tweets = function(y) {
return client.get('statuses/user_timeline', {screen_name: y, count: 1});
};
This way though the results in Promise.all are mixed responses and you would need to identify which are tweets and which are mentions to process them properly.
For saving the data to a file without a database, you can use fs.writeFile():
fs.writeFile('message.txt', 'Hello Node.js', (err) => {
if (err) throw err;
console.log('It\'s saved!');
});
https://nodejs.org/api/fs.html#fs_fs_writefile_file_data_options_callback
Server/Node:
var app = require('http').createServer(handler)
var io = require('socket.io')(app);
var fs = require('fs');
app.listen(80);
function handler (req, res) {
fs.readFile(__dirname + '/index.html',
function (err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
});
}
io.on('connection', function (socket) {
if(twitterInfoReady){
socket.emit('twitterInfoIncoming', {
twitterInfo1: 'blah',
twitterInfo2: 'more data',
twitterInfo3: 'and more data'
});
}
});
Client
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io('http://localhost');
socket.on('twitterInfoIncoming', function (data) {
console.log(data.twitterInfo1);
});
</script>
Example taken from Socket.io docs, just modified a tiny bit http://socket.io/docs/

Cleaner code to save array data with mongoose

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)

JS - Express - Mongoose execute all mongoose's promises before send a response

Im doing a forum api, where a forum has many threads, thread has many posts and post could have many post.
The relations are done like this:
var PostSchema = new Schema({
text: String,
authorId: String,
slug: Number,
posts: [{ type: Schema.Types.ObjectId, ref: 'Post'}],
created: { type: Date, default: Date.now }
});
The parent model has a list of ids of son model.
I made my controller like this:
var util = require('util'),
mongoose = require('mongoose'),
Forum = mongoose.model('Forum'),
Thread = mongoose.model('Thread'),
Post = mongoose.model('Post'),
async = require('async');
exports.show = function(req, res){
var forums;
var getThreads = function(forum) {
return forum.populate('threads', function(err, _forum){
if(err) throw new Error(err);
forum.threads = _forum.threads;
forum.threads.forEach(getPosts);
return callback(err);
});
};
var getPosts = function(thread) {
return thread.populate('posts', function(err, _thread){
if(err) throw new Error(err);
thread.posts = _thread.posts;
thread.posts.forEach(getComments);
return callback(err);
});
};
var getComments = function(post) {
return post.populate('posts', function(err, _post){
if(err) throw new Error(err);
post.posts = _post.posts;
post.posts.forEach(getComments);
return callback(err);
});
};
async.parallel([
function(callback) {
return Forum.find({ ownerId: req.params.owner_id }).exec(function(err, _forums) {
if(err) throw new Error(err);
forums = _forums;
forums.forEach(getThreads);
return callback(err);
});
}
], function(err){
res.json(forums);
}
);
};
I need to made the complete forum object, then use this on the response, as posts has posts i cannot just do a nested populate.
I tried to use the async lib, but it execute the callback function before the promises.
How can i build the complete forum object?
You need to properly handle your tree structure in an asynchronous way. Try this approach:
(I haven't tested, but hope it works)
// ...
var Forum = mongoose.model('Forum');
exports.show = function(req, res){
//Get the owner's forums
Forum.find({ ownerId: req.params.owner_id }).exec(function(err, forums) {
if(err) throw new Error(err);
if(!forums.length) return response.json(forums); //Send an empty array if no forums where found
//Build forums one by one
var forum = forums.shift();
buildForum(forum, function () {
forum = forums.shift();
if (forum) {
buildForum(forum, this);
} else {
//All forums were built.
res.json(forums);
};
});
});
var buildForum = function (forum, onSuccess) {
forum.populate('threads', function(err, forum){
if(err) throw new Error(err);
if(!forum.threads.length) return onSuccess();
//Build threads one by one
var threads = forum.threads;
var thread = threads.shift();
buildThread(thread, function () {
thread = threads.shift();
if (thread) {
buildThread(thread, this);
} else {
//All threads were built.
onSuccess();
};
});
});
};
var buildThread = function (thread, onSuccess) {
thread.populate('posts', function(err, thread){
if(err) throw new Error(err);
if(!thread.posts.length) return onSuccess();
//Build posts one by one
var posts = thread.posts;
var post = posts.shift();
buildPost(post, function () {
post = posts.shift();
if (post) {
buildPost(post, this);
} else {
//All posts were built.
onSuccess();
};
});
});
};
var buildPost = function (post, onSuccess) {
post.populate('posts', function(err, post){
if(err) throw new Error(err);
if(!post.posts.length) return onSuccess();
//Build comments one by one
var posts = post.posts;
var _post = posts.shift();
buildPost(_post, function () {
_post = posts.shift();
if (_post) {
buildPost(_post, this);
} else {
//All comments were built.
onSuccess();
};
});
});
};
};
This is my solution, just a minor fixs in the #Danypype solution.
exports.show = function(req, res){
//Get the owner's forums
Forum.find({ ownerId: req.params.owner_id }).exec(function(err, forums) {
if(err) throw new Error(err);
if(!forums.length) return response.json(forums); //Send an empty array if no forums where found
//Build forums one by one
var forum = forums.shift();
var responseForums = [forum];
buildForum(forum, function () {
forum = forums.shift();
if (forum) {
responseForums.push(forum);
buildForum(forum, arguments.callee);
} else {
//All forums were built.
res.json(responseForums);
};
});
});
var buildForum = function (forum, onSuccess) {
forum.populate('threads', function(err, forum){
if(err) throw new Error(err);
if(!forum.threads.length) return onSuccess();
if(forum.length == 1) return onSuccess();
var thread = forum.threads.shift();
var responseThreads = [thread];
buildThread(thread, function () {
thread = forum.threads.shift();
if (thread) {
responseThreads.push(thread)
buildThread(thread, arguments.callee);
} else {
//All threads were built.
forum.threads = responseThreads;
onSuccess();
};
});
});
};
var buildThread = function (thread, onSuccess) {
thread.populate('posts', function(err, thread){
if(err) throw new Error(err);
if(!thread.posts.length) return onSuccess();
var post = thread.posts.shift();
var responsePosts = [post]
buildPost(post, function () {
post = thread.posts.shift();
if (post) {
responsePosts.push(post);
buildPost(post, arguments.callee);
} else {
//All posts were built.
thread.posts = responsePosts;
onSuccess();
};
});
});
};
var buildPost = function (post, onSuccess) {
post.populate('posts', function(err, post){
if(err) throw new Error(err);
if(!post.posts.length) return onSuccess();
//Build comments one by one
var _post = post.posts.shift();
var response_posts = [_post];
buildPost(_post, function () {
_post = post.posts.shift();
if (_post) {
response_posts.push(_post);
buildPost(_post, arguments.callee);
} else {
//All comments were built.
post.posts = response_posts;
onSuccess();
};
});
});
};
};

How can convert this node.async code to using q? Do I need to return a promise?

In "view" method within my controller was previously using node-async but I wanted to try out using q.
I'm currently trying to convert this
exports.view = function (req, res) {
var category = req.params.category,
id = req.params.id,
ip = req.connection.remoteAddress,
slug = req.params.slug,
submission,
userId = typeof req.session.user !== 'undefined' && req.session.user.id ? req.session.user.id : null,
views;
var getSubmission = function (submissionId, callback) {
Submission.getSubmission({
id: submissionId
}, function (err, submission) {
if (err) {
callback(err);
} else if (submission) {
callback(null, submission);
} else {
callback(err);
}
});
};
async.waterfall([
function (callback) {
getSubmission(id, callback);
},
function (submission, callback) {
res.render('submission', {
title: submission.title + ' -',
submission: submission
});
}]);
To using q... I started doing something like:
var getSubmission = function(id) {
return Submission.getSubmission({
id : submissionId
}).then(function(submission) {
return submission;
});
};
q.fcall(getSubmission).then(function(submission) {
console.log(submission);
});
But it's not quite working as I intended. Am I doing something wrong here? How can I do this?
Is Submission.getSubmission a call to a database? Then you can't "chain" promises to that. You'll have to use the deferred method:
var getSubmission = function(id) {
var deferred = Q.defer();
Submission.getSubmission({
id: id
}, function(err, data){
if (err) {
deferred.reject(err);
} else {
deferred.resolve(data);
}
});
return deferred.promise;
}
getSubmission(some_id).then(successCallback, failureCallback);
You can also use Q#denodeify to convert a function using nodejs-style callbacks (function(err, data)) into a promise based function. Thus, the above can also be achieved by the following:
getSubmissionPromise = Q.denodeify(Submission.getSubmission);
getSubmissionPromise({id: some_id}).then(successCallback, failureCallback);

Categories

Resources