Permalink
Browse files

Get transparent auto-increment ids working

  • Loading branch information...
1 parent 08c44f6 commit 1aeb66c09a3a26eb601d2df472dbdb4160fc1cc5 mde committed Aug 17, 2012
Showing with 129 additions and 54 deletions.
  1. +26 −18 lib/adapters/sql/postgres.js
  2. +15 −4 lib/generators/sql.js
  3. +36 −23 lib/index.js
  4. +32 −7 test/adapters/sql/postgres.js
  5. +5 −1 test/create_user.js
  6. +2 −0 test/fixtures/user.js
  7. +13 −1 test/generators/sql.js
@@ -14,7 +14,6 @@ _baseConfig = {
, password: null
, port: 5432
, host: null
-, autoConnect: true
};
Adapter = function (options) {
@@ -39,6 +38,7 @@ utils.mixin(Adapter.prototype, new (function () {
var self = this
, items = Array.isArray(data) ? data : [data]
, modelName = items[0].type
+ , ctor = model[modelName]
, reg = model.descriptionRegistry
, props = reg[modelName].properties
, prop
@@ -50,13 +50,19 @@ utils.mixin(Adapter.prototype, new (function () {
var cols = []
, vals = [];
- // FIXME: Is this the right way to deal with transparent IDs?
- item.id = utils.string.uuid();
- cols.push(self._columnizePropertyName('id'));
- vals.push(datatypes.string.serialize(item.id, {
- escape: true
- , useQuotes: true
- }));
+ // If using string UUID ids
+ if (!ctor.autoIncrementId) {
+ item.id = utils.string.uuid();
+ cols.push(self._columnizePropertyName('id'));
+ vals.push(datatypes.string.serialize(item.id, {
+ escape: true
+ , useQuotes: true
+ }));
+ }
+ else {
+ cols.push(self._columnizePropertyName('id'));
+ vals.push('DEFAULT');
+ }
for (var p in props) {
def = props[p];
@@ -75,17 +81,22 @@ utils.mixin(Adapter.prototype, new (function () {
sql += '(' + cols.join(', ') + ')';
sql += ' VALUES ';
sql += '(' + vals.join(', ') + ')';
+ sql += ' RETURNING id'
sql += ';\n';
});
this.exec(sql, function (err, res) {
+ var item
+ , rows = res.rows;
if (err) {
callback(err, null);
}
else {
- items.forEach(function (item) {
- item.saved = true;
- });
+ for (var i = 0, ii = items.length; i < ii; i++) {
+ item = items[i];
+ item.id = rows[i].id;
+ item._saved = true;
+ }
callback(null, items.length == 1 ? items[0] : items);
}
});
@@ -141,17 +152,14 @@ utils.mixin(Adapter.prototype, new (function () {
callback(err, null);
}
else {
- data.saved = true;
+ data._saved = true;
callback(null, data);
}
});
};
this.init = function () {
this.client = new pg.Client(this.config);
- if (this.config.autoConnect) {
- this.connect();
- }
};
this.connect = function () {
@@ -200,7 +208,7 @@ utils.mixin(Adapter.prototype, new (function () {
rows.forEach(function (row) {
var inst = query.model.create(row);
inst.id = row.id;
- inst.saved = true;
+ inst._saved = true;
res.push(inst);
});
// `load` method
@@ -241,7 +249,7 @@ utils.mixin(Adapter.prototype, new (function () {
saved = false;
for (var i = 0, ii = data.length; i < ii; i++) {
item = data[i];
- if (item.saved) {
+ if (item._saved) {
return callback(new Error('A bulk-save can only have new ' +
'items in it.'), null);
}
@@ -252,7 +260,7 @@ utils.mixin(Adapter.prototype, new (function () {
}
}
else {
- saved = data.saved;
+ saved = data._saved;
// Bail out if instance isn't valid
if (!data.isValid()) {
return callback(data.errors, null);
View
@@ -15,7 +15,7 @@ datatypeMap = {
};
generator = new (function () {
- var _createTable = function (name, props) {
+ var _createTable = function (name, props, opts) {
var sql = ''
, tableName
, prop
@@ -28,8 +28,14 @@ generator = new (function () {
sql += 'DROP TABLE IF EXISTS ' + tableName + ';\n';
sql += 'CREATE TABLE ' + tableName + ' (\n';
- // FIXME: What to do with IDs?
- propArr.push(' id varchar(256) PRIMARY KEY');
+ // Use DB auto-increment
+ if (opts.autoIncrementId) {
+ propArr.push(' id SERIAL PRIMARY KEY');
+ }
+ // Use string UUIDs
+ else {
+ propArr.push(' id varchar(256) PRIMARY KEY');
+ }
for (var p in props) {
prop = props[p];
@@ -48,12 +54,17 @@ generator = new (function () {
this.createTable = function (modelNames) {
var sql = ''
, reg = model.descriptionRegistry
+ , ctor
, props
+ , opts
, names = Array.isArray(modelNames) ?
modelNames : [modelNames];
names.forEach(function (name) {
+ opts = {};
+ ctor = model[name];
+ opts.autoIncrementId = ctor.autoIncrementId;
props = reg[name].properties;
- sql += _createTable(name, props);
+ sql += _createTable(name, props, opts);
});
return sql;
};
View
@@ -54,21 +54,24 @@ utils.mixin(model, new (function () {
this.adapters = {};
this.loadedAdapters = {};
this.descriptionRegistry = {};
- this.useTimestamps = false;
+ this.useTimestamps = true;
this.forceCamel = true;
var _createModelItemConstructor = function (def) {
// Base constructor function for all model items
var ModelItemConstructor = function (params) {
- var self = this;
+ var self = this
+ , relations = model.descriptionRegistry[def.name].relations
+ , relationKey;
+
this.type = def.name;
// Items fetched from an API should have this flag set to true
- this.saved = params.saved || false;
+ this._saved = params._saved || false;
// If fetched and instantiated from an API-call, give the
// instance the appropriate ID -- newly created objects won't
// have one until saved
- if (params.saved) {
+ if (params.id) {
this.id = params.id;
}
@@ -153,36 +156,45 @@ utils.mixin(model, new (function () {
this.toJson = this.toString;
- if (model.descriptionRegistry[this.type].relations.hasMany) {
- var hasMany = model.descriptionRegistry[this.type].relations.hasMany;
- this[utils.string.decapitalize(hasMany)] = function (query, opts, callback) {
+ // Relation intstance-methods
+ // FIXME: There's got to be a nicer way to do this dynamic-method crap
+ relationKey = relations.hasMany;
+ if (relationKey) {
+ this[utils.string.decapitalize(relationKey)] = function (query,
+ opts, callback) {
query = query || {};
opts = opts || {};
- callback = callback || function(){};
- query[this.type.toLowerCase() + 'id'] = this.id;
- model.adapters[geddy.inflection.singularize(hasMany)].all(query, opts, callback);
+ callback = callback || function () {};
+ query[geddy.string.decapitalize(this.type) + 'Id'] = this.id;
+ model.adapters[geddy.inflection.singularize(
+ relationKey)].all(query, opts, callback);
}
}
- if (model.descriptionRegistry[this.type].relations.hasOne) {
- var hasOne = model.descriptionRegistry[this.type].relations.hasOne;
- this[utils.string.decapitalize(hasOne)] = function (callback) {
+ relationKey = relations.hasOne;
+ if (relationKey) {
+ this[utils.string.decapitalize(relationKey)] = function (callback) {
var query = {};
- query[this.type.toLowerCase() + 'id'] = this.id;
- model.adapters[hasOne].load(query, {}, callback);
+ query[geddy.string.decapitalize(this.type) + 'Id'] = this.id;
+ model.adapters[relationKey].load(query, {}, callback);
}
}
- if (model.descriptionRegistry[this.type].relations.belongsTo) {
- var belongsTo = model.descriptionRegistry[this.type].relations.belongsTo;
- this[utils.string.decapitalize(belongsTo)] = function (callback) {
- model.adapters[belongsTo].load(this[utils.string.decapitalize(belongsTo)
- + 'id'], {}, callback);
+ relationKey = relations.belongsTo;
+ if (relationKey) {
+ this[utils.string.decapitalize(relationKey)] = function (callback) {
+ model.adapters[relationKey].load(
+ this[utils.string.decapitalize(relationKey)
+ + 'Id'], {}, callback);
}
}
};
+ if (def.autoIncrementId) {
+ ModelItemConstructor.autoIncrementId = true;
+ }
+
return ModelItemConstructor;
};
@@ -258,7 +270,7 @@ utils.mixin(model, new (function () {
callback(data.errors, null);
}
else {
- if (model.useTimestamps && data.saved) {
+ if (model.useTimestamps && data._saved) {
this.updatedAt = new Date();
}
return adapt.save.apply(adapt, [data, opts, callback]);
@@ -518,6 +530,7 @@ model.ModelDefinitionBase = function (name) {
};
this.name = name;
+
this.property = function (name, datatype, o) {
model.descriptionRegistry[this.name].properties[name] =
new model.PropertyDescription(name, datatype, o);
@@ -544,8 +557,8 @@ model.ModelDefinitionBase = function (name) {
// Add the base model properties -- these should not be handled by user input
if (model.useTimestamps) {
- this.property('createdAt', 'datetime');
- this.property('updatedAt', 'datetime');
+ //this.property('createdAt', 'datetime');
+ //this.property('updatedAt', 'datetime');
}
this.hasMany = function (pluralCtorName) {
@@ -18,20 +18,19 @@ var utils = require('utilities')
, currentId
, tests
, testItems = []
- , Zooby = require('../../fixtures/zooby').Zooby;
+ , Zooby = require('../../fixtures/zooby').Zooby
+ , User = require('../../fixtures/user').User;
tests = {
'before': function (next) {
var sql;
adapter = new Adapter({
database: 'model_test'
- , autoConnect: false
});
model.adapters.Zooby = adapter;
- sql = generator.createTable(['Zooby']);
adapter.once('connect', function () {
- var sql = generator.createTable(['Zooby']);
+ var sql = generator.createTable(['Zooby', 'User']);
adapter.exec(sql, function (err, data) {
if (err) {
throw err;
@@ -41,8 +40,9 @@ tests = {
});
adapter.connect();
- model.adapter = {
+ model.adapters = {
'Zooby': adapter
+ , 'User': adapter
};
}
@@ -68,7 +68,32 @@ tests = {
});
}
-, 'test save new': function (next) {
+, 'test save new, auto-increment id': function (next) {
+ var u = User.create({
+ login: 'asdf'
+ , password: 'zerb'
+ , confirmPassword: 'zerb'
+ });
+ u.save(function (err, data) {
+ if (err) {
+ throw err;
+ }
+ currentId = u.id;
+ next();
+ });
+ }
+
+, 'test load via auto-increment id': function (next) {
+ User.load(currentId, {}, function (err, data) {
+ if (err) {
+ throw err;
+ }
+ assert.equal(data.id, currentId);
+ next();
+ });
+ }
+
+, 'test save new, string UUID id': function (next) {
var z = Zooby.create({foo: 'FOO'});
z.save(function (err, data) {
if (err) {
@@ -79,7 +104,7 @@ tests = {
});
}
-, 'test load via id': function (next) {
+, 'test load via string id': function (next) {
Zooby.load(currentId, {}, function (err, data) {
if (err) {
throw err;
View
@@ -14,7 +14,11 @@ _params = {
tests = {
- 'test validity': function () {
+ 'test autoIncrementId': function () {
+ assert.ok(User.autoIncrementId);
+ }
+
+, 'test validity': function () {
var user = User.create(_params);
assert.ok(user.isValid());
}
@@ -11,6 +11,8 @@ var User = function () {
this.validatesFormat('login', /[a-z]+/, {message: 'Subdivisions!'});
this.validatesLength('login', {min: 3});
this.validatesConfirmed('password', 'confirmPassword');
+
+ this.autoIncrementId = true;
};
User.prototype.someMethod = function () {
Oops, something went wrong. Retry.

0 comments on commit 1aeb66c

Please sign in to comment.