Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
754 changes: 34 additions & 720 deletions lib/classes/Model.js

Large diffs are not rendered by default.

61 changes: 32 additions & 29 deletions lib/utils/bootstrapEnv.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,37 @@
'use strict';

var injector = require('injector')
, env = null
, path = require('path')
, chalk = require('chalk')
, debug = require('debug')('cleverstack:app')
, config = require('config')
, moment = require('moment')
, Promise = require('bluebird')
, appRoot = path.resolve(path.join(__dirname, '..', '..'));
var injector = require('injector')
, env = null
, path = require('path')
, chalk = require('chalk')
, debug = require('debug')('cleverstack:app')
, config = require('config')
, express = require('express')
, moment = require('moment')
, Promise = require('bluebird')
, appRoot = path.resolve(path.join(__dirname, '..', '..'))
, packageJson = require(appRoot + '/package.json');

function logger(msg) {
if (debug.enabled) {
debug.apply(debug, ['(pid ' + chalk.yellow(process.pid) + ' at ' + chalk.cyan(moment().format('HH:mm:ss [on] Do MMMM')) + ') - ' + msg].concat([].slice.call(arguments, 1)));
}
}

function injectCommonResources() {
if (process.env.NODE_ENV !== 'PROD') {
logger('Turning longStackTraces on');
Promise.longStackTraces();
}

function injectBaseCoreResources() {
injector.instance('injector', injector);
injector.instance('packageJson',packageJson);
injector.instance('express', express);
injector.instance('appRoot', appRoot);
injector.instance('config', config);
injector.instance('injector', injector);
}

function injectCommonResources() {
logger('Setting up injector...');

injector.instance('logger', logger);
injector.instance('Exceptions', require('exceptions'));
injector.instance('Promise', Promise);
Expand All @@ -42,23 +54,14 @@ function injectClasses() {
}

function bootApplication() {
var packageJson = require(appRoot + '/package.json')
, express = require('express')
, app = express()
var app = express()
, moduleLdr;

logger('Booting in %s mode...', chalk.yellow(process.env.NODE_ENV ? process.env.NODE_ENV : "LOCAL"));

if (process.env.NODE_ENV !== 'PROD') {
logger('Turning longStackTraces on');
Promise.longStackTraces();
}

logger('Setting up injector...')
logger('Booting in %s mode...', chalk.yellow(process.env.NODE_ENV ? process.env.NODE_ENV : 'LOCAL'));

injectBaseCoreResources();
injectCommonResources();
injector.instance('express', express);
injector.instance('app', app);
injector.instance('packageJson', packageJson);
injector.instance('app', app);

env = {
app : app,
Expand All @@ -69,7 +72,7 @@ function bootApplication() {
packageJson : packageJson
};

logger('Setting up module loader....')
logger('Setting up module loader....');
env.moduleLoader = moduleLdr = require(path.resolve(path.join(__dirname, '..', 'classes', 'ModuleLoader.js'))).getInstance(env);
injector.instance('moduleLoader', moduleLdr);

Expand All @@ -82,4 +85,4 @@ module.exports = function bootstrapEnv() {
bootApplication();
}
return env;
}
};
2 changes: 1 addition & 1 deletion lib/utils/getModulePaths.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module.exports = function() {
, args = [].slice.call(arguments);

packageJson.bundledDependencies.forEach(function(name) {
var modulePath = [ 'modules', name ].concat(args).join(path.sep);
var modulePath = ['modules', name].concat(args).join(path.sep);
if ((modulePath.indexOf('*') !== -1 && /^([^\*]+)(.*)/.test(modulePath) && fs.existsSync(RegExp.$1)) || fs.existsSync(modulePath)) {
paths.push(modulePath);
}
Expand Down
4 changes: 4 additions & 0 deletions lib/utils/model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
var path = require('path')
, model = path.join(__dirname, 'model') + path.sep;

module.exports = require('require-folder-tree')(model);
1 change: 1 addition & 0 deletions lib/utils/model/aggregate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// @todo aggregate
21 changes: 21 additions & 0 deletions lib/utils/model/behaviours/softDeleteable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module.exports.setup = function softDeleteableBehaviour(Klass) {
if (Klass.softDeleteable === true) {
Klass.fields.deletedAt = {
type : Date,
columnName : Klass.deletedAt
};
if (Klass.deletedAt !== 'deletedAt') {
Klass.aliases.push({
key : 'deletedAt',
columnName : Klass.deletedAt
});
}
}
};

module.exports.beforeFindersOptions = function beforeFindSoftDeleteableModel(findOptions, callback) {
if (findOptions[this.deletedAt] === undefined) {
findOptions[this.deletedAt] = {is: null};
}
callback(null);
};
33 changes: 33 additions & 0 deletions lib/utils/model/behaviours/timeStampable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module.exports.setup = function timeStampableBehaviour(Static) {
if (Static.timeStampable === true) {
Static.fields.createdAt = {
type : Date,
columnName : Static.createdAt
};
if (Static.createdAt !== 'createdAt') {
Static.aliases.push({
key : 'createdAt',
columnName : Static.createdAt
});
}

Static.fields.updatedAt = {
type : Date,
columnName : Static.updatedAt
};
if (Static.updatedAt !== 'updatedAt') {
Static.aliases.push({
key : 'updatedAt',
columnName : Static.updatedAt
});
}
}
};

module.exports.beforeCreate = function beforeCreateTimeStampableModel(modelData, callback) {
if (this.modelType === 'ODM' && !!this.timeStampable) {
modelData[this.createdAt] = Date.now();
modelData[this.updatedAt] = Date.now();
}
callback(null);
};
1 change: 1 addition & 0 deletions lib/utils/model/bulkCreate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// @todo bulkCreate
1 change: 1 addition & 0 deletions lib/utils/model/count.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// @todo count
44 changes: 44 additions & 0 deletions lib/utils/model/create.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
var utils = require('utils')
, async = require('async')
, Promise = require('bluebird');

/**
* Create a new model using the values provided and persist/save it to the database.
* @see http://cleverstack.io/documentation/backend/models/#creating-instances
*
* @function Model.create
* @param {Object} values The values that will be used to create a model instance
* @param {Object} queryOptions={}
* @param {Transaction} queryOptions.transaction The transaction (if any) to use in the query
* @return {Promise}
*/
module.exports = function createModel(values, queryOptions) {
var utilName = this.type.toLowerCase() + 'Utils'
, helpers = utils.model.helpers
, driverUtil = utils[utilName]
, timeStampable = utils.model.behaviours.timeStampable.beforeCreate;

queryOptions = utils.model.helpers.queryOptions.normalize(queryOptions);

if (this.debug.enabled) {
this.debug('create(%s)', utils.model.helpers.debugInspect(values));
}

return new Promise(function create(resolve, reject) {
async.waterfall([
this.callback(helpers.isExtendedModel),
this.callback(helpers.isNewModel, values),
this.callback(helpers.defaultValues, values),
this.callback(helpers.alias.fields.forOutput, values),
this.callback(helpers.validator, values),
this.callback(timeStampable, values),
this.callback(helpers.events.beforeEvent, 'beforeCreate', values, queryOptions),
this.callback(helpers.alias.associations.forQuery, values, false),
this.callback(helpers.alias.fields.forQuery, values),
this.callback(driverUtil.create, values, queryOptions),
this.callback(helpers.events.afterEvent, 'afterCreate', values, queryOptions)
],
this.callback(helpers.handleResult.returnModels, resolve, reject));
}
.bind(this));
};
1 change: 1 addition & 0 deletions lib/utils/model/describe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// @todo describe
38 changes: 38 additions & 0 deletions lib/utils/model/destroy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
var utils = require('utils')
, async = require('async')
, Promise = require('bluebird');

/**
* Destroy a model using the provided queryOptions's where criteria.
* @see http://cleverstack.io/documentation/backend/models/#destroying-instances
*
* @param {Object} queryOptions={}
* @param {Object} queryOptions.where Criteria to use for the UPDATE statement
* @param {Transaction} queryOptions.transaction The transaction (if any) to use in the query
* @return {Promise}
*/
module.exports = function destroyModels(queryOptions) {
var utilName = this.type.toLowerCase() + 'Utils'
, helpers = utils.model.helpers
, driverUtil = utils[utilName];

queryOptions = utils.model.helpers.queryOptions.normalize(queryOptions);

if (this.debug.enabled) {
this.debug('destroy where %s', utils.model.helpers.debugInspect(queryOptions ? queryOptions.where : queryOptions));
}

return new Promise(function destroy(resolve, reject) {
async.waterfall([
this.callback(helpers.isExtendedModel),
this.callback(helpers.where.exists, 'destroy', queryOptions.where, queryOptions),
this.callback(helpers.events.beforeEvent, 'beforeDestroy', queryOptions.where, queryOptions),
this.callback(helpers.alias.associations.forQuery, queryOptions.where, true),
this.callback(helpers.alias.fields.forQuery, queryOptions.where),
this.callback(driverUtil.destroy, queryOptions),
this.callback(helpers.events.afterEvent, 'afterDestroy', queryOptions)
],
this.callback(helpers.handleResult.removeReferencedModel, resolve, reject));
}
.bind(this));
};
53 changes: 53 additions & 0 deletions lib/utils/model/extend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
var utils = require('utils')
, injector = require('injector')
, moduleLdr = injector.getInstance('moduleLoader')
, defineProp = utils.helpers.defineProperty
, debuggr = require('debug')('cleverstack:models');

/**
* Creates a new Model that extends from this one
* @see http://cleverstack.io/documentation/backend/models/#definition
*
* @todo refactor this out, and allow extend to be called on existing models, to put partial schema's in different databases
*
* @override
* @param {String} tableName optionally define the name of the table/collection
* @param {Object} Static={} Class Static
* @param {Object} Proto Class Prototype
* @return {Model}
*/
module.exports = function extendModel() {
var extendingArgs = [].slice.call(arguments)
, modelName = utils.model.helpers.resolveName((typeof extendingArgs[0] === 'string') ? extendingArgs.shift() : false)
, Klass = (extendingArgs.length === 2) ? extendingArgs.shift() : {}
, Proto = extendingArgs.shift()
, modelType = (Klass.type !== undefined ? Klass.type : this.defaults.type).toUpperCase()
, moduleName = 'clever-' + modelType.toLowerCase()
, model = null
, debug = null;

// Return the cached (already built) model if we have it
if (this.models[modelName] !== undefined) {
debuggr(modelName + 'Model: Returning model class from the cache...');
return this.models[modelName];
}

// Ensure that there is a driver loaded and available (clever-orm or clever-odm)
utils.model.helpers.driver(moduleLdr, Klass, modelType, moduleName, modelName, debuggr);

// Create a unique debugger for the new model
debug = require('debug')('cleverstack:models:' + modelName);
utils.model.helpers.defineFields.apply(this, [Klass, Proto, modelName, modelType, debug]);

Proto.setup = utils[modelType.toLowerCase() + 'Utils'].setup;

debug('Creating model class...');
model = this._super.apply(this, [Klass, Proto]);

utils.model.helpers.afterExtend.apply(this, [utils, defineProp, model, Klass, modelName, debug]);

// Cache the model
this.models[modelName] = model;

return model;
};
49 changes: 49 additions & 0 deletions lib/utils/model/find.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
var utils = require('utils')
, async = require('async')
, Promise = require('bluebird');

/**
* Find and return the first model that matches the provided findOptions.
* @see http://cleverstack.io/documentation/backend/models/#finders-find
*
* @function Model.find
* @param {Object} findOptions={}
* @param {Object} findOptions.where Criteria for the query
* @param {Number} findOptions.limit Result Limiting (Paging)
* @param {Number} findOptions.offset Result Offset (Paging)
* @param {Array} findOptions.include Lazy/Eager Loading including Nested
* @param {Object} queryOptions={}
* @param {Transaction} queryOptions.transaction The transaction (if any) to use in the query
* @return {Promise}
*/
module.exports = function findModel(findOptions, queryOptions) {
var utilName = this.type.toLowerCase() + 'Utils'
, helpers = utils.model.helpers
, driverUtil = utils[utilName]
, softDeleteable = driverUtil.softDeleteable;

findOptions = helpers.findOptions.normalize(findOptions);
queryOptions = helpers.queryOptions.normalize(queryOptions);

if (this.debug.enabled) {
this.debug('find(%s)', helpers.debugInspect(findOptions));
}

return new Promise(function find(resolve, reject) {
async.waterfall([
this.callback(helpers.isExtendedModel),
this.callback(helpers.criteria.requirePrimaryKeys, findOptions),
this.callback(helpers.events.beforeEvent, 'beforeAllFindersOptions', findOptions, queryOptions),
this.callback(helpers.events.beforeEvent, 'beforeFindOptions', findOptions, queryOptions),
this.callback(helpers.findOptions.valid, findOptions), // @todo needed anymore?
this.callback(helpers.alias.fields.forQuery, findOptions.where),
this.callback(helpers.alias.associations.forQuery, findOptions.where, true),
this.callback(softDeleteable, findOptions, queryOptions),
this.callback(helpers.events.beforeEvent, 'beforeFind', findOptions, queryOptions),
this.callback(driverUtil.find, findOptions, queryOptions),
this.callback(helpers.events.afterEvent, 'afterFind', findOptions, queryOptions)
],
this.callback(helpers.handleResult.returnModels, resolve, reject));
}
.bind(this));
};
Loading