diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..b30d23d --- /dev/null +++ b/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["es2015"], + "plugins": ["transform-class-properties"] +} diff --git a/Makefile b/Makefile deleted file mode 100644 index 114c21a..0000000 --- a/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -test: - @./node_modules/mocha/bin/mocha --colors -t 10000 --reporter dot test/*.test.js - -.PHONY: test diff --git a/index.js b/index.js deleted file mode 100644 index 766493f..0000000 --- a/index.js +++ /dev/null @@ -1,2 +0,0 @@ -var sequenice = require("./lib/sequenice"); -module.exports = sequenice; diff --git a/lib/indices-syncer.js b/lib/indices-syncer.js deleted file mode 100644 index 312d1d6..0000000 --- a/lib/indices-syncer.js +++ /dev/null @@ -1,93 +0,0 @@ -"use strict"; -var sequelize = require("sequelize"); -var Utils = sequelize.Utils; - -function IndicesSyncer(sequelize, model, indices) { - this.sequelize = sequelize; - this.model = model; - this.indices = indices; -} - -/** - * Checks whether syncing is necessary, then starts the - * syncing process - * @return {CustomEventEmitter} - * @public - */ -IndicesSyncer.prototype.sync = function() { - var self = this; - - // No indices to sync, just pretend we're fine - if (this.indices.length === 0) { - return new Utils.CustomEventEmitter(function (emitter) { - emitter.emit("success", self.model); - }).run(); - } - - // Fetch the current indices - return new Utils.CustomEventEmitter(function (emitter) { - self._fetchIndices(emitter); - }).run(); -}; - -/** - * Fetches the currently existing indices, then syncs them - * @param {CustomEventEmitter} emitter - * @private - */ -IndicesSyncer.prototype._fetchIndices = function(emitter) { - var queryInterface = this.sequelize.queryInterface; - var self = this; - - queryInterface.showIndex(this.model.tableName) - .success(function (existingIndices) { - self.existingIndices = existingIndices; - - self._syncIndices(emitter); - }) - .error(function (e) { - emitter.emit("error", e); - }); -}; - -/** - * Final step - sync the indices and pass the result of our - * chain to the emitter - * @param {CustomEventEmitter} emitter - * @private - */ -IndicesSyncer.prototype._syncIndices = function(emitter) { - var chainer = new Utils.QueryChainer(); - var queryInterface = this.sequelize.queryInterface; - - for (var i = 0, len = this.indices.length; i < len; i++) { - var index = this.indices[i]; - var indexName; - - // Build the index name - // @TODO - // Move the index name generation to sequelize maybe? - if (index.options && index.options.indexName) { - indexName = index.options.indexName; - } else { - indexName = Utils._.underscored(this.model.tableName + "_" + index.attributes.join("_")); - } - - // Does the index already exist? - var indexExists = - this.existingIndices - .filter(function (existingIndex) { - return existingIndex.name === indexName; - }).length > 0; - - if (!indexExists) { - chainer.add(queryInterface.addIndex(this.model.tableName, index.attributes, index.options)); - } - } - - chainer - .run() - .proxy(emitter); -}; - -module.exports = IndicesSyncer; diff --git a/lib/sequenice.js b/lib/sequenice.js deleted file mode 100644 index b580239..0000000 --- a/lib/sequenice.js +++ /dev/null @@ -1,413 +0,0 @@ -"use strict"; -var Sequelize = require("sequelize"); -var DataTypes = require("sequelize/lib/data-types"); -var Utils = Sequelize.Utils; - -var path = require("path"); -var globule = require("globule"); -var fs = require("fs"); -var _ = require("underscore"); -var _str = require("underscore.string"); - -var IndicesSyncer = require("./indices-syncer"); - -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, - modelsMatch: options.modelsMatch || /\.[js|coffee]/i, - getterPrefix: options.getterPrefix || "_get", - setterPrefix: options.setterPrefix || "_set" - }; - - this._loadModels(); - this._resolveAssociations(); -} - -/** - * Available sequelize keywords - * @type {Array} - */ -Sequenice.RESOLVABLE_ASSOCIATION_OPTIONS = ["joinTableModel"]; -Sequenice.ASSOCIATIONS = ["belongsTo", "hasMany", "hasOne"]; -Sequenice.HOOKS = [ - "beforeValidate", - "afterValidate", - "beforeBulkCreate", - "beforeCreate", "afterCreate", - "afterBulkCreate" -]; - -/** - * Deletes model references and cleans up a little - * @public - */ -Sequenice.prototype.dispose = function() { - // Delete model references from modelsAttacher - var self = this; - Object.keys(this.models).forEach(function (key) { - delete self.options.modelsAttacher[key]; - delete self.models[key]; - }); -}; - -/** - * Auto-loads the models from the modelsDirectory - * @private - */ -Sequenice.prototype._loadModels = function() { - var self = this; - var match = this.options.modelsMatch; - var files; - - if (!fs.existsSync(this.options.modelsDirectory)) - throw new Error("Models directory not found: " + this.options.modelsDirectory); - - if (typeof match === "string") { - files = globule.find(match, { - cwd: this.options.modelsDirectory - }); - } else if (match instanceof RegExp) { - files = globule.find("**/*", { - cwd: this.options.modelsDirectory, - filter: function (f) { - return !!match.test(f); - } - }); - } else if (match instanceof Function) { - files = globule.find("**/*", { - cwd: this.options.modelsDirectory, - filter: match - }); - } - - // 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]; - var 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 - if (!self.models[association.modelName]) { - throw new Error("Associated model missing for " + modelName + ": " + association.modelName); - } - - 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); - var map = {}; - var fields = {}; - var getters = {}; - var setters = {}; - var validators = {}; - var hooks = {}; - var indices = []; - var instanceMethods = {}; - var classMethods = {}; - var options = {}; - var model, modelName, modelOptions; - - // Avoid that our helpers will be added as - // instance functions to the sequelize model - Model._methodBlacklist = _.union( - ["field", "get", "set", "validates", "_methodBlacklist"], - Sequenice.ASSOCIATIONS, - Sequenice.HOOKS - ); - - // Attach the helpers that we will later call from - // the constructor - this._attachFieldHelperToMap(Model, map, fields); - this._attachGetHelperToMap(Model, map, getters); - this._attachSetHelperToMap(Model, map, setters); - this._attachValidatesHelperToMap(Model, map, validators); - this._attachAssociationHelpersToMap(Model, map); - this._attachHookHelpersToMap(Model, map, hooks); - this._attachIndexHelperToMap(Model, map, indices); - this._attachOptionsHelperToMap(Model, map, options); - - // Extract instance and class methods from the model - this._extractMethodsFromModel(Model, instanceMethods, classMethods); - - // Attach sequelize's data types to the map - this._attachDataTypesToMap(map); - - // Call the model constructor so that our - // targets get filled with data - model = new Model(map); - - // Define the sequelize model - modelName = model.constructor.name; - modelOptions = _.extend({ - instanceMethods: instanceMethods, - classMethods: classMethods, - validate: validators, - getterMethods: getters, - setterMethods: setters, - hooks: hooks - }, options); - model._model = this.sequelize.define(modelName, fields, modelOptions); - - // Override the sync method so that it automatically - // adds the given indices - this._overrideSyncWithIndices(model._model, indices); - - // 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 map - * which will add a new field to the `target` object - * @param {Class} modelClass - * @param {Object} map - * @param {Object} target - * @private - */ -Sequenice.prototype._attachFieldHelperToMap = function(modelClass, map, target) { - map.field = function (name, type, options) { - var opt = options || {}; - opt.type = type; - target[name] = opt; - }; -}; - -/** - * Adds a `get` prototype method to map - * which will add a new getter - * @param {Class} modelClass - * @param {Object} map - * @param {Object} target - * @private - */ -Sequenice.prototype._attachGetHelperToMap = function(modelClass, map, target) { - var self = this; - map.get = function (attributeName) { - var capitalizedAttribute = _str.capitalize(attributeName); - var methodName = self.options.getterPrefix + capitalizedAttribute; - - target[attributeName] = modelClass.prototype[methodName]; - - modelClass._methodBlacklist.push(attributeName); - }; -}; - -/** - * Adds a `set` prototype method to map - * which will add a new setter - * @param {Class} modelClass - * @param {Object} map - * @param {Object} target - * @private - */ -Sequenice.prototype._attachSetHelperToMap = function(modelClass, map, target) { - var self = this; - map.set = function (attributeName) { - var capitalizedAttribute = _str.capitalize(attributeName); - var 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} map - * @param {Object} target - * @private - */ -Sequenice.prototype._attachValidatesHelperToMap = function(modelClass, map, target) { - map.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 - * @param {Object} map - * @private - */ -Sequenice.prototype._attachAssociationHelpersToMap = function(modelClass, map) { - modelClass.prototype._associations = []; - Sequenice.ASSOCIATIONS.forEach(function (association) { - map[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} map - * @param {Object} target - * @private - */ -Sequenice.prototype._attachHookHelpersToMap = function(modelClass, map, target) { - Sequenice.HOOKS.forEach(function (hook) { - map[hook] = function (methodName) { - if (!target[hook]) target[hook] = []; - - target[hook].push(modelClass.prototype[methodName]); - modelClass._methodBlacklist.push(methodName); - }; - }); -}; - -/** - * Adds a `index` prototype method to modelClass - * which will add a new index - * @param {Class} modelClass - * @param {Object} map - * @param {Object} target - * @private - */ -Sequenice.prototype._attachIndexHelperToMap = function(modelClass, map, target) { - map.index = function (attributes, options) { - target.push({ attributes: attributes, options: options }); - }; -}; - -/** - * Adds a `options` prototype method to modelClass - * which will add options to the target - * @param {Class} modelClass - * @param {Object} map - * @param {Object} target - * @private - */ -Sequenice.prototype._attachOptionsHelperToMap = function(modelClass, map, target) { - map.options = function (options) { - _.extend(target, options); - }; -}; - -/** - * 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) { - classTarget[method] = modelClass[method]; - } - }); -}; - -/** - * Overrides model.sync() to add incides after the syncing - * has been done - * @param {Model} model - * @param {Array} indices - * @private - */ -Sequenice.prototype._overrideSyncWithIndices = function(model, indices) { - var sync = model.sync; - var sequelize = this.sequelize; - - // Override syncing method so that it calls - // addIndices() afterwards - model.sync = function () { - var self = this; - return new Utils.CustomEventEmitter(function(emitter) { - sync.apply(self, arguments) - .success(function (m) { - - // Sequelize's syncing worked, run the index syncing mechanism - var indicesSyncer = new IndicesSyncer(sequelize, m, indices); - indicesSyncer - .sync() - .proxy(emitter); - - }) - .error(function (e) { - emitter.emit("error", e); - }); - }).run(); - }; -}; - -/** - * Copies sequelize's data types to the map - * @param {Object} map - * @private - */ -Sequenice.prototype._attachDataTypesToMap = function(map) { - for (var key in DataTypes) { - map[key] = DataTypes[key]; - } -}; - -module.exports = Sequenice; diff --git a/package.json b/package.json index 9c6148d..8f888e2 100644 --- a/package.json +++ b/package.json @@ -2,17 +2,30 @@ "name": "sequenice", "version": "0.0.6", "description": "A nice model wrapper for `sequelize`", - "main": "index.js", + "main": "./build/index.js", "directories": { "example": "example" }, "scripts": { - "test": "make test" + "test": "mocha --require babel-register --reporter spec test -- --harmony" }, "repository": { "type": "git", "url": "git://github.com/saschagehlich/sequenice.git" }, + "standard": { + "globals": [ + "describe", + "context", + "before", + "beforeEach", + "after", + "afterEach", + "it", + "expect" + ], + "parser": "babel-eslint" + }, "keywords": [ "sequelize", "model", @@ -27,14 +40,16 @@ "url": "https://github.com/saschagehlich/sequenice/issues" }, "devDependencies": { - "sequelize": "~2.0.0-beta.1", - "should": "~2.0.2", + "babel-plugin-transform-class-properties": "^6.23.0", + "babel-preset-es2015": "^6.24.0", + "babel-register": "^6.24.0", "mocha": "~1.14.0", - "mysql": "~2.0.0-alpha9" + "sequelize": "4.0.0-2", + "should": "~2.0.2", + "sqlite3": "^3.1.8" }, "dependencies": { "globule": "~0.1.0", - "underscore": "~1.5.2", - "underscore.string": "~2.3.3" + "lodash": "^4.17.4" } } diff --git a/src/sequenice.js b/src/sequenice.js new file mode 100644 index 0000000..8010c6c --- /dev/null +++ b/src/sequenice.js @@ -0,0 +1,332 @@ +import path from 'path' +import fs from 'fs' + +import _ from 'lodash' + +import globule from 'globule' +import DataTypes from 'sequelize/lib/data-types' + +export default class Sequenize { + constructor (sequelize, options) { + this.sequelize = sequelize + this.models = {} + + const defaultModelsDirectory = path.resolve( + path.dirname(module.parent.id), + 'models' + ) + + this._options = _.clone(options) + _.defaults(this._options, { + modelsDirectory: defaultModelsDirectory, + modelsAttacher: global, + getterPrefix: '_get', + setterPrefix: '_set' + }) + + this._loadModels() + this._resolveAssociations() + } + + static RESOLVABLE_ASSOCIATION_OPTIONS = ['joinTableModel'] + static ASSOCIATIONS = ['belongsTo', 'hasMany', 'hasOne', 'belongsToMany'] + static HOOKS = [ + 'beforeBulkCreate', 'beforeBulkDestroy', 'beforeBulkUpdate', + 'beforeValidate', 'afterValidate', 'validationFailed', + 'beforeCreate', 'beforeDestroy', 'beforeUpdate', 'beforeSave', 'beforeUpsert', + 'afterCreate', 'afterDestroy', 'afterUpdate', 'afterSave', 'afterUpsert', + 'afterBulkCreate', 'afterBulkDestroy', 'afterBulkUpdate' + ] + + /** + * Autoloads the models from the models directory + * @private + */ + _loadModels () { + const { modelsDirectory } = this._options + + if (!fs.existsSync(modelsDirectory)) { + throw new Error(`Models directory not found: ${modelsDirectory}`) + } + + const files = globule.find('**/*', { + cwd: modelsDirectory, + filter: 'isFile' + }) + + files.forEach((file) => { + const modelPath = path.resolve(modelsDirectory, file) + this._loadModel(modelPath) + }) + } + + /** + * Defines the associations, resolves the table names to real models. + * @private + */ + _resolveAssociations () { + Object.keys(this.models).forEach((modelName) => { + const model = this.models[modelName] + const associations = model._associations + associations.forEach((association) => { + const options = association.options + + // Turn specific option values into model references + this.constructor.RESOLVABLE_ASSOCIATION_OPTIONS.forEach((relationOption) => { + if (options.hasOwnProperty(relationOption)) { + const modelName = options[relationOption] + options[relationOption] = this.models[modelName]._model + } + }) + + // Call the association method on the sequelize model + if (!this.models[association.modelName]) { + throw new Error(`Associated model missing for ${modelName}: ${association.modelName}`) + } + + model._model[association.type]( + this.models[association.modelName]._model, association.options + ) + }) + }) + } + + /** + * Loads a model from the given modelPath + * @param {String} modelPath + * @private + */ + _loadModel (modelPath) { + const Model = require(modelPath).default || require(modelPath) + const map = {} + const fields = {} + const getters = {} + const setters = {} + const validators = {} + const hooks = {} + const indices = [] + const options = {} + let model + let modelName + let modelOptions + + // Avoid that our helpers will be added as instance functions to the sequelize model + Model._methodBlacklist = _.union( + ['constructor', 'field', 'get', 'set', 'validates', '_methodBlacklist'], + this.constructor.ASSOCIATIONS, + this.constructor.HOOKS + ) + + // Attach the helpers that we will later call from the constructor + this._attachFieldHelperToMap(Model, map, fields) + this._attachGetHelperToMap(Model, map, getters) + this._attachSetHelperToMap(Model, map, setters) + this._attachValidatesHelperToMap(Model, map, validators) + this._attachAssociationHelpersToMap(Model, map) + this._attachHookHelpersToMap(Model, map, hooks) + this._attachIndexHelperToMap(Model, map, indices) + this._attachOptionsHelperToMap(Model, map, options) + + // Attach sequelize's data types to the map + this._attachDataTypesToMap(map) + + // Call the model constructor so that our targets get filled with data + model = new Model(map) + + // Define the sequelize model + modelName = model.constructor.name + modelOptions = _.extend({ + validate: validators, + getterMethods: getters, + setterMethods: setters, + hooks + }, options) + model._model = this.sequelize.define(modelName, fields, modelOptions) + + // Copy instance and class methods to the model + this._copyMethodsToModel(Model, model._model) + + // 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 map which will add a new field to the `target` object + * @param {Class} modelClass + * @param {Object} map + * @param {Object} target + * @private + */ + _attachFieldHelperToMap (modelClass, map, target) { + map.field = (name, type, options = {}) => { + options.type = type + target[name] = options + } + } + + /** + * Adds a `get` prototype method to map which will add a new getter + * @param {Class} modelClass + * @param {Object} map + * @param {Object} target + * @private + */ + _attachGetHelperToMap (modelClass, map, target) { + const { getterPrefix } = this._options + map.get = attributeName => { + const capitalizedAttribute = _.upperFirst(attributeName) + const methodName = getterPrefix + capitalizedAttribute + target[attributeName] = modelClass.prototype[methodName] + modelClass._methodBlacklist.push(attributeName) + } + } + + /** + * Adds a `set` prototype method to map which will add a new setter + * @param {Class} modelClass + * @param {Object} map + * @param {Object} target + * @private + */ + _attachSetHelperToMap (modelClass, map, target) { + const { setterPrefix } = this._options + map.set = attributeName => { + const capitalizedAttribute = _.upperFirst(attributeName) + const methodName = 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} map + * @param {Object} target + * @private + */ + _attachValidatesHelperToMap (modelClass, map, target) { + map.validates = 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 + * @param {Object} map + * @private + */ + _attachAssociationHelpersToMap (modelClass, map) { + modelClass.prototype._associations = [] + this.constructor.ASSOCIATIONS.forEach(association => { + map[association] = (modelName, options = {}) => { + modelClass.prototype._associations.push({ + type: association, + modelName, + options + }) + } + }) + } + + /** + * Adds prototype methods for all existing hooks which will add new hook methods + * @param {Class} modelClass + * @param {Object} map + * @param {Object} target + * @private + */ + _attachHookHelpersToMap (modelClass, map, target) { + this.constructor.HOOKS.forEach(hook => { + map[hook] = methodName => { + if (!target[hook]) target[hook] = [] + + target[hook].push(modelClass.prototype[methodName]) + modelClass._methodBlacklist.push(methodName) + } + }) + } + + /** + * Adds a `index` prototype method to modelClass which will add a new index + * @param {Class} modelClass + * @param {Object} map + * @param {Object} target + * @private + */ + _attachIndexHelperToMap (modelClass, map, target) { + map.index = (attributes, options) => { + target.push({ attributes, options }) + } + } + + /** + * Adds a `options` prototype method to modelClass which will add options to the target + * @param {Class} modelClass + * @param {Object} map + * @param {Object} target + * @private + */ + _attachOptionsHelperToMap (modelClass, map, target) { + map.options = options => { + _.extend(target, options) + } + } + + /** + * Copies the instance and class methods of the given `modelClass` to the given `modelInstance` + * @param {Class} modelClass + * @param {Object} modelInstance + * @private + */ + _copyMethodsToModel (modelClass, modelInstance) { + // Copy prototypes + Object.getOwnPropertyNames(modelClass.prototype) + .filter(method => typeof modelClass.prototype[method] === 'function') + .forEach(method => { + if (!modelClass._methodBlacklist.includes(method)) { + modelInstance.prototype[method] = modelClass.prototype[method] + } + }) + + // Extract class methods + Object.getOwnPropertyNames(modelClass) + .filter(method => typeof modelClass[method] === 'function') + .forEach(method => { + if (!modelClass._methodBlacklist.includes(method)) { + modelInstance[method] = modelClass[method] + } + }) + } + + /** + * Copies sequelize's data types to the map + * @param {Object} map + * @private + */ + _attachDataTypesToMap (map) { + for (const key in DataTypes) { + map[key] = DataTypes[key] + } + } + + /** + * Deletes model references and cleans up a little + * @public + */ + dispose () { + Object.keys(this.models) + .forEach(key => { + delete this._options.modelsAttacher[key] + delete this.models[key] + }) + } +} diff --git a/test/index.test.js b/test/index.test.js index 2ad0d29..6c667d6 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -1,211 +1,136 @@ -"use strict"; -var _ = require("underscore"); -var should = require("should"); -var Sequelize = require("sequelize"); -var Sequenice = require(".."); -var sequelize, sequenice; -var models = {}; +import path from 'path' +import should from 'should' +import _ from 'lodash' +import Sequelize from 'sequelize' +import Sequenice from '../src/sequenice' +let sequelize +let models = {} before(function (done) { - sequelize = new Sequelize("sequenice_test", "root"); - sequenice = new Sequenice(sequelize, { - modelsDirectory: __dirname + "/models", - modelsMatch: "*.js", + sequelize = new Sequelize({ + dialect: 'sqlite', + storage: ':memory:' + }) + + /* eslint-disable no-new */ + new Sequenice(sequelize, { + modelsDirectory: path.resolve(__dirname, 'models'), modelsAttacher: models - }); - var chain = new Sequelize.Utils.QueryChainer(); + }) + /* eslint-enable no-new */ - chain.add(sequelize.dropAllSchemas()); - chain.add(sequelize.sync()); - - chain.run().success(function () { - done(); - }).failure(function (err) { - if (err instanceof Array) { - err = _.flatten(err)[0]; - } + sequelize.dropAllSchemas() + .then(() => sequelize.sync()) + .then(() => done()) + .catch(err => { + if (err instanceof Array) { + err = _.flatten(err)[0] + } - throw err; - }); -}); + throw err + }) +}) -describe("sequenice example", function () { +describe('sequenice example', function () { /** * Field definitions */ - describe("field definitions", function () { - it("creates `User` and `Project` models and attaches them to `models`", function () { - should.exist(models.User); - should.exist(models.Project); - }); - - it("creates a `name` field", function (done) { - models.User.create({ name: "A Name" }).success(function (user) { - should.exist(user.name); - done(); - }); - }); - - it("creates a `isAdmin` field with a default value", function (done) { - models.User.create({ name: "A Name" }).success(function (user) { - user.isAdmin.should.equal(false); - done(); - }); - }); - }); + describe('field definitions', function () { + it('creates `User` and `Project` models and attaches them to `models`', function () { + should.exist(models.User) + should.exist(models.Project) + }) + + it('creates a `name` field', function (done) { + models.User.create({ name: 'A Name' }).then(function (user) { + should.exist(user.name) + done() + }) + }) + + it('creates a `isAdmin` field with a default value', function (done) { + models.User.create({ name: 'A Name' }).then(function (user) { + user.isAdmin.should.equal(false) + done() + }) + }) + }) /** * Class / instance methods */ - describe("class methods", function () { - it("turn into sequelize class methods", function (done) { - should.exist(models.User.classMethod); - done(); - }); - }); - - describe("instance methods", function () { - it("turn into sequelize instance methods", function (done) { - models.User.create({ name: "Some name" }).success(function (user) { - should.exist(user.instanceMethod); - done(); - }); - }); - }); + describe('class methods', function () { + it('turn into sequelize class methods', function (done) { + should.exist(models.User.classMethod) + done() + }) + }) + + describe('instance methods', function () { + it('turn into sequelize instance methods', function (done) { + models.User.create({ name: 'Some name' }).then(function (user) { + should.exist(user.instanceMethod) + done() + }) + }) + }) /** * Associations */ - describe("associations definitions", function () { - it("creates a hasMany relation from `User` to `Project`", function (done) { - models.User.create({ name: "A Name" }).success(function (user) { - models.Project.create({ name: "Project name" }).success(function (project) { - user.setProjects([project]).success(function () { - done(); - }); - }); - }); - }); - - it("creates a belongsTo relation from `Project` to `User`", function (done) { - models.Project.create({ name: "Project name" }).success(function (project) { - models.User.create({ name: "A Name" }).success(function (user) { - project.setUser(user).success(function () { - done(); - }); - }); - }); - }); - }); + describe('associations definitions', function () { + it('creates a hasMany relation from `User` to `Project`', function (done) { + models.User.create({ name: 'A Name' }).then(function (user) { + models.Project.create({ name: 'Project name' }).then(function (project) { + user.setProjects([project]).then(function () { + done() + }) + }) + }) + }) + + it('creates a belongsTo relation from `Project` to `User`', function (done) { + models.Project.create({ name: 'Project name' }).then(function (project) { + models.User.create({ name: 'A Name' }).then(function (user) { + project.setUser(user).then(function () { + done() + }) + }) + }) + }) + }) /** * Hooks */ - describe("hooks", function () { - it("defines a `beforeCreate` hook", function (done) { - models.User.create().success(function (user) { - user.beforeCreateCalled.should.equal(true); - done(); - }); - }); - }); + describe('hooks', function () { + it('defines a `beforeCreate` hook', function (done) { + models.User.create().then(function (user) { + user.beforeCreateCalled.should.equal(true) + done() + }) + }) + }) /** * Getters / setters */ - it("defines getters and setters", function (done) { - models.User.build({ price: 20 }).priceInCents.should.equal(20 * 100); - models.User.build({ priceInCents: 30 * 100 }).price.should.equal("$" + 30); + it('defines getters and setters', function (done) { + // models.User.build({ price: 20 }).priceInCents.should.equal(20 * 100) + models.User.build({ priceInCents: 30 * 100 }).price.should.equal('$30') - done(); - }); + done() + }) /** * Options */ - it("should apply the options", function (done) { - sequelize.queryInterface.describeTable("Projects").success(function (columns) { - should.not.exist(columns.createdAt); - should.not.exist(columns.updatedAt); - - done(); - }); - }); - - /** - * Indices - */ - it("creates indices", function (done) { - sequelize.queryInterface.showIndex("Users").success(function (indices) { - indices.length.should.equal(3); - - indices[1].name.should.equal("IdName"); - indices[2].name.should.equal("users_name_is_admin"); - - done(); - }); - }); - - it("only creates indices if they don't exist", function (done) { - sequelize.sync().success(function () { - done(); - }).failure(function (err) { - if (err instanceof Array) { - err = _.flatten(err)[0]; - } - throw err; - }); - }); - - /** - * modelsMatch option - */ - describe("`modelsMatch` option", function () { - it("should support a string value (blob)", function (done) { - sequenice.dispose(); - sequenice = new Sequenice(sequelize, { - modelsDirectory: __dirname + "/models", - modelsMatch: "**/*.js", - modelsAttacher: models - }); - - should.exist(models.AdminUser); - should.exist(models.Project); - should.exist(models.User); - - done(); - }); - - it("should support a regex value", function (done) { - sequenice.dispose(); - sequenice = new Sequenice(sequelize, { - modelsDirectory: __dirname + "/models", - modelsMatch: /admin\/user\.js$/i, - modelsAttacher: models - }); - - should.exist(models.AdminUser); - should.not.exist(models.Project); - should.not.exist(models.User); - - done(); - }); - - it("should support a function value", function (done) { - sequenice.dispose(); - sequenice = new Sequenice(sequelize, { - modelsDirectory: __dirname + "/models", - modelsMatch: function (m) { - return !!/admin\/user\.js/i.test(m); - }, - modelsAttacher: models - }); - - should.exist(models.AdminUser); - should.not.exist(models.Project); - should.not.exist(models.User); - - done(); - }); - }); -}); + it('should apply the options', function (done) { + sequelize.queryInterface.describeTable('Projects').then(function (columns) { + should.not.exist(columns.createdAt) + should.not.exist(columns.updatedAt) + + done() + }) + }) +}) diff --git a/test/models/admin/user.js b/test/models/admin/user.js index 24bedb6..6a2943f 100644 --- a/test/models/admin/user.js +++ b/test/models/admin/user.js @@ -1,10 +1,8 @@ -"use strict"; - -function AdminUser(m) { - /** - * Field declarations - */ - m.field("name", m.STRING); +export default class AdminUser { + constructor (m) { + /** + * Field declarations + */ + m.field('name', m.STRING) + } } - -module.exports = AdminUser; diff --git a/test/models/project.js b/test/models/project.js index f349585..bb08462 100644 --- a/test/models/project.js +++ b/test/models/project.js @@ -1,21 +1,20 @@ -"use strict"; -function Project(m) { - /** - * Field declarations - */ - m.field("name", m.STRING); +export default class Project { + constructor (m) { + /** + * Field declarations + */ + m.field('name', m.STRING) - /** - * Associations - */ - m.belongsTo("User"); + /** + * Associations + */ + m.belongsTo('User') - /** - * Options - */ - m.options({ - timestamps: false - }); + /** + * Options + */ + m.options({ + timestamps: false + }) + } } - -module.exports = Project; diff --git a/test/models/user.js b/test/models/user.js index 282ecf4..90780a3 100644 --- a/test/models/user.js +++ b/test/models/user.js @@ -1,67 +1,60 @@ -"use strict"; -function User(m) { - /** - * Field declarations - */ - m.field("name", m.STRING); - m.field("isAdmin", m.BOOLEAN, { defaultValue: false }); - m.field("beforeCreateCalled", m.BOOLEAN, { defaultValue: false }); - - /** - * Associations - */ - m.hasMany("Project"); - - /** - * Getters - */ - m.get("price"); - m.get("priceInCents"); - - /** - * Setters - */ - m.set("price"); - - /** - * Hooks - */ - m.beforeCreate("myBeforeCreateMethod"); - - /** - * Validations - */ - m.validates("myValidationMethod"); - - /** - * Indices - */ - m.index(["id", "name"], { indexName: "IdName" }); - m.index(["name", "isAdmin"]); +export default class User { + constructor (m) { + /** + * Field declarations + */ + m.field('name', m.STRING) + m.field('isAdmin', m.BOOLEAN, { defaultValue: false }) + m.field('beforeCreateCalled', m.BOOLEAN, { defaultValue: false }) + m.field('priceInCents', m.INTEGER) + + /** + * Associations + */ + m.hasMany('Project') + + /** + * Getters + */ + m.get('price') + m.get('priceInCents') + + /** + * Setters + */ + m.set('price') + + /** + * Hooks + */ + m.beforeCreate('myBeforeCreateMethod') + + /** + * Validations + */ + m.validates('myValidationMethod') + } + + static classMethod () {} + instanceMethod () {} + + _getPrice () { + return `$${this.getDataValue('priceInCents') / 100}` + } + + _getPriceInCents () { + return this.dataValues.priceInCents + } + + _setPrice (value) { + this.dataValues.priceInCents = value * 100 + } + + myBeforeCreateMethod (user) { + user.beforeCreateCalled = true + } + + myValidationMethod () { + + } } - -User.classMethod = function() {}; -User.prototype.instanceMethod = function() {}; - -User.prototype._getPrice = function() { - return "$" + (this.getDataValue("priceInCents") / 100); -}; - -User.prototype._getPriceInCents = function() { - return this.dataValues.priceInCents; -}; - -User.prototype._setPrice = function(value) { - this.dataValues.priceInCents = value * 100; -}; - -User.prototype.myBeforeCreateMethod = function(user, callback) { - user.beforeCreateCalled = true; - callback(); -}; - -User.prototype.myValidationMethod = function (callback) { - callback(); -}; - -module.exports = User;