From 3b920a11a557b5f72105f89d858f3bd28c02684b Mon Sep 17 00:00:00 2001 From: Andrew Ettinger Date: Sun, 22 Feb 2015 10:12:50 -0800 Subject: [PATCH 1/2] Add createMany helper method --- README.md | 19 +++++- libs/base.js | 83 ++++++++++++++------------ libs/orm.js | 20 +++++-- migrations/20150222174247-test-many.js | 14 +++++ package.json | 1 + test/base.js | 21 ++++--- test/relations.js | 43 +++++++++++++ 7 files changed, 152 insertions(+), 49 deletions(-) create mode 100644 migrations/20150222174247-test-many.js create mode 100644 test/relations.js 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..bf8b994 100644 --- a/libs/base.js +++ b/libs/base.js @@ -3,6 +3,7 @@ var pg = require('pg'); var fs = require('fs'); var Q = require('q'); +var _ = require('lodash'); // read in the database.json file: var dbFile = './database.json'; @@ -10,7 +11,7 @@ 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: @@ -24,13 +25,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 +38,41 @@ 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) { + for (var i in hash) { if (this.tableProperties.hasOwnProperty(i)) { - _propertyNames.push(i); - _ppo.push('$' + _cnt); - _cnt++; - _propertyValues.push(data[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'; + 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 +80,51 @@ Base.prototype.create = function(data) { return defer.promise; }; // end create +Base.prototype.createMany = function(arrayOfHashes) { + var _this = this; + var promiseArray = _.map(arrayOfHashes, function(objectDefinition) { + console.log(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..76ed4d4 100644 --- a/libs/orm.js +++ b/libs/orm.js @@ -1,17 +1,29 @@ '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; + + /* 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..7f97e6f 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "orm" ], "dependencies": { + "lodash": "^3.3.0", "pg": "^4.2.0", "q": "^1.1.2" }, diff --git a/test/base.js b/test/base.js index ae4767a..6dbcad5 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'; diff --git a/test/relations.js b/test/relations.js new file mode 100644 index 0000000..601719a --- /dev/null +++ b/test/relations.js @@ -0,0 +1,43 @@ +/* Test of our ability to do relations */ +'use strict'; + +var assert = require('assert'); +var orm = require('../libs/orm.js'); + +// tie the base tests to the test table: +var testBase = orm.build({ + tableName: 'test', + tableProperties: { + id: { + type: 'key' + }, + name: { + type: 'string' + } + }, + relations: { + hasMany: [ 'testMany' ] + } +}); + +var testMany = orm.build({ + tableName: 'test_many', + tableProperties: { + id: { + type: 'key' + }, + name: { + type: 'string' + } + }, + relations: { + belongsTo: [ 'test' ] + } +}); + +describe('crud operators', function () { + it('should be able to load', function () { + assert(testBase.tableName == 'test', 'named object should be named'); + }); + +}); From f23fb8c2b07723e5c1cb69422c9bf6a9fc2f9e99 Mon Sep 17 00:00:00 2001 From: Andrew Ettinger Date: Sun, 22 Feb 2015 10:38:57 -0800 Subject: [PATCH 2/2] Rough ability to create with a belongsTo definition --- libs/base.js | 21 +++++++++++++++++++-- libs/orm.js | 1 + package.json | 1 + test/base.js | 1 + test/relations.js | 19 ++++++++++++++----- 5 files changed, 36 insertions(+), 7 deletions(-) diff --git a/libs/base.js b/libs/base.js index bf8b994..7ad35d3 100644 --- a/libs/base.js +++ b/libs/base.js @@ -4,6 +4,7 @@ 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'; @@ -16,6 +17,7 @@ var connectionString = 'postgres://' + db.dev.user + ':' + db.dev.password + '@' // 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) { @@ -52,7 +54,7 @@ Base.prototype.create = function(hash) { // check the incoming data to ensure that it's legit mapped to the db: for (var i in hash) { - if (this.tableProperties.hasOwnProperty(i)) { + if (_this.tableProperties.hasOwnProperty(i)) { propertyNames.push(i); ppo.push('$' + cnt); cnt++; @@ -60,6 +62,22 @@ Base.prototype.create = function(hash) { } } + // 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(); @@ -83,7 +101,6 @@ Base.prototype.create = function(hash) { Base.prototype.createMany = function(arrayOfHashes) { var _this = this; var promiseArray = _.map(arrayOfHashes, function(objectDefinition) { - console.log(objectDefinition); return _this.create(objectDefinition); }); diff --git a/libs/orm.js b/libs/orm.js index 76ed4d4..26bee80 100644 --- a/libs/orm.js +++ b/libs/orm.js @@ -13,6 +13,7 @@ module.exports.build = function(obj) { var newObjectBase = new baseOrm.Base(); newObjectBase.tableName = obj.tableName; newObjectBase.tableProperties = obj.tableProperties; + newObjectBase.tableRelations = obj.tableRelations; /* if (obj.relations) { if (obj.relations.belongsTo) { diff --git a/package.json b/package.json index 7f97e6f..a22bce3 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "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 6dbcad5..9261eb4 100644 --- a/test/base.js +++ b/test/base.js @@ -81,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 index 601719a..f825740 100644 --- a/test/relations.js +++ b/test/relations.js @@ -3,6 +3,7 @@ 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({ @@ -15,7 +16,7 @@ var testBase = orm.build({ type: 'string' } }, - relations: { + tableRelations: { hasMany: [ 'testMany' ] } }); @@ -30,14 +31,22 @@ var testMany = orm.build({ type: 'string' } }, - relations: { + tableRelations: { belongsTo: [ 'test' ] } }); -describe('crud operators', function () { - it('should be able to load', function () { - assert(testBase.tableName == 'test', 'named object should be named'); +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(); + }); + }) }); });