diff --git a/README.md b/README.md index bdb43c0..9f44755 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,11 @@ var person = { 'email': { 'type': 'string', 'required': true + }, + 'relations': { + 'belongsTo': [ 'account' ], + 'hasMany': [ 'phone' ], + 'hasOne': [ 'address' ] } } }; @@ -69,9 +74,12 @@ I like [db-migrate](https://www.npmjs.com/package/db-migrate) to manage the data ### Available methods -##### create({ hash }) +##### create(hash) Create an object with the properties in the hash. This will return a new pg-orm object. +##### createMany([hash]) +Create multiple objects, one for each entry in the array. Only properties that match inside the hash will be used to create the database records. This will return an array of new pg-orm objects. + ##### update() Update the database with the properties of the object that matches tableProperties. This will return the raw pg result. @@ -87,6 +95,15 @@ Delete the object by ID. This will return the raw pg result. ##### delete() Euphamisn for deleteById with the current ID. +### Caveats + +`pg-orm` expects objects to have a primary key named `id`. + +### Todos + +* Add in simple relationships (has / belongs to) +* Add in quick and dirty validation +* Add in event bindings ### Quick start for developing pg-orm Clone the project locally then install node modules: diff --git a/libs/base.js b/libs/base.js index f167c53..7ad35d3 100644 --- a/libs/base.js +++ b/libs/base.js @@ -3,6 +3,8 @@ var pg = require('pg'); var fs = require('fs'); var Q = require('q'); +var _ = require('lodash'); +var humps = require('humps'); // read in the database.json file: var dbFile = './database.json'; @@ -10,11 +12,12 @@ if (fs.existsSync(dbFile) == false) { throw new Error('No database.json to read.'); } -var db = JSON.parse(fs.readFileSync('./database.json', 'utf8')); +var db = JSON.parse(fs.readFileSync(dbFile, 'utf8')); var connectionString = 'postgres://' + db.dev.user + ':' + db.dev.password + '@' + db.dev.host + '/' + db.dev.database; // convenience method: function dbCall(stmnt, values) { + console.log(stmnt, values); // return a promise encased db call var defer = Q.defer(); pg.connect(connectionString, function(err, client, done) { @@ -24,13 +27,12 @@ function dbCall(stmnt, values) { client.query(stmnt, values, function(err, result) { if(err) { - return console.error('Error running query', err); + return console.error('Error running query:\n', err + '\n', stmnt, values + '\n'); } // release! client.end(); defer.resolve(result); - }); }); return defer.promise; @@ -38,43 +40,57 @@ function dbCall(stmnt, values) { // --------------------------------------- -// This should probably be in another file... var Base = function() { this.tableName = null; this.tableProperties = null; }; -Base.prototype.create = function(data) { - var _this = this; - - var _ppo = [], - _propertyNames = [], - _propertyValues = [], - _cnt = 1; +Base.prototype.create = function(hash) { + var _this = this, + ppo = [], + propertyNames = [], + propertyValues = [], + cnt = 1; // check the incoming data to ensure that it's legit mapped to the db: - for (var i in data) { - if (this.tableProperties.hasOwnProperty(i)) { - _propertyNames.push(i); - _ppo.push('$' + _cnt); - _cnt++; - _propertyValues.push(data[i]); + for (var i in hash) { + if (_this.tableProperties.hasOwnProperty(i)) { + propertyNames.push(i); + ppo.push('$' + cnt); + cnt++; + propertyValues.push(hash[i]); } } - var stmnt = 'INSERT INTO ' + _this.tableName + ' ( ' + _propertyNames.join() + ' ) VALUES (' + _ppo.join() + ') RETURNING ID'; + // check relations: + if (_this.tableRelations) { + _.each(_this.tableRelations, function(item, key) { + if (key == 'belongsTo') { + _.each(item, function(bt) { + if (hash.hasOwnProperty(bt) && hash[bt]) { + propertyNames.push(humps.decamelize(bt) + '_id'); + ppo.push('$' + cnt); + cnt++; + propertyValues.push(hash[bt].id); + } + }); + } + }); + } + + var stmnt = 'INSERT INTO ' + _this.tableName + ' ( ' + propertyNames.join() + ' ) VALUES (' + ppo.join() + ') RETURNING ID'; var defer = Q.defer(); - dbCall(stmnt, _propertyValues).then(function(data) { + dbCall(stmnt, propertyValues).then(function(data) { var newObject = new Base(); newObject.tableProperties = _this.tableProperties; newObject.tableName = _this.tableName; newObject.id = data.rows[0].id; - for(var j in _propertyNames) { - var k = _propertyNames[j]; - newObject[k] = _propertyValues[j]; + for(var j in propertyNames) { + var k = propertyNames[j]; + newObject[k] = propertyValues[j]; } defer.resolve(newObject); }); @@ -82,40 +98,50 @@ Base.prototype.create = function(data) { return defer.promise; }; // end create +Base.prototype.createMany = function(arrayOfHashes) { + var _this = this; + var promiseArray = _.map(arrayOfHashes, function(objectDefinition) { + return _this.create(objectDefinition); + }); + + return Q.all(promiseArray); +}; + Base.prototype.update = function(hash) { var _this = this; - var _propertyValues = [], - _cnt = 1, - _stmntChunks = [], - _sourceValues, - _sourceKeys; + var propertyValues = [], + cnt = 1, + stmntChunks = [], + sourceValues, + sourceKeys; + // use our own properties or use a passed-in hash? if (hash) { - _sourceValues = _sourceKeys = hash; + sourceValues = sourceKeys = hash; } else { - _sourceKeys = _this.tableProperties; - _sourceValues = _this; + sourceKeys = _this.tableProperties; + sourceValues = _this; } - if (_sourceKeys.hasOwnProperty('id') == false || _sourceKeys.id == null) { + if (sourceKeys.hasOwnProperty('id') == false || sourceKeys.id == null) { throw new Error('Update call with hash requires \'id\' to update'); } // statement building: - for (var i in _sourceKeys) { + for (var i in sourceKeys) { if (i != 'id') { - _stmntChunks.push(i + ' = $' + _cnt); - _cnt++; - _propertyValues.push(_sourceValues[i]); + stmntChunks.push(i + ' = $' + cnt); + cnt++; + propertyValues.push(sourceValues[i]); } } - _propertyValues.push(_sourceValues.id); + propertyValues.push(sourceValues.id); - var stmnt = 'UPDATE ' + _this.tableName + ' SET ' + _stmntChunks.join() + ' WHERE id=$' + _cnt; + var stmnt = 'UPDATE ' + _this.tableName + ' SET ' + stmntChunks.join() + ' WHERE id=$' + cnt; var defer = Q.defer(); - dbCall(stmnt, _propertyValues).then(function(data) { + dbCall(stmnt, propertyValues).then(function(data) { defer.resolve(data); }); diff --git a/libs/orm.js b/libs/orm.js index 7e72ca1..26bee80 100644 --- a/libs/orm.js +++ b/libs/orm.js @@ -1,17 +1,30 @@ 'use strict'; +var _ = require('lodash'); var baseOrm = require('./base.js'); + module.exports.build = function(obj) { // some simple error checking: if (typeof obj != 'object') throw new Error('Cannot build without an object'); if (obj.hasOwnProperty('tableName') == false && obj.tableName != null) throw new Error('Cannot build without a tableName to connect'); if (obj.hasOwnProperty('tableProperties') == false && obj.tableProperties != null) throw new Error('Cannot build without tableProperties to export'); - var _return = new baseOrm.Base(); - _return.tableName = obj.tableName; - _return.tableProperties = obj.tableProperties; + var newObjectBase = new baseOrm.Base(); + newObjectBase.tableName = obj.tableName; + newObjectBase.tableProperties = obj.tableProperties; + newObjectBase.tableRelations = obj.tableRelations; + + /* if (obj.relations) { + if (obj.relations.belongsTo) { + _.map(obj.relations.belongsTo, function(fakeMethod) { + obj[fakeMethod] = + }); + } + attachRelations(obj, obj.relations.hasMany); + attachRelations(obj, obj.relations.hasOne); + } */ - return _return; + return newObjectBase; }; diff --git a/migrations/20150222174247-test-many.js b/migrations/20150222174247-test-many.js new file mode 100644 index 0000000..4648826 --- /dev/null +++ b/migrations/20150222174247-test-many.js @@ -0,0 +1,14 @@ +dbm = dbm || require('db-migrate'); +var type = dbm.dataType; + +exports.up = function(db, callback) { + db.createTable('test_many', { + id: { type: 'serial', primaryKey: true }, + name: 'string', + test_id: 'integer' + }, callback); +}; + +exports.down = function(db, callback) { + db.dropTable('test_many', callback); +}; diff --git a/package.json b/package.json index 29e59c2..a22bce3 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,8 @@ "orm" ], "dependencies": { + "humps": "^0.4.2", + "lodash": "^3.3.0", "pg": "^4.2.0", "q": "^1.1.2" }, diff --git a/test/base.js b/test/base.js index ae4767a..9261eb4 100644 --- a/test/base.js +++ b/test/base.js @@ -6,19 +6,19 @@ var orm = require('../libs/orm.js'); // tie the base tests to the test table: var testBase = orm.build({ - 'tableName': 'test', - 'tableProperties': { - 'id': { - 'type': 'key' + tableName: 'test', + tableProperties: { + id: { + type: 'key' }, - 'name': { - 'type': 'string' + name: { + type: 'string' } } }); describe('crud operators', function () { - it('should should build an object', function () { + it('should build an object', function () { assert(testBase.tableName == 'test', 'named object should be named'); }); @@ -31,6 +31,13 @@ describe('crud operators', function () { }); }); + it('should be able to create an array of objects', function(done) { + testBase.createMany([{ 'name': 'Create1' }, { 'name': 'Create2' }]).then(function(testObjectArray) { + assert(testObjectArray.length == 2, 'Return array should have 2 items'); + done(); + }); + }); + it('should be able to update an existing object', function(done) { testBase.create({ 'name': 'Update' }).then(function(testObject) { testObject.name = 'Mike'; @@ -74,6 +81,7 @@ describe('crud operators', function () { it('should be able to delete an existing object', function(done) { testBase.create({ 'name': 'Delete' }).then(function(testObject) { testObject.delete().then(function(data) { + // TODO: reload the object, make sure its gone done(); }) }); diff --git a/test/relations.js b/test/relations.js new file mode 100644 index 0000000..f825740 --- /dev/null +++ b/test/relations.js @@ -0,0 +1,52 @@ +/* Test of our ability to do relations */ +'use strict'; + +var assert = require('assert'); +var orm = require('../libs/orm.js'); +var _ = require('lodash'); + +// tie the base tests to the test table: +var testBase = orm.build({ + tableName: 'test', + tableProperties: { + id: { + type: 'key' + }, + name: { + type: 'string' + } + }, + tableRelations: { + hasMany: [ 'testMany' ] + } +}); + +var testMany = orm.build({ + tableName: 'test_many', + tableProperties: { + id: { + type: 'key' + }, + name: { + type: 'string' + } + }, + tableRelations: { + belongsTo: [ 'test' ] + } +}); + +describe('relationships matter', function () { + it('should be able to assign a parent to a hasMany', function (done) { + testBase.create({ name: 'HasMany' }).then(function(hasManyObject) { + testMany.createMany([{ name: 'belongs1', test: hasManyObject }, { name: 'belongs2', test: hasManyObject }]).then(function(belongsToArray) { + assert(belongsToArray.length == 2, 'Should have 2 objects'); + _.each(belongsToArray, function(obj) { + assert(obj['test_id'] == hasManyObject.id, 'Relationships should map back'); + }); + done(); + }); + }) + }); + +});