diff --git a/lib/util.js b/lib/util.js index 54082d61..35cc73ba 100644 --- a/lib/util.js +++ b/lib/util.js @@ -11,6 +11,7 @@ const i = require('inflect') const _ = require('lodash') +const autoLoader = require('auto-loader') const prettyHrtime = require('pretty-hrtime') const util = exports = module.exports = {} const isolatedLodash = _.runInContext() @@ -269,3 +270,26 @@ util.timeDiff = function (start) { let end = process.hrtime(start) return prettyHrtime(end) } + +/** + * loads all .js files from a given directory and + * then back as object. + * + * @method loadJsFiles + * + * @param {String} fromPath + * @return {Object} + * + * @public + */ +util.loadJsFiles = function (fromPath) { + return _(autoLoader.load(fromPath)) + .map(function (file, name) { + if (name.endsWith('.js')) { + return [name.replace('.js', ''), file] + } + }) + .compact() + .fromPairs() + .value() +} diff --git a/package.json b/package.json index b8e232a0..75b15d2b 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ } }, "dependencies": { + "auto-loader": "git+https://github.com/thetutlage/node-auto-loader.git", "cat-log": "^1.0.0", "co": "^4.6.0", "co-functional": "^0.2.1", diff --git a/src/Factory/ModelFactory.js b/src/Factory/ModelFactory.js new file mode 100644 index 00000000..40b06dc1 --- /dev/null +++ b/src/Factory/ModelFactory.js @@ -0,0 +1,113 @@ +'use strict' + +/** + * adonis-lucid + * + * (c) Harminder Virk + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. +*/ + +const cf = require('co-functional') +const _ = require('lodash') +const Ioc = require('adonis-fold').Ioc +const faker = require('faker') + +class ModelFactory { + + constructor (binding, callback) { + this.binding = Ioc.use(binding) + this.callback = callback + this.instances = [] + } + + /** + * makes instance of a given model + * + * @param {Object} values + * @return {Object} + * + * @private + */ + _makeInstance (values) { + const Model = this.binding + return new Model(values) + } + + /** + * calls blueprint and passed faker library + * to it. + * + * @return {Object} + * + * @private + */ + _callBlueprint () { + return this.callback(faker) + } + + /** + * returns a model instace by calling the blueprint + * and setting values on model instance + * + * @return {Object} + * + * @public + */ + make () { + return this._makeInstance(this._callBlueprint()) + } + + /** + * creates rows inside the database by calling create + * method on the given model + * + * @method create + * + * @param {Number} rows + * @return {Object} reference to this + * + * @public + */ + * create (rows) { + const self = this + const range = _.range(rows) + this.instances = yield cf.mapSerial(function * () { + return yield self.binding.create(self._callBlueprint()) + }, range) + return this + } + + /** + * loops through all the created instances and + * executes a callback with support for + * calling generators + * + * @method each + * + * @param {Function} callback + * + * @public + */ + each (callback) { + return cf.forEach(function * (instance) { + yield callback(instance) + }, this.instances) + } + + /** + * will reset the given model by calling + * truncate method on it. + * + * @return {Number} + * + * @public + */ + reset () { + return this.binding.query().truncate() + } + +} + +module.exports = ModelFactory diff --git a/src/Factory/index.js b/src/Factory/index.js new file mode 100644 index 00000000..a3302c62 --- /dev/null +++ b/src/Factory/index.js @@ -0,0 +1,73 @@ +'use strict' + +/** + * adonis-lucid + * + * (c) Harminder Virk + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. +*/ + +const Factory = exports = module.exports = {} +const ModelFactory = require('./ModelFactory') +const NE = require('node-exceptions') +let blueprints = {} + +/** + * defines a new factory blueprint mapped on a given + * key. Later callback is called and passed the + * faker object. + * + * @method define + * + * @param {String} key + * @param {Function} callback + * + * @public + */ +Factory.blueprint = function (key, callback) { + if (typeof (callback) !== 'function') { + throw new NE.InvalidArgumentException('callback should be a function while define a factory blueprint') + } + blueprints[key] = callback +} + +/** + * returns all registered blueprints inside a factory + * @method blueprints + * + * @return {Object} + * + * @public + */ +Factory.blueprints = function () { + return blueprints +} + +/** + * clears all registered blueprints + * + * @method clear + * + * @public + */ +Factory.clear = function () { + blueprints = {} +} + +/** + * returns instance of model factory and pass it + * the blueprint defination. + * + * @method model + * + * @param {String} key + * @return {Object} + * + * @public + */ +Factory.model = function (key) { + const callback = blueprints[key] + return new ModelFactory(key, callback) +} diff --git a/src/Schema/index.js b/src/Schema/index.js index 271a0e67..ab3646bb 100644 --- a/src/Schema/index.js +++ b/src/Schema/index.js @@ -9,15 +9,22 @@ * file that was distributed with this source code. */ -const proxy = require('./proxy') +const proxyHandler = require('./proxyHandler') require('harmony-reflect') class Schema { constructor () { this.store = {} - return new Proxy(this, proxy) + return new Proxy(this, proxyHandler) } + /** + * connection to be used while creating schema + * + * @return {String} + * + * @public + */ static get connection () { return 'default' } diff --git a/src/Schema/proxy.js b/src/Schema/proxyHandler.js similarity index 92% rename from src/Schema/proxy.js rename to src/Schema/proxyHandler.js index 3d9e3b48..599fd5cc 100644 --- a/src/Schema/proxy.js +++ b/src/Schema/proxyHandler.js @@ -29,7 +29,7 @@ const aliases = { dropIfExists: 'dropTableIfExists' } -let proxy = exports = module.exports = {} +let proxyHandler = exports = module.exports = {} /** * proxies target get calls and returns custom @@ -42,7 +42,7 @@ let proxy = exports = module.exports = {} * * @public */ -proxy.get = function (target, name) { +proxyHandler.get = function (target, name) { if (target[name] !== undefined || mustImplement.indexOf(name) > -1) { return target[name] } diff --git a/src/Seeder/index.js b/src/Seeder/index.js new file mode 100644 index 00000000..9aff1a59 --- /dev/null +++ b/src/Seeder/index.js @@ -0,0 +1,31 @@ +'use strict' + +/** + * adonis-lucid + * + * (c) Harminder Virk + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. +*/ + +const cf = require('co-functional') +const Ioc = require('adonis-fold').Ioc +const Seeder = exports = module.exports = {} + +/** + * executes given seeds in a sequence by calling + * run method on them. + * + * @method exec + * + * @param {Array} seeds + * + * @public + */ +Seeder.exec = function (seeds) { + cf.forEach(function * (Seed) { + const seedInstance = typeof (Seed) === 'string' ? Ioc.make(Seed) : new Seed() + yield seedInstance.run() + }, seeds) +} diff --git a/test/unit/app/Model/Hooks/Users.js b/test/unit/app/Model/Hooks/Users.js new file mode 100644 index 00000000..7c19be5b --- /dev/null +++ b/test/unit/app/Model/Hooks/Users.js @@ -0,0 +1,8 @@ +'use strict' + +const UsersHooks = exports = module.exports = {} + +UsersHooks.validate = function * (next) { + this.username = 'viahook' + yield next +} diff --git a/test/unit/autoload/foo.js b/test/unit/autoload/foo.js new file mode 100644 index 00000000..e69de29b diff --git a/test/unit/autoload/paths.js b/test/unit/autoload/paths.js new file mode 100644 index 00000000..e69de29b diff --git a/test/unit/autoload/readme.md b/test/unit/autoload/readme.md new file mode 100644 index 00000000..e69de29b diff --git a/test/unit/factory.spec.js b/test/unit/factory.spec.js new file mode 100644 index 00000000..48b3a148 --- /dev/null +++ b/test/unit/factory.spec.js @@ -0,0 +1,181 @@ +'use strict' + +/** + * adonis-lucid + * + * (c) Harminder Virk + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. +*/ + +/* global describe, it, after, before*/ +const chai = require('chai') +const expect = chai.expect +const Model = require('../../src/Lucid/Model') +const Database = require('../../src/Database') +const Factory = require('../../src/Factory') +const filesFixtures = require('./fixtures/files') +const modelFixtures = require('./fixtures/model') +const ModelFactory = require('../../src/Factory/ModelFactory') +const config = require('./helpers/config') +const Ioc = require('adonis-fold').Ioc +require('co-mocha') + +describe('Factory', function () { + before(function * () { + Database._setConfigProvider(config) + yield filesFixtures.createDir() + yield modelFixtures.up(Database) + Factory.clear() + }) + + after(function * () { + yield modelFixtures.down(Database) + Database.close() + }) + + it('should throw an error when blueprint callback is not a function', function () { + const fn = function () { + Factory.blueprint('App/Model/User', 'foo') + } + expect(fn).to.throw(/callback should be a function while define a factory blueprint/) + }) + + it('should be able to define a factory blueprint', function () { + Factory.blueprint('App/Model/User', function (faker) { + return { + username: faker.internet.userName(), + email: faker.internet.email() + } + }) + const blueprints = Factory.blueprints() + expect(blueprints).to.be.an('object') + expect(blueprints['App/Model/User']).to.be.a('function') + }) + + it('should return instance of model factory when using model method', function () { + Ioc.bind('App/Model/User', function () { + return {} + }) + Factory.blueprint('App/Model/User', function (faker) { + return { + username: faker.internet.userName(), + email: faker.internet.email() + } + }) + const userModelFactory = Factory.model('App/Model/User') + expect(userModelFactory instanceof ModelFactory).to.equal(true) + }) + + it('should return the model instance from ModelFactory make method', function () { + class User { + constructor (values) { + this.attributes = values + } + } + Ioc.bind('App/Model/User', function () { + return User + }) + Factory.blueprint('App/Model/User', function (faker) { + return { + username: faker.internet.userName(), + email: faker.internet.email() + } + }) + const user = Factory.model('App/Model/User').make() + expect(user instanceof User).to.equal(true) + expect(user.attributes.username).to.be.a('string') + expect(user.attributes.email).to.be.a('string') + }) + + it('should call user model create method to create given rows inside the database', function * () { + class User { + constructor (values) { + this.attributes = values + } + static * create (values) { + const instance = new this(values) + return instance + } + } + Ioc.bind('App/Model/User', function () { + return User + }) + Factory.blueprint('App/Model/User', function (faker) { + return { + username: faker.internet.userName(), + email: faker.internet.email() + } + }) + const userModelFactory = yield Factory.model('App/Model/User').create(10) + expect(userModelFactory.instances.length).to.equal(10) + userModelFactory.instances.forEach(function (user) { + expect(user instanceof User).to.equal(true) + }) + }) + + it('should be able to loop through each created instance and call generator methods inside it', function * () { + class User { + constructor (values) { + this.attributes = values + } + static * create (values) { + const instance = new this(values) + return instance + } + } + Ioc.bind('App/Model/User', function () { + return User + }) + Factory.blueprint('App/Model/User', function (faker) { + return { + username: faker.internet.userName(), + email: faker.internet.email() + } + }) + const userModelFactory = yield Factory.model('App/Model/User').create(10) + userModelFactory.each(function * (user) { + user.attributes.touched = true + }) + userModelFactory.instances.forEach(function (user) { + expect(user.attributes.touched).to.equal(true) + }) + }) + + it('should create rows inside associated table for a given model', function * () { + class User extends Model {} + Ioc.bind('App/Model/User', function () { + return User + }) + Factory.blueprint('App/Model/User', function (faker) { + return { + username: faker.internet.userName(), + firstname: faker.name.firstName() + } + }) + yield Factory.model('App/Model/User').create(10) + const users = yield User.all() + expect(users.size()).to.equal(10) + yield modelFixtures.truncate(Database) + }) + + it('should truncate rows inside associated table for a given model', function * () { + class User extends Model {} + Ioc.bind('App/Model/User', function () { + return User + }) + Factory.blueprint('App/Model/User', function (faker) { + return { + username: faker.internet.userName(), + firstname: faker.name.firstName() + } + }) + const userModelFactory = yield Factory.model('App/Model/User').create(10) + const users = yield User.all() + expect(users.size()).to.equal(10) + yield userModelFactory.reset() + const afterReset = yield User.all() + expect(afterReset.size()).to.equal(0) + }) +}) diff --git a/test/unit/fixtures/model.js b/test/unit/fixtures/model.js index 59962af1..f318dc1f 100644 --- a/test/unit/fixtures/model.js +++ b/test/unit/fixtures/model.js @@ -56,6 +56,17 @@ module.exports = { return bluebird.all(dropTables) }, + truncate: function (knex) { + const truncateTables = [ + knex.table('users').truncate(), + knex.table('accounts').truncate(), + knex.table('profiles').truncate(), + knex.table('cars').truncate(), + knex.table('keys').truncate() + ] + return bluebird.all(truncateTables) + }, + setupAccount: function (knex) { return knex.table('accounts').insert({account_name: 'sales', created_at: new Date(), updated_at: new Date()}) }, diff --git a/test/unit/util.spec.js b/test/unit/util.spec.js index 23837f7d..0c41e89e 100644 --- a/test/unit/util.spec.js +++ b/test/unit/util.spec.js @@ -11,6 +11,7 @@ /* global describe, it*/ const util = require('../../lib/util') +const path = require('path') const _ = require('lodash') const chai = require('chai') const expect = chai.expect @@ -153,4 +154,9 @@ describe('Utils', function () { const pivotKey = util.makePivotModelKey(AdminUsers) expect(pivotKey).to.equal('admin_user_id') }) + + it('should load all .js files from a given directory', function () { + const files = util.loadJsFiles(path.join(__dirname, './autoload')) + expect(Object.keys(files)).deep.equal(['foo', 'paths']) + }) })