Unexpected value for 'this' in express.js instantiated controllers - node.js

'this' does not appear to refer to the instantiated budget controller object. Instead it seems to refer to the global object. Does anyone know why this is?
I've defined a budget model. Injected into the controller and I'm attempting to simply generate a random 6 char string when I hit /budgets in my app. Instead this.DEFAULT_SLUG_LENGTH is undefined and I can't figure out why.
This is a dumbed down test case illustrating the issue with 'this'. I have a similar problem when referencing the injected this.budget within another function to query the db based on the slug value.
//models/budget.js
var Schema = require('jugglingdb').Schema;
var schema = new Schema('postgres',{url:process.env.DATABASE_URL});
var Budget = schema.define('budgets',{
total: Number,
slug: String
});
module.exports = Budget;
====================
//controllers/budget.js
function BudgetController (budget) {
this.budget = budget;
};
BudgetController.prototype.DEFAULT_SLUG_LENGTH = 6;
BudgetController.prototype.generateSlug = function (req,res) {
var slug = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < this.DEFAULT_SLUG_LENGTH; i++) {
slug += possible.charAt(Math.floor(Math.random() * possible.length));
}
res.send(slug);
};
module.exports = BudgetController;
===================
//app.js
var express = require('express');
var app = express();
app.use(express.bodyParser());
// models
var Budget = require('./models/budget');
// controllers
var BudgetController = require('./controllers/budget');
var budgetCtrl = new BudgetController(Budget);
// routes
app.get('/budgets',budgetCtrl.generateSlug);
app.listen(process.env.PORT || 4730);
If I manually instantiate the model/controller in the node repl, the generateSlug method works fine. If I restructure my code so that the BudgetController is a function that returns an object {} with methods, that seems to work fine. Is there some issue with my use of prototype/new ?

express takes functions and invokes them without a preceding object, so if you want to use an object method bound to a specific this as an express route handler function, you need to bind it:
app.get('/budgets', budgetCtrl.generateSlug.bind(budgetCtrl));

Related

node.js module export how to use data amoung all modules?

I would like to use updated (and only then) globals among all node modules. How to do that? Questions are in code.
app.js
var data = 'data';
var global = require('glob.js')(data);
// here we are require your globals variables and we corectly 'set them'
console.log(globals.glob1);
// we can use them here
glob.js
module.exports = function(data)
{
var globs = {
glob1 : data.toLowerCase(),
glob2 : data.toUpperCase()
}
return globs;
}
mod.js
var global = require('glob.js'); // I require globals but they are not set...
function funct(someOtherData, someMoreData)
{
var test = global.glob1;
console.log(test);
// why I can't use globals here ? How can I use corectly set globals (globals need to be updated first - app.js, then ALL other modules should be able to use correctly set globals)?
}
module.export = funct;
For the answer scroll down to the TLDR section below but do read on to understand why.
Part1 - the difference between a function and a function call
Your first mistake is that you are exporting a function, not an object:
module.exports = function(data) // <---- this is a function
{
var globs = {
glob1 : data.toLowerCase(),
glob2 : data.toUpperCase()
}
return globs;
}
and in app.js you do this:
console.log(globs.glob1); <--- globs is a function, not an object
when you should be doing this:
console.log(globs().glob1);
Why is this? OK, lets forget for a moment your module. Consider the following code:
var a = function(){ return 2 };
console.log(a); // do you expect this to print a function or 2?
console.log(a()); // what do you expect this to print?
This is a very basic rule about functions in all programming languages, not just javascript: to get the return value you need to call the function. So in your code:
function myExportedFunction (data) {
// some logic here...
return globs;
}
console.log(myExportedFunction); // prints a function
console.log(myExportedFunction()); // prints the globs object
console.log(myExportedFunction().glob1); // prints value of glob1
So it's simple really. There is no magic syntax going on. You've just forgotten to return the glob object and are using the function pointer instead. Obviously the function has no glob1 property so it's correct for it to be undefined.
Part2 - function local variables
OK. So let's say you made the changes I recommended above. There's an obvious problem with the way the function was written. What happens when you do this:
var glob = require('glob.js')();
console.log(glob.glob1); // <--- prints "undefined"
So the first problem is you're not checking if you're passing data or nothing. So every time you call the function you will overwrite the stored value.
There's another problem, you are always returning a different object every time you call the function. Let's look at how local variables work when returned:
function a () {
var data = {}
return data;
}
var x = a();
var y = a();
x.testing = 1;
y.testing = 2;
console.log(x.testing); // prints 1
console.log(y.testing); // prints 2
So, every time you call a function that creates a local variable you are returning a different object. Actually what's doing this is not really the variable but the object literal syntax:
var a = {};
// is basically the same as
var a = new Object();
If we change the above example to:
function a () {
return {};
}
it would still behave the same.
TLDR
So, how do we fix it? Simple, create the object outside of the function and check if we pass data to initialize:
var globs = {
glob1 : "",
glob2 : ""
}
module.exports = function(data)
{
globs.glob1 = data.toLowerCase();
globs.glob2 = data.toUpperCase();
return globs;
}
Now everything should work:
In app.js
var global = require('glob.js')(data);
In mod.js
var global = require('glob.js')();
Epologue - modules are singletons
It may or may not be obvious to you why the above should work. In case you already know why I'm writing this as reference to future readers.
In node.js modules are implemented as proper singletons. Therefore in node if you want a singleton all you need to do is write a module, you don't need to implement any special code for it.
What this means is that all module globals (module scoped variables) are shared amongst all requires. Here's a very simple module to share one variable amongst all modules:
shared.js
var x = "";
module.exports = {
set: function (val) {x=val},
get: function () {return x}
}
a.js
var shared = require('./shared');
shared.set("hello world");
b.js
var shared = require('./shared');
console.log(shared.get()); // prints "hello world"
We're using this feature to declare a shared glob variable in the code above.
You can use the global. variable identifier to set global variables in NodeJS, instead of var, example:
app.js
var data = 'data';
var glob = require('./glob.js');
glob(data);
// here we are require your globals variables and we corectly 'set them'
console.log(global.gl.glob1);
var mod = require('./mod.js');
mod();
// we can use them here
glob.js
module.exports = function(data)
{
console.log("setting globals");
global.gl = {
glob1 : '1' + data,
glob2 : '2' + data
}
// return global.gl; // can be removed
}
mod.js
function funct(someOtherData, someMoreData)
{
var test = global.gl.glob1;
console.log(test);
test = global.gl.glob2;
console.log(test);
// why I can't use globals here ? How can I use corectly set globals (globals need to be updated first - app.js, then ALL other modules should be able to use correctly set globals)?
}
module.exports = funct;
As you can see in glob.js, i switched to var globs = to global.gl = and then in mod.js used it as global.gl.
Running app.js outputs:
setting globals
1data // From app.js
1data // From mod.js imported in app.js
2data // From mod.js imported in app.js
There are 2 options:
Use nodejs global variable (not recommended)
Create shared module
You chose 2nd option, but did it a bit wrong way by exporting a function. When you import the package and call the function it always creates new globs object and fulfill it with your data. Instead you can export an object. Simple example
glob.js
Global object is defined here
module.exports = {
glob1: '1',
glob2: '2'
};
mod.js
You can change global object here, like
var globs = require('./glob');
module.exports.updateGlob1 = function(data) {
globs.glob1 = data;
};
app.js
Here if you access global variable you can see it updated
var globs = require('./glob');
var mod = require('./mod');
mod.updateGlob1('1 plus 2');
console.log(globs.glob1); // Output: '1 plus 2'
There can be more complex examples, as for module design pattern often IIFE is used.
UPDATE
Another example using IIFE.
glob.js
module.exports = (function() {
var glob1 = 'initial value';
return {
// Getter method
getGlob1() {
return glob1;
},
// Setter method
setGlob1(value) {
glob1 = value;
}
}
})();
mod.js
var shared = require('./shared');
module.exports.testFn = function() {
// Access global variable with getter method
console.log('In mod.js', shared.getGlob1());
};
app.js
var shared = require('./shared');
var mod = require('./mod');
// Print initial value
console.log('Initial', shared.getGlob1());
// Set new value to global variable
shared.setGlob1('new value');
// Print updated value
console.log('In app.js', shared.getGlob1());
// Use global variable in mod.js file
mod.testFn();

Access variable in main node file from an imported file

//in app.js
var x = require("x.js");
var instanceX = new x();
require("./Weather")();
//in Weather.js
instanceX.getName();
In this case instanceX wouldn't exist when referenced from Weather.js. How do I make instanceX accessible in Weather.js?
There are a number of different ways to approach this in module design. One simple way is to just pass the variable to the weather.js constructor:
//in app.js
var x = require("x.js");
var instanceX = new x();
require("./Weather")(instanceX);
//in Weather.js
var instanceX;
module.exports = function(ix) {
instanceX = ix;
}
// then elsewhere in the module
instanceX.getName();
I refer to this as the "push" model because you're pushing things to the module that you want to share with it.

Node js put schemas in other folder

i have created basic node project in which i created model folder inside model folder i have schema folder and index.js
in schema folder have all schema of my project and in index.js i write some thing like this
var s1 = require('./schema/schema1');
var s2 = require('./schema/schema2');
module.export = function(app) {
schema1= s1 ;
schema2= s2 ;
}
and in controller file i m accessing model like this
var model = require('./models/');
model.schema1.find(function(err, data) {
})
but this above code not working its give me error TypeError: Cannot read property 'find' of undefined but when i try something like this in controller
var schema1= require('../models/schema/schema1');
schema1.find(function(err,data){});
its working fine but i want 1st structure in my project i using mongoose, express, node, mongodb.
Please help i don't know what i m doing wrong
Your index.js exports a function returning nothing. Either do:
var s1 = require('./schema/schema1');
var s2 = require('./schema/schema2');
module.exports = function (app) {
return {
schema1: s1,
schema2: s2
}
}
Then require like this: var model = require('./models/')(); (notice the (), it's a function, you must call it to get the returned object).
Or, if you don't use the app parameter at all:
var s1 = require('./schema/schema1');
var s2 = require('./schema/schema2');
module.exports = {
schema1: s1,
schema2: s2
}
Then you can simply var model = require('./models/'); (no () here)).
You also had a typo on module.exports, you forgot the s.
pass your Schema to second file do something like this in first file :
var mySchema = new Schema({
// my props
});
require('./app/anotherFolder.js')(mySchema)

Node.js - passing object with require

I am pretty certain there is a way to pass a variable using require.
So it would look something like this:
var model = require('model')(mongoose);
With the above line of code, I want to pass my model file my database information (mongoose), so that if I access the same model with a different database, I can pass it different database information.
However, even if the above syntax is correct, I am not sure what my model file itself would have to look like. Can anyone help me out with this?
module.exports = function (mongoose) {
// . . .
return model;
};
You can pass moongoose by argument to that file
var model = require('model')(mongoose);
Your module will look like this, you can make an object in module.exports and can attach multiple properties to that object and in the end return it from the function
module.exports = function (mongoose) {
model ={};
model.properties = {};
model.yourfunction1 = function(){};
return model;
};
I guess I can't assign anything else to module.exports in this case?
Answer to your comment is explained below
Choosing between module.exports and exports depends on you
For exports
exports.object1 = {};
exports.object2 = {};
For module.exports
module.exports = function(){
myobj={}
myobj.object1 = {};
myobj.object2 = {};
return myobj
}
Now calling it will be different
For exports it will be directly available on file variable
var file = require('./file');
console.log(file.object1);
For module.exports you will execute it like a function by appending function parenthesis so that object can be returned
var file = require('./file')();
console.log(file.myobj.object1);

How to stub out express after you require it with jasmine?

I'm trying to get the code below under test when occurred to me that I already included express at the top of this file. Can you some how monkey patch the express object after it's already loaded?
var express = require('express')
Helper = (function() {
var HelperObject = function(params) {
this.directories = params.directories;
};
HelperObject.prototype.addStaticPath = function(app) {
for(i = 0; i < this.directories.length; i++) {
var static = express.static('/public');
app.use(static);
}
};
return HelperObject;
})();
The problem is that when you create a node module the required modul is bound in the closure of the module and you can't start spying on it cause it isn't visible in your test.
There is Gently where you can override require but it will sprinkle your code with boilerplate test related code.
From the docs:
Returns a new require functions that catches a reference to all
required modules into gently.hijacked.
To use this function, include a line like this in your 'my-module.js'.
if (global.GENTLY) require = GENTLY.hijack(require);
var sys = require('sys');
exports.hello = function() {
sys.log('world');
};
Now you can write a test for the module above:
var gently = global.GENTLY = new (require('gently'))
, myModule = require('./my-module');
gently.expect(gently.hijacked.sys, 'log', function(str) {
assert.equal(str, 'world');
});
myModule.hello();

Resources