I am going through the tutorials of nodejs and while learning about the shared state of module, i come through few doubts :
i have written this code :
Sparsh.js
var popcorn = require('./popcorn');
popcorn.favPopCorn = 'cheese';
console.log(popcorn.favPopCorn);
Ravi.js
var popcorn = require('./popcorn');
console.log(popcorn.favPopCorn);
app.js
require('./Ravi');
require('./Sparsh');
require('./Ravi');
popcorn.js
module.exports = {
printRate : function() {
console.log('popcorn rate');
},
favPopCorn : ''
};
output
(blank)
cheese
(blank)
As per the output the firstblank is considerable as we didn't assign any value to favPopCorn.But after i assign the value to favPopCorn in Sparsh.js.It should print 'cheese' while we again use require('.\Ravi.js'); as it is a shared state.
Please help
Ravi.js is also shared (well, "cached" is a better word), so it's loaded just once (the first time). The second time, Node knows that it was already loaded and doesn't load (/execute) it a second time (it doesn't output a blank, it doesn't output at all).
A common method to work around that is to export a function:
// Ravi.js
var popcorn = require('./popcorn');
module.exports = function() {
console.log(popcorn.favPopCorn);
}
// Sparsh.js
var popcorn = require('./popcorn');
module.exports = function() {
popcorn.favPopCorn = 'cheese';
console.log(popcorn.favPopCorn);
}
// app.js
require('./Ravi')();
require('./Sparsh')();
require('./Ravi')();
A clean pattern for this kind of think is to create an object with new in your shared module:
//popcorn.js
function popcorn(){
this.printRate = function() {
console.log('popcorn rate');
}
this.favPopCorn = ""
}
module.exports = new popcorn()
Now when you get and set the favPopCorn property in other modules, you will be manipulating a singleton.
Related
I have the following code that does not work currently.
var config = require('./libs/sequelize-lib.js');
var connection = config.getSequelizeConnection();//Choosing to not pass in variable this time since this should only run via script.
var models = config.setModels(connection);//Creates live references to the models.
//Alter table as needed but do NOT force the change. If an error occurs we will fix manually.
connection.sync({ alter: true, force: false }).then(function() {
models.users.create({
name: 'joe',
loggedIn: true
}).then( task => {
console.log("saved user!!!!!");
});
process.exit();//close the nodeJS Script
}).catch(function(error) {
console.log(error);
});
sequelize-lib.js
var Sequelize = require('sequelize');
exports.getSequelizeConnection = function(stage){
var argv = require('minimist')(process.argv.slice(2)); //If this file is being used in a script, this will attempt to get information from the argument stage passed if it exists
//Change connection settings based on stage variable. Assume localhost by default.
var dbname = argv['stage'] ? argv['stage']+"_db" : 'localdb';
var dbuser = argv['stage'] ? process.env.RDS_USERNAME : 'admin';
var dbpass = argv['stage'] ? process.env.RDS_PASSWORD : 'local123';
var dbhost = argv['stage'] ? "database-"+argv['stage']+".whatever.com" : 'localhost';
//If state variable used during require overide any arguments passed.
if(stage){
dbname = stage+"_db";
dbuser = process.env.RDS_USERNAME
dbpass = process.env.RDS_PASSWORD
dbhost = "database-"+stage+".whatever.com"
}
var connection = new Sequelize(dbname,dbuser,dbpass, {
dialect: 'mysql',
operatorsAliases: false, //This gets rid of a sequelize deprecated warning , refer https://github.com/sequelize/sequelize/issues/8417
host: dbhost
});
return connection;
}
exports.setModels = function(connection){
//Import all the known models for the project.
const fs = require('fs');
const dir = __dirname+'/../models';
var models = {}; //empty model object for adding model instances in file loop below.
//#JA - Wait until this function finishes ~ hence readdirSync vs regular readdir which is async
fs.readdirSync(dir).forEach(file => {
console.log(file);
//Split the .js part of the filename
var arr = file.split(".");
var name = arr[0].toLowerCase();
//Create a modle object using the filename as the reference without the .js pointing to a created sequelize instance of the file.
models[name] = connection.import(__dirname + "/../models/"+file);
})
//Showcase the final model.
console.log(models);
return models; //This returns a model with reference to the sequelize models
}
I can't get the create command to work however with this setup. My guess is the variables must not be passing through correctly somehow. I'm not sure what I'm doing wrong?
The create command definitely works because if in the sequelize-lib.js I modify the setModels function to this...
exports.setModels = function(connection){
//Import all the known models for the project.
const fs = require('fs');
const dir = __dirname+'/../models';
var models = {}; //empty model object for adding model instances in file loop below.
//#JA - Wait until this function finishes ~ hence readdirSync vs regular readdir which is async
fs.readdirSync(dir).forEach(file => {
console.log(file);
//Split the .js part of the filename
var arr = file.split(".");
var name = arr[0].toLowerCase();
//Create a modle object using the filename as the reference without the .js pointing to a created sequelize instance of the file.
models[name] = connection.import(__dirname + "/../models/"+file);
models[name].create({
"name":"joe",
"loggedIn":true
});
})
//Showcase the final model.
console.log(models);
return models; //This returns a model with reference to the sequelize models
}
Then it works and I see the item added to the database! (refer to proof image below)
Take note, I am simply running create on the variable at this point. What am I doing wrong where the model object is not passing between files correctly? Weird part is I don't get any errors thrown in the main file?? It's as if everything is defined but empty or something and the command is never run and nothing added to the database.
I tried this in the main file also and no luck.
models["users"].create({
name: 'joe',
loggedIn: true
}).then( task => {
console.log("saved user!!!!!");
});
The purpose of this all is to read models automatically from the model directory and create instances that are ready to go for every model, even if new one's are added in the future.
UPDATE::
So I did another test that was interesting, it seems that the create function won't work in the .then() function of the sync command. It looks like it was passing it correctly though. After changing the front page to this...
var config = require('./libs/sequelize-lib.js');
var connection = config.getSequelizeConnection();//Choosing to not pass in variable this time since this should only run via script.
var models = config.setModels(connection);//Creates live references to the models using connection previosly created.
models["users"].create({
"name":"joe",
"loggedIn":true
});
//Alter table as needed but do NOT force the change. If an error occurs we will fix manually.
connection.sync({ alter: true, force: false }).then(function() {
process.exit();//close the nodeJS Script
}).catch(function(error) {
console.log(error);
});
Doing this seems to get create to work. I'm not sure if this is good form or not though since the database might not be created at this point? I need a way to get it to work in the sync function.
Well I answered my question finally, but I'm not sure I like the answer.
var config = require('./libs/sequelize-lib.js');
var connection = config.getSequelizeConnection();//Choosing to not pass in variable this time since this should only run via script.
var models = config.setModels(connection);//Creates live references to the models using connection previosly created.
//Alter table as needed but do NOT force the change. If an error occurs we will fix manually.
connection.sync({ alter: false, force: false }).then( () => {
models["users"].create({
"name":"joe",
"loggedIn":true
}).then( user => {
console.log("finished, with user.name="+user.name);
process.exit();
}).catch( error => {
console.log("Error Occured");
console.log(error);
});
}).catch(function(error) {
console.log(error);
});
turns out that process.exit was triggering before create would occur because create happens async. This means that all my code will have to constantly be running through callbacks...which seems like a nightmare a bit. I wonder if there is a better way?
I'm having hard time figuring out why my module isnt sigleton, as it should be by default, and why its variables suddenly resetting. Any attempts to make a variable a member of the module.exports object got the same result with unpredictable behavior.
This is completely baffling to me and I would be grateful for any help.
//target module, ./api/models/person/index.js:
"use strict";
module.exports = {};
var own_counter = 0;
module.exports.setup = function () {
console.log('setup, pre own_counter = ' + own_counter);
++own_counter;
console.log('setup, post own_counter = ' + own_counter);
}
//first use, ./api/hooks/sequelizeConnecionsInit/index.js:
...
initialize: function(done){
var person_orm = require('../../models/person');
person_orm.setup();
person_orm.setup();
return done();
},
...
//second use, ./node_modules/sails-userhooks-ws/index.js:
...
var person_orm = require('../../api/models/person');
person_orm.setup();
...
//(its wrong to write code to node_modules, but I havent figured out yet how to use it with my code without module code change)
//Output:
...
setup, pre own_counter = 0
setup, post own_counter = 1
setup, pre own_counter = 1
setup, post own_counter = 2
...
setup, pre own_counter = 0 //Why resetted???
setup, post own_counter = 1
...
The reason was here:
http://justbuildsomething.com/node-js-best-practices/#2
In the first and second using too I called require inside the functions, so when I moved it to head (global scope?), my problem resolved.
I'm writing a nodeJs application that uses google flat buffer.
I installed flatc on my macbook pro and compiled the following schema:
namespace MyAlcoholist;
table Drink {
drink_type_name: string;
drink_company_name: string;
drink_brand_name: string;
drink_flavor_type_name : string;
liquid_color_type_name : string;
liquid_color_is_transparent : bool;
alcohol_vol : float;
calories_for_100g : uint;
global_image_id: ulong;
drink_flavor_id: ulong;
}
table Drinks { drinks:[Drink]; }
root_type Drinks;
the schema file is called drink.fbs and it generated a javascript file called drink_generated.js
I include this file in my nodejs application and add data to it using the following code.. this is my flatBufferUtil.js utility file.
var flatbuffers = require('../js/flatbuffers').flatbuffers;
var builder = new flatbuffers.Builder();
var drinks = require('../fbs/drinks_generated').MyAlcoholist; // Generated by `flatc`.
function drinkArrayToBuffer(drinkArray) {
var drinksVectArray = [];
drinkArray.forEach(function (element, index, array) {
var drinkObj = element;
var drinkBrandName = builder.createString(drinkObj.drink_brand_name);
var drinkCompanyName = builder.createString(drinkObj.drink_company_name);
var drinkflavorTypeName = builder.createString(drinkObj.drink_flavor_type_name);
var drinkTypeName = builder.createString(drinkObj.drink_type_name);
var liquidColorTypeName = builder.createString(drinkObj.liquid_color_type_name);
drinks.Drink.startDrink(builder);
drinks.Drink.addAlcoholVol(builder, drinkObj.alcohol_vol);
drinks.Drink.addCaloriesFor100g(builder,drinkObj.calories_for_100g);
drinks.Drink.addDrinkBrandName(builder,drinkBrandName);
drinks.Drink.addDrinkCompanyName(builder,drinkCompanyName);
drinks.Drink.addDrinkFlavorId(builder,drinkObj.drink_flavor_id);
drinks.Drink.addDrinkFlavorTypeName(builder, drinkflavorTypeName);
drinks.Drink.addDrinkTypeName(builder,drinkTypeName);
drinks.Drink.addGlobalImageId(builder,drinkObj.global_image_id);
drinks.Drink.addLiquidColorIsTransparent(builder,drinkObj.is_transparent);
drinks.Drink.addLiquidColorTypeName(builder,liquidColorTypeName);
var drink = drinks.Drink.endDrink(builder);
drinksVectArray.push(drink);
})
var drinksVect = drinks.createDrinksVector(builder,drinksVectArray);
builder.finish(drinksVect);
var buf = builder.dataBuffer();
return buf;
}
module.exports.drinkArrayToBuffer=drinkArrayToBuffer;
now when I execute this function it fails with the error flatbuffers is not defined.
I debugged my code and I saw that it files on the following line of code:
drinks.Drink.addDrinkFlavorId(builder,drinkObj.drink_flavor_id);
if i get inside addDrinkFlavorId function i see this code in drinks_generted.js:
MyAlcoholist.Drink.addDrinkFlavorId = function(builder, drinkFlavorId) {
builder.addFieldInt64(9, drinkFlavorId, flatbuffers.Long.ZERO);
};
as you can see it uses flatbuffers.Long.ZERO but flatbuffers is not defined in that file at all. the compilation did not provide any errors so what do I miss?
It seems to me like it is a bug... The generated file appears to be meant to exist autonomously from the flatbuffers require. However for the custom flatbuffers.Long class, the default of flatbuffers.Long.ZERO bleeds into the generated file.
While this isn't a solution per-say, one workaround is to manually add the flatbuffers require to the generated file; it's ugly, but it might be better than being blocked until a more appropriate answer (or fix) comes around.
// In `drinks_generated.js`
var flatbuffers = require('../js/flatbuffers').flatbuffers;
Note:
The drinks.Drink.addDrinkFlavorId() and drinks.Drink.addGlobalImageId() functions expect flatbuffers.Long values to be passed into them, because they were specified as ulong in the schema (fbs file). So you will need to ensure that you are not trying to pass in a simple number type.
For example:
var my_long = flatbuffers.Long(100, 0); // low = 100, high = 0
drinks.Drink.addDrinkFlavorId(builder, my_long);
As a result, another possible workaround is to change the datatype of those fields in the schema to avoid using ulong until it becomes more clear what is going on here.
P.S. I am pretty sure drinks.createDrinksVector on line 30 of that snippet should be drinks.Drinks.createDrinksVector.
I have multiple nightwatch tests with setup and teardown in every single test. I am trying to unify it into globalModule.js in before after(path set in globals_path in nightwatch.json).
//globalModule.js
before:function(test, callback){
// do something with test object
}
//sampletest.js
before: function(test){
..
},
'testing':function(test){
....
}
My problem is test context is not available in globalsModule.js. How do i get it there? Can someone let me know?
Test contex not available now. As said beatfactor, it will available soon.
While it not available try use local before first file, but it hack.
Also you can export all your file into one object and export it into nightwatch, but then you can use local before just in time.
For example:
var tests = {};
var befores = [];
var fs =require('fs');
var requireDir = require('require-dir');
var dirs = fs.readdirSync('build');
//if you have dirs that should exclude
var usefull = dirs.filter(function(item){
return !(item=='data')
});
usefull.forEach(function(item){
var dirObj = requireDir('../build/' + item);
for(key in dirObj){
if(dirObj.hasOwnProperty(key))
for(testMethod in dirObj[key])
if(dirObj[key].hasOwnProperty(testMethod))
if(testMethod == 'before')
befores.push(dirObj[key][testMethod]);
else
tests[testMethod] = dirObj[key][testMethod];
}
});
tests.before = function(browser){
//some global before actions here
//...
befores.forEach(function(item){
item.call(tests,browser);
});
};
module.exports = tests;
For more information https://github.com/beatfactor/nightwatch/issues/388
Scroll down to the bottom of this post to see a work around / possible solution.
This is probably easier just to explain in the source code with comments. The issue at hand is I cannot figure out how pseudo classes work together to perform the task I'm trying to do (explained in the code below).
The code is broken down into 3 files: lead.js, router.js, and db.js.
There are a decent amount of lines of code but most of it is comments.
[lead.js]
var bcrypt = require('bcrypt'),
validators = require('../lib/validators'),
utility = require('../lib/utility'),
document = {};
var Lead = module.exports = function (db) {
// Save a reference to the database.
this.db = db;
// Reference initial document.
// This is totally wrong, not sure how to 'send' a variable to the constructor of a class
// when I cannot add another param. Due to how I'm importing the db model, I won't know what
// the document is until I fill out the form. I've also tried 'document' instead of 'Lead.document'.
this.document = Lead.document;
// Setup the document if it exists.
// This also doesn't work.
// Basically I want to be able to set up a document variable outside of this module (line #100),
// Then pass it to this module after filling it up with values from a form.
// Then based on what's been filled in, it would fix up (trim, convert to lower case)
// some of the values automatically and default a few values that I'm not always going to pass.
if (!document) {
var salt = bcrypt.genSaltSync(10),
hash = bcrypt.hashSync(utility.generatePassword(), salt);
// Default values.
if (!document.meta.createdAt) { this.document.meta.createdAt = Date.now(); }
if (!document.login.password) { this.document.login.password = hash; }
if (!document.login.role) { this.document.login.role = 'User'; }
// Normalize a few values.
this.document.login.email = document.login.email.toLowerCase().trim();
this.document.contact.name.first = document.contact.name.first.trim();
this.document.contact.name.last = document.contact.name.last.trim();
this.document.contact.address.street = document.contact.address.street.trim();
this.document.contact.address.city = document.contact.address.city.trim();
this.document.contact.address.state = document.contact.address.state.trim();
this.document.contact.address.zip = document.contact.address.zip.trim();
this.document.contact.phone.home = document.contact.phone.home.trim();
}
// So in regards to the above code, the end result I'm looking for is...
// I want to append some properties to the this.document reference when the document is empty (when I'm updating it, I won't set the document),
// and on new documents it will append a few default values/normalize all the fields.
};
Lead.prototype.validate = function(fn) {
var errors = [];
// Some validation rules I cut out to make this shorter.
if (errors.length) return fn(errors);
fn();
};
Lead.prototype.save = function(fn) {
this.db.collection('leads', function(err, collection) {
if (err) { fn(new Error({message: err})); }
collection.insert(this.document, function(err, result) {
return fn(err, result);
});
});
};
---
[route.js file]
var db = require('../models/db');
app.post('/register', function(req, res) {
var data = req.body.lead || {};
// Fill the document.
var document = {
meta: {
host: req.headers.host,
referer: req.headers.referer,
createdIPAddress: req.connection.remoteAddress
},
login: {
email: data.email
},
contact: {
name: {
first: data.first,
last: data.last
},
address: {
street: data.street,
city: data.city,
state: data.state,
zip: data.zip
},
phone: {
home: data.phone
}
}
};
// Write the document.
db.lead.document = document;
db.lead.validate(function(err) {
if (err) {
req.session.error = err;
return res.redirect('back');
}
db.lead.save(function(err) {
res.redirect('/register/success');
});
});
});
---
[db.js]
var mongodb = require('mongodb'),
server = new mongodb.Server('localhost', 27017),
connection = new mongodb.Db('test', server);
connection.open(function(err, db) {});
module.exports = {
lead: new (require('./lead'))(connection)
};
When I run this, my validator always reports that the password is empty which makes sense. I'm sending the document initially to the class with an empty password (the password is randomly generated, not a form field) -- the problem is I have no idea what to do with the if (!document) ... code block to actually set the this.document properly.
I hope between the comments and code you can get an idea of what I'm trying to do. I've been stuck on this for a while.
EDIT
I changed the flow of it a bit to get a solution.
In the db.js, I exported the connection rather than instantiating the lead (and future models) directly.
In the router.js file, I require the db and lead file, then pass both the db connection and the document in the constructor of the Lead. Ex.
var lead = new Lead(db, document);
In the lead.js file, it becomes as simple as doing this.document = document (same as the db). When I submit a new lead, the values I don't send over from router.js get appended to the document (the created date, a random password, etc.) and everything is good.
Is this a decent way of handling this, or is there a better way to refactor this?
This is completely wrong way even if make this code work as you want. In this example you have singleton lead. By requesting /register url you want to set 'document' field to this singleton . (IMPORTANT) But requests work asynchronously. Absolutely no guarantee that you save the document, which has just validate. Because new request may overwrite it in lead object. You need to do this logic in request scope. One scope for one request. Not one for all.
You need to read up on object-oriented programming in Javascript.
The anonymous function you're defining near the top of your code is the constructor function, so with respect to the document property you want that is currently uninitialized, just type something like:
this.document = null;
Then some time later when you create a new object using this constructor, like so:
var myLead = new Lead(dbConnection);
You'll have the myLead.document property.
There are many other things wrong with your code, though. Why are you assuming that there is a global document variable with relevant data visible in your library when it's defined as {}? The code in that if statement at the end of your constructor should be run when the document property is set in your other file below, and should only expect this.document to exist.
You set var document = {} initially, and {} is not falsy. Better would be to set as a starting value document = null and then after checking for !document set document = {} before assigning whatever properties you need.