cramit
Version:
<a href="https://nodei.co/npm/cramit/" target="_blank"><img src="https://nodei.co/npm/cramit.png?downloads=true&downloadRank=true"></a>
693 lines (613 loc) • 20.4 kB
JavaScript
/* ************************************************** *
* ******************** Library Global Variables
* ************************************************** */
var async = require('async'),
crave = require('crave'),
path = require('path'),
_ = require('lodash');
// Library component locations.
var fixturesFolder = path.resolve(__dirname, '.'+path.sep+'fixtures') + path.sep;
databaseAdaptersFolder = path.resolve(__dirname, '.'+path.sep+'databaseAdapters') + path.sep;
// Default configuration object.
var defaultConfig = {
database: {
connectionUri: undefined,
idAttributeName: undefined,
instance: undefined,
type: undefined
},
crave: {
cache: { // Crave can store the list of files to load rather than create it each time.
enable: false // Disable caching of the list of files to load. In production this should be enabled.
},
identification: { // Variables related to how to find and require files are stored here.
type: "filename", // Determines how to find files. Available options are: 'string', 'filename'
identifier: "_" // Determines how to identify the files.
}
},
fixture: {},
fixtureFileSuffix: "fixture"
};
var defaultLogConfig = {
level: 40, //Information about Levels https://github.com/trentm/node-bunyan#levels
name: 'Cramit'
};
/* ************************************************** *
* ******************** Constructor
* ************************************************** */
/**
* Constructor to setup and initialize a new or existing
* instance.
*
* @param {object|undefined} config is a cramit configuration object.
* @param {object|undefined} log is a bunyan instance.
* @param {object|undefined} error is a cramit-error instance.
* @returns {object} the new or current Cramit instance.
* @constructor
*/
var Cramit = function(config, log, error) {
"use strict";
// Auto instantiate the module when it is required.
if(! (this instanceof Cramit)) {
return new Cramit(config, log, error);
} else {
// Initalize the class with the passed parameters.
this.setConfig(config, true);
this.setLog(log);
this.setError(error);
// Initialize the fixture class with the cramit instance.
this.Fixture = (require(fixturesFolder + 'index.js'))(this);
return this;
}
};
/* ************************************************** *
* ******************** Initalize and Set Methods
* ************************************************** */
/**
* Set and apply new configurations for Cramit.
* Any attribute included in the configuration
* object will overwrite the existing attribute.
*
* A config value of undefined will reset the
* configuration object to the default settings.
*
* @param {object|undefined} config is a Cramit
* configuration object.
* @param {boolean|undefined} initalize when true
* will set the cramit's config instance to the
* default settings before applying the passed
* configuration values.
*/
Cramit.prototype.setConfig = function(config, initalize) {
if(initalize && ! this.config) {
this.config = JSON.parse(JSON.stringify(defaultConfig));
}
if( ! config || ! _.isObject(config) && ! initalize) {
this.config = JSON.parse(JSON.stringify(defaultConfig));
} else {
for(var key in config) {
switch(key) {
case "database":
case "crave":
for(var subObjectKey in config[key]) {
this.config[key][subObjectKey] = config[key][subObjectKey];
}
break;
default:
this.config[key] = config[key];
break;
}
}
}
this.setDatabaseAdapter();
};
/**
* Set the database instance to the specified value.
* @param databaseInstance is the new database instance value.
*/
Cramit.prototype.setDatabaseInstance = function(databaseInstance) {
if( ! this.config || ! _.isObject(this.config)) {
this.config = JSON.parse(JSON.stringify(defaultConfig));
}
this.config.database.instance = databaseInstance;
this.setDatabaseAdapter();
};
/**
* Set or configure the Cramit bunyan log instace.
*
* Passing a value of undefined for both the config
* and log parameters will initalize a new bunyan
* log instance with the default values.
*
* @param {object|undefined} config is a bunyan
* configuration object.
* @param {object|undefined} log is a bunyan instance.
*/
Cramit.prototype.setLog = function(config, log) {
if(log) {
this.log = log;
} else {
var bunyan = require('bunyan');
this.log = bunyan.createLogger(config || defaultLogConfig);
}
};
/**
* Set or configure the Cramit error object.
* The error object is used to build and display
* errors that occur in Cramit.
*
* Passing a value of undefined for the error
* object will reset the error object to the
* default.
*
* @param {object|undefined} error is an object
* with methods related to building error objects.
*/
Cramit.prototype.setError = function(error) {
if(error) {
this.error = error;
} else {
this.error = {
build: function(message, code) {
var err = new Error(message);
err.status = code || 500;
return err;
}
};
}
};
/**
* Set the database adapter to interact with the desired
* database.
*
* If the configuration object is undefined, then the current
* configuraiton object will be used.
*
* @param {undefined|object} config is a cramit configuration
* object.
*/
Cramit.prototype.setDatabaseAdapter = function(config) {
var cramit = this;
config = config || cramit.config;
switch((config && config.database && config.database.type) ? config.database.type.toLowerCase(): "") {
case 'mongoose':
if( ! cramit.MongooseAdapter) {
cramit.MongooseAdapter = require(databaseAdaptersFolder + 'mongoose.js');
}
cramit.databaseAdapter = cramit.MongooseAdapter(cramit);
break;
default:
if( ! cramit.DatabaseAdapter) {
cramit.DatabaseAdapter = require(databaseAdaptersFolder + 'index.js');
}
cramit.databaseAdapter = cramit.DatabaseAdapter(cramit.config, cramit.log);
break;
}
};
/* ************************************************** *
* ******************** Public API
* ************************************************** */
/**
* Uses the module Crave to find all fixtures in a given
* directory.
*
* You can configure how Crave will find the fixtures
* by passing in a configuration object as the options
* parameter. The key 'crave' will be passed directly
* into the Crave module.
*
* The options object will also be passed as a parameter
* into each fixture that is loaded. However the crave
* key will be removed.
*
* @param {string} applicationPath is the path to your
* application's files where the fixtures can be found.
* @param {object|undefined} options configure the fixtures
* and how they will be loaded.
* @param {fixtureCallbackMethod} cb is a callback method.
*/
Cramit.prototype.findAllFixtures = function(applicationPath, options, cb) {
var cramit = this,
fixtures = {};
// Callback method is required, otherwise calling this method would be useless.
if( ! cb) {
return this.log.fatal(this.build.error('Callback is a required parameter for the cramit.finalAllFixtures() method.'), 500);
}
// Combine the default configurations with the options specified.
if(options && options.crave) {
crave.setConfig(options.crave);
delete options.crave;
} else {
crave.setConfig(cramit.config.crave);
}
// Format the returned data object into a fixtures object, with
// the fixture ID as the key and the fixture class as the value.
var formatFixtures = function(err, files, results) {
if(err) {
cb(err);
} else {
for(var i = 0; i < results.length; i++) {
if(results[i] !== undefined && results[i].error === undefined && results[i].id !== undefined) {
fixtures[results[i].id] = results[i];
}
}
cb(undefined, fixtures);
}
};
// Recursively load all data files that are located in the apps folder.
crave.directory(applicationPath, [cramit.config.fixtureFileSuffix], formatFixtures, cramit, options);
};
/**
* Uses the module Crave to find all fixtures in a given
* directory. Then inserts all data found in the fixtures.
*
* You can configure how Crave will find the fixtures
* by passing in a configuration object as the options
* parameter. The key 'crave' will be passed directly
* into the Crave module.
*
* The options object will also be passed as a parameter
* into each fixture that is loaded. However the crave
* key will be removed.
*
* @param {string} applicationPath is the path to your
* application's files where the fixtures can be found.
* @param {object|undefined} options configure the fixtures
* and how they will be loaded.
* @param {fixtureCallbackMethod} cb is a callback method.
*/
Cramit.prototype.findAllFixturesAndInsertData = function(applicationPath, options, cb) {
var cramit = this;
if( ! cb) {
cb = function(err) { if(err) { this.log.error(err); } };
}
cramit.findAllFixtures(applicationPath, options, function(err, fixtures) {
if(err) {
cb(err);
} else {
cramit.insertFixtureData(fixtures, cb);
}
});
};
/**
* Uses the module Crave to find all fixtures in a given
* directory. Then deletes all data found in the fixtures.
*
* You can configure how Crave will find the fixtures
* by passing in a configuration object as the options
* parameter. The key 'crave' will be passed directly
* into the Crave module.
*
* The options object will also be passed as a parameter
* into each fixture that is loaded. However the crave
* key will be removed.
*
* @param {string} applicationPath is the path to your
* application's files where the fixtures can be found.
* @param {object|undefined} options configure the fixtures
* and how they will be loaded.
* @param {fixtureCallbackMethod} cb is a callback method.
*/
Cramit.prototype.findAllFixturesAndRemoveData = function(applicationPath, options, cb) {
var cramit = this;
if( ! cb) {
cb = function(err) { if(err) { this.log.error(err); } };
}
cramit.findAllFixtures(applicationPath, options, function(err, fixtures) {
if(err) {
cb(err);
} else {
cramit.removeFixtureData(fixtures, cb);
}
});
};
/**
* Uses the module Crave to find all fixtures in a given
* directory. Then upsert all data found in the fixtures.
*
* Upsert refers to inserting the new data or
* updating the existing data.
*
* You can configure how Crave will find the fixtures
* by passing in a configuration object as the options
* parameter. The key 'crave' will be passed directly
* into the Crave module.
*
* The options object will also be passed as a parameter
* into each fixture that is loaded. However the crave
* key will be removed.
*
* @param {string} applicationPath is the path to your
* application's files where the fixtures can be found.
* @param {object|undefined} options configure the fixtures
* and how they will be loaded.
* @param {fixtureCallbackMethod} cb is a callback method.
*/
Cramit.prototype.findAllFixturesAndUpsertData = function(applicationPath, options, cb) {
var cramit = this;
if( ! cb) {
cb = function(err) { if(err) { this.log.error(err); } };
}
cramit.findAllFixtures(applicationPath, options, function(err, fixtures) {
if(err) {
cb(err);
} else {
cramit.upsertFixtureData(fixtures, cb);
}
});
};
/**
* Must be called by a class that inherits Fixture in
* the constructor. This will initialize any parameters
* defined by the Fixture class.
*
* @param {object} instance is the class's instance.
* @param {string} id is the class's unique identifier.
*/
Cramit.prototype.fixtureSuper = function(instance, id) {
this.Fixture.call(instance, id);
};
/**
* Must be called by a class that inherits Fixture and
* the results must be stored as a prototype. For
* example:
* MyFixture.prototype = cramit.fixturePrototype();
*
* @return {object} all Fixture prototype methods.
*/
Cramit.prototype.fixturePrototype = function() {
return this.inherit(this.Fixture.prototype);
};
/**
* Create an object that contains each fixture's data,
* populated using the getAll() fixture method. The
* keys for the object will remain the same, but each
* key's value will instead be the fixture data.
* @param fixtures is a fixtures object returned from
* Cramit.
* @param options is an object with settings for how
* to create the fixture data object.
* @param cb is a callback method where a result or
* error is returned.
*/
Cramit.prototype.getAllFixtureDataObject = function(fixtures, options, cb) {
if( ! fixtures) {
cb(new Error("Fixtures are invalid."));
} else {
var allFixtureDataObject = {};
for(var key in fixtures) {
if(fixtures.hasOwnProperty(key)) {
allFixtureDataObject[key] = fixtures[key].getAll();
}
}
cb(undefined, allFixtureDataObject);
}
};
/**
* Insert all data found in a list of fixtures.
*
* The fixtures parameter must be an object with
* each fixture's ID as the key and class as the
* value.
*
* @param {object|undefined} fixtures is a fixture
* object that references one or more fixtures.
* @param {cudCallback|undefined} is a callback method.
*/
Cramit.prototype.insertFixtureData = function(fixtures, cb) {
var cramit = this,
tasks = [];
if( ! cb) {
cb = function(err) { if(err) { this.log.error(err); } };
}
cramit.formatFixtures(fixtures, function(err, fixtures) {
if(err) {
cb(err);
} else {
for(var id in fixtures) {
if(fixtures.hasOwnProperty(id)) {
tasks.push(cramit.createInsertAllFixtureDataMethod(fixtures[id]));
}
}
async.parallel(tasks, cb);
}
});
};
Cramit.prototype.formatFixtures = function(fixtures, cb) {
var fixturesObject = {};
if( _.isArray(fixtures)) {
for(var i = 0; i < fixtures.length; i++) {
if(fixtures[i] !== undefined && fixtures[i].error === undefined && fixtures[i].id !== undefined) {
fixturesObject[fixtures[i].id] = fixtures[i];
}
}
cb(undefined, fixturesObject);
} else if( _.isObject(fixtures)) {
if(fixtures.id !== undefined && fixtures.error === undefined) {
fixturesObject[fixtures.id] = fixtures;
cb(undefined, fixturesObject);
} else {
for(var key in fixtures) {
if(fixtures.hasOwnProperty(key)) {
if(fixtures[key] !== undefined && fixtures[key].id !== undefined) {
fixturesObject[key] = fixtures[key];
}
}
}
cb(undefined, fixturesObject);
}
} else {
cb(undefined, fixturesObject);
}
};
/**
* Delete all data found in a list of fixtures.
*
* The fixtures parameter must be an object with
* each fixture's ID as the key and class as the
* value.
*
* @param {object|undefined} fixtures is a fixture
* object that references one or more fixtures.
* @param {cudCallback|undefined} is a callback method.
*/
Cramit.prototype.removeFixtureData = function(fixtures, cb) {
var cramit = this,
tasks = [];
if( ! cb) {
cb = function(err) { if(err) { console.log(err); } };
}
cramit.formatFixtures(fixtures, function(err, fixtures) {
if(err) {
cb(err);
} else {
for(var id in fixtures) {
if(fixtures.hasOwnProperty(id)) {
tasks.push(cramit.createDeleteAllFixtureDataMethod(fixtures[id]));
}
}
async.parallel(tasks, cb);
}
});
};
/**
* Upsert all data found in a list of fixtures.
*
* Upsert refers to inserting the new data or
* updating the existing data.
*
* The fixtures parameter must be an object with
* each fixture's ID as the key and class as the
* value.
*
* @param {object|undefined} fixtures is a fixture
* object that references one or more fixtures.
* @param {cudCallback|undefined} is a callback method.
*/
Cramit.prototype.upsertFixtureData = function(fixtures, cb) {
var cramit = this,
tasks = [];
if( ! cb) {
cb = function(err) { if(err) { cramit.log.error(err); } };
}
cramit.formatFixtures(fixtures, function(err, fixtures) {
if(err) {
cb(err);
} else {
for(var id in fixtures) {
if(fixtures.hasOwnProperty(id)) {
tasks.push(cramit.createUpsertAllFixtureDataMethod(fixtures[id]));
}
}
async.parallel(tasks, cb);
}
});
};
/* ************************************************** *
* ******************** Private Methods
* ************************************************** */
/**
* Create an asynchronous method to delete all of a
* fixture's data in the database.
*
* @param {object} fixture is a fixture class instance.
* @return {asyncFunction} the described method.
*/
Cramit.prototype.createDeleteAllFixtureDataMethod = function(fixture) {
var cramit = this;
return function(cb) {
if(fixture.deleteAll !== undefined) {
fixture.deleteAll(cb);
} else {
cramit.log.error('Fixture with id "%s" does not have a deleteAll() method.', fixture.id);
}
};
};
/**
* Create an asynchronous method to insert all of a
* fixture's data into the database.
*
* @param {object} fixture is a fixture class instance.
* @return {asyncFunction} the described method.
*/
Cramit.prototype.createInsertAllFixtureDataMethod = function(fixture) {
var cramit = this;
return function(cb) {
if(fixture.insertAll !== undefined) {
fixture.insertAll(cb);
} else {
cramit.log.error('Fixture with id "%s" does not have an insertAll() method.', fixture.id);
}
};
};
/**
* Create an asynchronous method to upsert all of a
* fixture's data in the database.
*
* Upsert refers to inserting the new data or
* updating the existing data.
*
* @param {object} fixture is a fixture class instance.
* @return {asyncFunction} the described method.
*/
Cramit.prototype.createUpsertAllFixtureDataMethod = function(fixture) {
var cramit = this;
return function(cb) {
if(fixture.upsertAll !== undefined) {
fixture.upsertAll(cb);
} else {
cramit.log.error('Fixture with id "%s" does not have an upsertAll() method.', fixture.id);
}
};
};
/**
* A method used to aid in the creation of a class
* that will inherit another class. Used to generate
* the proper prototype object that will be used
* by the child class.
*/
Cramit.prototype.inherit = function(proto) {
function F() {}
F.prototype = proto;
return new F;
};
/* ************************************************** *
* ******************** Expose the Public API
* ************************************************** */
exports = module.exports = Cramit;
exports = Cramit;
/* ************************************************** *
* ******************** Documentation Stubs
* ************************************************** */
/**
* Fixtures are used to manage data. This callback method
* will return an error or a fixture object. A fixture
* object is one or more fixtures with the fixture ID as
* the key and the fixture's class as a value.
*
* @callback fixtureCallbackMethod
* @param {object|undefined} error describes the error that occurred.
* @param {object|undefined} fixtures is a fixture object.
*/
/**
* A callback used when fixture data is inserted, updated,
* or deleted in the database. The result data will be an
* array of objects. Each object will contain a transaction
* and result key. The result key's value will relate to the
* data inserted, updated, or deleted. The transaction key's
* data will be related to the transaction used to modify the
* data in the database.
*
* @callback cudCallback
* @param {object|undefined} error describes the error that occurred.
* @param {array|undefined} result is a list of objects with
* information related to the database action.
*/
/**
* An asynchronous function that accepts only a callback as the parameter.
* All results will be passed to the callback with the first parameter
* being an error.
*
* @callback asyncFunction
* @param {function} cb is a callback method.
*/