Skip to content

Commit

Permalink
Implementation done
Browse files Browse the repository at this point in the history
  • Loading branch information
saschagehlich committed Nov 6, 2013
1 parent 1de5db2 commit 1df5e00
Show file tree
Hide file tree
Showing 5 changed files with 287 additions and 6 deletions.
269 changes: 269 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,274 @@
var path = require("path")
, fs = require("fs")
, _ = require("underscore")
, Sequelize = require("sequelize");
_.str = require("underscore.string");

function Sequenice(sequelize, options) {
if (!sequelize) throw new Error("sequenice needs an instance of sequelize");
this.sequelize = sequelize;
this.models = {};

var defaultModelsDirectory = path.resolve(
path.dirname(module.parent.id),
"models"
);

this.options = {
modelsDirectory: options.modelsDirectory || defaultModelsDirectory,
modelsAttacher: options.modelsAttacher || global,
getterPrefix: options.getterPrefix || "get",
setterPrefix: options.setterPrefix || "set"
};

this._loadModels();
this._resolveAssociations();
};
Sequenice.RESOLVABLE_ASSOCIATION_OPTIONS = ["joinTableModel"];
Sequenice.ASSOCIATIONS = ["belongsTo", "hasMany", "hasOne"];
Sequenice.HOOKS = [
"beforeValidate",
"afterValidate",
"beforeBulkCreate",
"beforeCreate", "afterCreate",
"afterBulkCreate"
];

/**
* Auto-loads the models from the modelsDirectory
* @private
*/
Sequenice.prototype._loadModels = function() {
var self = this;
if (!fs.existsSync(this.options.modelsDirectory))
throw new Error("Models directory not found: " + this.options.modelsDirectory);

// Read the models directory
var files = fs.readdirSync(this.options.modelsDirectory);

// Iterate over the model files
files.forEach(function (file) {
var modelPath = path.resolve(self.options.modelsDirectory, file);
self._loadModel(modelPath);
});
};

/**
* Defines the associations, resolves the table names
* to real models.
* @private
*/
Sequenice.prototype._resolveAssociations = function() {
var self = this;
Object.keys(this.models).forEach(function (modelName) {
var model = self.models[modelName]
, associations = model._associations;
associations.forEach(function (association) {
var options = association.options;

// Turn specific option values into model references
Sequenice.RESOLVABLE_ASSOCIATION_OPTIONS.forEach(function (relationOption) {
if (options.hasOwnProperty(relationOption)) {
var modelName = options[relationOption];
options[relationOption] = self.models[modelName]._model;
}
});

// Call the association method on the sequelize model
model._model[association.type](
self.models[association.modelName]._model, association.options
);
});
});
};

/**
* Loads a model from the given modelPath
* @param {String} modelPath
* @private
*/
Sequenice.prototype._loadModel = function(modelPath) {
var Model = require(modelPath)
, fields = {}
, getters = {}
, setters = {}
, validators = {}
, hooks = {}
, instanceMethods = {}
, classMethods = {}
, model, modelName, modelOptions;

// Avoid that our helpers will be added as
// instance functions to the sequelize model
Model._methodBlacklist = _.union(
['field', 'get', 'set', 'validates'],
Sequenice.ASSOCIATIONS,
Sequenice.HOOKS
);

// Attach the helpers that we will later call from
// the constructor
this._attachFieldHelperToModel(Model, fields);
this._attachGetHelperToModel(Model, getters);
this._attachSetHelperToModel(Model, setters);
this._attachValidatesHelperToModel(Model, validators);
this._attachAssociationHelpersToModel(Model);
this._attachHookHelpersToModel(Model, hooks);
this._extractMethodsFromModel(Model, instanceMethods, classMethods);

// Call the model constructor so that our
// targets get filled with data
model = new Model(Sequelize);

// Define the sequelize model
modelName = model.constructor.name;
modelOptions = {
instanceMethods: instanceMethods,
classMethods: classMethods,
validate: validators,
getterMethods: getters,
setterMethods: setters,
hooks: hooks
};
model._model = this.sequelize.define(modelName, fields, modelOptions);

// Attach the real sequelize models to the modelsAttacher.
// Since modelsAttacher defaults to `global`, we will make
// the models globally available per default
this.options.modelsAttacher[modelName] = model._model;

// Store our fake model
this.models[modelName] = model;
};

/**
* Adds a `field` prototype method to modelClass
* which will add a new field to the `target` object
* @param {Class} modelClass
* @param {Object} target
* @private
*/
Sequenice.prototype._attachFieldHelperToModel = function(modelClass, target) {
modelClass.prototype.field = function (name, type, options) {
opt = options || {}
opt.type = type;
target[name] = opt;
};
};

/**
* Adds a `get` prototype method to modelClass
* which will add a new getter
* @param {Class} modelClass
* @param {Object} target
* @private
*/
Sequenice.prototype._attachGetHelperToModel = function(modelClass, target) {
var self = this;
modelClass.prototype.get = function (attributeName) {
var capitalizedAttribute = _.str.capitalize(attributeName)
, methodName = self.options.getterPrefix + capitalizedAttribute;

target[attributeName] = modelClass.prototype[methodName];

modelClass._methodBlacklist.push(attributeName);
};
};

/**
* Adds a `set` prototype method to modelClass
* which will add a new setter
* @param {Class} modelClass
* @param {Object} target
* @private
*/
Sequenice.prototype._attachSetHelperToModel = function(modelClass, target) {
var self = this;
modelClass.prototype.set = function (attributeName) {
var capitalizedAttribute = _.str.capitalize(attributeName)
, methodName = self.options.setterPrefix + capitalizedAttribute;

target[attributeName] = modelClass.prototype[methodName];

modelClass._methodBlacklist.push(attributeName);
};
};

/**
* Adds a `validates` prototype method to modelClass
* which will add a new validator
* @param {Class} modelClass
* @param {Object} target
* @private
*/
Sequenice.prototype._attachValidatesHelperToModel = function(modelClass, target) {
var self = this;
modelClass.prototype.validates = function(methodName) {
target[methodName] = modelClass.prototype[methodName];
modelClass._methodBlacklist.push(methodName);
};
};

/**
* Adds prototype methods for all existing association
* types which will add a new association
* @param {Class} modelClass
* @private
*/
Sequenice.prototype._attachAssociationHelpersToModel = function(modelClass) {
modelClass.prototype._associations = [];
Sequenice.ASSOCIATIONS.forEach(function (association) {
modelClass.prototype[association] = function (modelName, options) {
modelClass.prototype._associations.push({
type: association,
modelName: modelName,
options: options || {}
});
};
});
};

/**
* Adds prototype methods for all existing hooks
* which will add new hook methods
* @param {Class} modelClass
* @param {Object} target
* @private
*/
Sequenice.prototype._attachHookHelpersToModel = function(modelClass, target) {
Sequenice.HOOKS.forEach(function (hook) {
modelClass.prototype[hook] = function (methodName) {
if (!target[hook]) target[hook] = [];

target[hook].push(modelClass.prototype[methodName])
modelClass._methodBlacklist.push(methodName);
}
});
};

/**
* Extracts instance methods and class methods from the given
* model class, excluding all methods in `modelClass._methodBlacklist`
* and adds them to `instanceTarget` and `classTarget`
* @param {Class} modelClass
* @param {Object} instanceTarget
* @param {Object} classTarget
* @private
*/
Sequenice.prototype._extractMethodsFromModel = function(modelClass, instanceTarget, classTarget) {
// Extract instance methods
Object.keys(modelClass.prototype).forEach(function (method) {
if(modelClass._methodBlacklist.indexOf(method) === -1) {
instanceTarget[method] = modelClass.prototype[method];
}
});

// Extract class methods
Object.keys(modelClass).forEach(function (method) {
if(modelClass._methodBlacklist.indexOf(method) === -1) {
instanceTarget[method] = modelClass[method];
}
});
};

module.exports = Sequenice;
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
"devDependencies": {
"sequelize": "~2.0.0-beta.1",
"should": "~2.0.2",
"mocha": "~1.14.0"
"mocha": "~1.14.0",
"mysql": "~2.0.0-alpha9"
},
"dependencies": {
"underscore": "~1.5.2",
"underscore.string": "~2.3.3"
}
}
8 changes: 5 additions & 3 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ var should = require("should")
before(function (done) {
sequelize = new Sequelize("sequenice_test", "root", "");
sequenice = new Sequenice(sequelize, {
modelDirectory: __dirname + "/models",
modelAttacher: models,
modelsDirectory: __dirname + "/models",
modelsAttacher: models,
getterPrefix: "get",
setterPrefix: "set"
});
sequelize.sync({ force: true }).success(function () {
done()
}).failure(function (err) {
throw err[0][0];
});
});

Expand Down Expand Up @@ -73,7 +75,7 @@ describe("sequenice example", function () {
describe("hooks", function () {
it("defines a `beforeCreate` hook", function (done) {
models.User.create().success(function (user) {
user.getDataValue("beforeCreateCalled").should.be.true;
user.beforeCreateCalled.should.be.true;
done();
});
});
Expand Down
2 changes: 2 additions & 0 deletions test/models/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ function Project(s) {
*/
this.belongsTo("User");
}

module.exports = Project;
7 changes: 5 additions & 2 deletions test/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ function User(s) {
*/
this.field("name", s.STRING);
this.field("isAdmin", s.BOOLEAN, { defaultValue: false });
this.field("beforeCreateCalled", s.BOOLEAN, { defaultValue: false });

/**
* Associations
Expand Down Expand Up @@ -44,11 +45,13 @@ User.prototype.setPrice = function(value) {
this.dataValues.priceInCents = value * 100;
};

User.prototype.myBeforeCreateMethod = function(callback) {
this.dataValues.beforeCreateCalled = true;
User.prototype.myBeforeCreateMethod = function(user, callback) {
user.beforeCreateCalled = true;
callback();
};

User.prototype.myValidationMethod = function (callback) {
callback();
}

module.exports = User;

0 comments on commit 1df5e00

Please sign in to comment.