Automigrade/update
This commit is contained in:
parent
e72db08ad0
commit
4cb7af139d
2
Makefile
2
Makefile
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@./support/nodeunit/bin/nodeunit test/common_test.js
|
@ONLY=memory ./support/nodeunit/bin/nodeunit test/*_test.*
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
|
|
|
@ -85,6 +85,10 @@ function AbstractClass(data) {
|
||||||
this.trigger("initialize");
|
this.trigger("initialize");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AbstractClass.defineProperty = function (prop, params) {
|
||||||
|
this.schema.defineProperty(this.modelName, prop, params);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param data [optional]
|
* @param data [optional]
|
||||||
* @param callback(err, obj)
|
* @param callback(err, obj)
|
||||||
|
@ -412,10 +416,9 @@ AbstractClass.prototype.reset = function () {
|
||||||
AbstractClass.hasMany = function (anotherClass, params) {
|
AbstractClass.hasMany = function (anotherClass, params) {
|
||||||
var methodName = params.as; // or pluralize(anotherClass.modelName)
|
var methodName = params.as; // or pluralize(anotherClass.modelName)
|
||||||
var fk = params.foreignKey;
|
var fk = params.foreignKey;
|
||||||
// console.log(this.modelName, 'has many', anotherClass.modelName, 'as', params.as, 'queried by', params.foreignKey);
|
|
||||||
// each instance of this class should have method named
|
// each instance of this class should have method named
|
||||||
// pluralize(anotherClass.modelName)
|
// pluralize(anotherClass.modelName)
|
||||||
// which is actually just anotherClass.all({thisModelNameId: this.id}, cb);
|
// which is actually just anotherClass.all({where: {thisModelNameId: this.id}}, cb);
|
||||||
defineScope(this.prototype, anotherClass, methodName, function () {
|
defineScope(this.prototype, anotherClass, methodName, function () {
|
||||||
var x = {};
|
var x = {};
|
||||||
x[fk] = this.id;
|
x[fk] = this.id;
|
||||||
|
|
|
@ -27,9 +27,14 @@ MySQL.prototype.define = function (descr) {
|
||||||
this._models[descr.model.modelName] = descr;
|
this._models[descr.model.modelName] = descr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MySQL.prototype.defineProperty = function (model, prop, params) {
|
||||||
|
this._models[model].properties[prop] = params;
|
||||||
|
};
|
||||||
|
|
||||||
MySQL.prototype.query = function (sql, callback) {
|
MySQL.prototype.query = function (sql, callback) {
|
||||||
var time = Date.now();
|
var time = Date.now();
|
||||||
var log = this.log;
|
var log = this.log;
|
||||||
|
if (typeof callback !== 'function') throw new Error('callback should be a function');
|
||||||
this.client.query(sql, function (err, data) {
|
this.client.query(sql, function (err, data) {
|
||||||
log(sql, time);
|
log(sql, time);
|
||||||
callback(err, data);
|
callback(err, data);
|
||||||
|
@ -191,3 +196,118 @@ MySQL.prototype.disconnect = function disconnect() {
|
||||||
this.client.end();
|
this.client.end();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MySQL.prototype.automigrate = function (cb) {
|
||||||
|
var self = this;
|
||||||
|
var wait = 0;
|
||||||
|
Object.keys(this._models).forEach(function (model) {
|
||||||
|
wait += 1;
|
||||||
|
self.dropTable(model, function () {
|
||||||
|
self.createTable(model, function (err) {
|
||||||
|
if (err) console.log(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function done() {
|
||||||
|
if (--wait === 0 && cb) {
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MySQL.prototype.autoupdate = function (cb) {
|
||||||
|
var self = this;
|
||||||
|
var wait = 0;
|
||||||
|
Object.keys(this._models).forEach(function (model) {
|
||||||
|
wait += 1;
|
||||||
|
self.query('SHOW FIELDS FROM ' + model, function (err, fields) {
|
||||||
|
self.alterTable(model, fields, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function done(err) {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
if (--wait === 0 && cb) {
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MySQL.prototype.alterTable = function (model, actualFields, done) {
|
||||||
|
var self = this;
|
||||||
|
var m = this._models[model];
|
||||||
|
var propNames = Object.keys(m.properties);
|
||||||
|
var sql = [];
|
||||||
|
actualFields.forEach(function (f) {
|
||||||
|
if (f.Field !== 'id') {
|
||||||
|
actualize(f.Field, f);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sql.length) {
|
||||||
|
this.query('ALTER TABLE `' + model + '` ' + sql.join(',\n'), done);
|
||||||
|
} else {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
|
function actualize(propName, oldSettings) {
|
||||||
|
var newSettings = m.properties[propName];
|
||||||
|
if (!newSettings) {
|
||||||
|
sql.push('ADD COLUMN `' + propName + '` ' + self.propertySettingsSQL(model, propName));
|
||||||
|
} else if (changed(newSettings, oldSettings)) {
|
||||||
|
sql.push('CHANGE COLUMN `' + propName + '` `' + propName + '` ' + self.propertySettingsSQL(model, propName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changed(newSettings, oldSettings) {
|
||||||
|
if (oldSettings.Null === 'YES' && (newSettings.allowNull === false || newSettings.null === false)) return true;
|
||||||
|
if (oldSettings.Null === 'NO' && !(newSettings.allowNull === false || newSettings.null === false)) return true;
|
||||||
|
if (oldSettings.Type.toUpperCase() !== datatype(newSettings)) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MySQL.prototype.dropTable = function (model, cb) {
|
||||||
|
this.query('DROP TABLE IF EXISTS ' + model, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
MySQL.prototype.createTable = function (model, cb) {
|
||||||
|
this.query('CREATE TABLE ' + model +
|
||||||
|
' (\n ' + this.propertiesSQL(model) + '\n)', cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
MySQL.prototype.propertiesSQL = function (model) {
|
||||||
|
var self = this;
|
||||||
|
var sql = ['`id` INT(11) NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY'];
|
||||||
|
Object.keys(this._models[model].properties).forEach(function (prop) {
|
||||||
|
sql.push('`' + prop + '` ' + self.propertySettingsSQL(model, prop));
|
||||||
|
});
|
||||||
|
return sql.join(',\n ');
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
MySQL.prototype.propertySettingsSQL = function (model, prop) {
|
||||||
|
var p = this._models[model].properties[prop];
|
||||||
|
return datatype(p) + ' ' +
|
||||||
|
(p.allowNull === false || p['null'] === false ? 'NOT NULL' : 'NULL') +
|
||||||
|
''; // (p.index ? ' KEY ix' + model + '_' + prop : '');
|
||||||
|
};
|
||||||
|
|
||||||
|
function datatype(p) {
|
||||||
|
switch (p.type.name) {
|
||||||
|
case 'String':
|
||||||
|
return 'VARCHAR(' + (p.limit || 255) + ')';
|
||||||
|
case 'Text':
|
||||||
|
return 'TEXT';
|
||||||
|
case 'Number':
|
||||||
|
return 'INT(11)';
|
||||||
|
case 'Date':
|
||||||
|
return 'DATETIME';
|
||||||
|
case 'Boolean':
|
||||||
|
return 'TINYINT(1)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,12 +75,28 @@ function Text() {
|
||||||
}
|
}
|
||||||
Schema.Text = Text;
|
Schema.Text = Text;
|
||||||
|
|
||||||
|
Schema.prototype.defineProperty = function (model, prop, params) {
|
||||||
|
this.definitions[model].properties[prop] = params;
|
||||||
|
if (this.adapter.defineProperty) {
|
||||||
|
this.adapter.defineProperty(model, prop, params);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Schema.prototype.automigrate = function (cb) {
|
Schema.prototype.automigrate = function (cb) {
|
||||||
this.freeze();
|
this.freeze();
|
||||||
if (this.adapter.automigrate) {
|
if (this.adapter.automigrate) {
|
||||||
this.adapter.automigrate(cb);
|
this.adapter.automigrate(cb);
|
||||||
} else {
|
} else if (cb) {
|
||||||
cb && cb();
|
cb();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Schema.prototype.autoupdate = function (cb) {
|
||||||
|
this.freeze();
|
||||||
|
if (this.adapter.autoupdate) {
|
||||||
|
this.adapter.autoupdate(cb);
|
||||||
|
} else if (cb) {
|
||||||
|
cb();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -127,6 +127,10 @@ Validatable.prototype.isValid = function (callback) {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!async) {
|
||||||
|
validationsDone();
|
||||||
|
}
|
||||||
|
|
||||||
var asyncFail = false;
|
var asyncFail = false;
|
||||||
function done(fail) {
|
function done(fail) {
|
||||||
asyncFail = asyncFail || fail;
|
asyncFail = asyncFail || fail;
|
||||||
|
|
|
@ -68,6 +68,14 @@ function testOrm(schema) {
|
||||||
// user.posts.create(data) // build and save
|
// user.posts.create(data) // build and save
|
||||||
// user.posts.find
|
// user.posts.find
|
||||||
|
|
||||||
|
// User.hasOne('latestPost', {model: Post, foreignKey: 'postId'});
|
||||||
|
|
||||||
|
// User.hasOne(Post, {as: 'latestPost', foreignKey: 'latestPostId'});
|
||||||
|
// creates instance methods:
|
||||||
|
// user.latestPost()
|
||||||
|
// user.latestPost.build(data)
|
||||||
|
// user.latestPost.create(data)
|
||||||
|
|
||||||
Post.belongsTo(User, {as: 'author', foreignKey: 'userId'});
|
Post.belongsTo(User, {as: 'author', foreignKey: 'userId'});
|
||||||
// creates instance methods:
|
// creates instance methods:
|
||||||
// post.author(callback) -- getter when called with function
|
// post.author(callback) -- getter when called with function
|
||||||
|
@ -309,6 +317,12 @@ function testOrm(schema) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// it('should handle hasOne relationship', function (test) {
|
||||||
|
// User.create(function (err, u) {
|
||||||
|
// if (err) return console.log(err);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
it('should support scopes', function (test) {
|
it('should support scopes', function (test) {
|
||||||
var wait = 2;
|
var wait = 2;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
juggling = require('../index')
|
||||||
|
Schema = juggling.Schema
|
||||||
|
Text = Schema.Text
|
||||||
|
|
||||||
|
DBNAME = 'migrationtest'
|
||||||
|
DBUSER = 'root'
|
||||||
|
DBPASS = ''
|
||||||
|
|
||||||
|
require('./spec_helper').init module.exports
|
||||||
|
|
||||||
|
schema = new Schema 'mysql', database: '', username: DBUSER, password: DBPASS
|
||||||
|
schema.log = (q) -> console.log q
|
||||||
|
|
||||||
|
query = (sql, cb) ->
|
||||||
|
schema.adapter.query sql, cb
|
||||||
|
|
||||||
|
User = schema.define 'User',
|
||||||
|
email: { type: String, null: false, index: true }
|
||||||
|
name: String
|
||||||
|
bio: Text
|
||||||
|
password: String
|
||||||
|
birthDate: Date
|
||||||
|
pendingPeriod: Number
|
||||||
|
createdByAdmin: Boolean
|
||||||
|
|
||||||
|
withBlankDatabase = (cb) ->
|
||||||
|
db = schema.settings.database = DBNAME
|
||||||
|
query 'DROP DATABASE IF EXISTS ' + db, (err) ->
|
||||||
|
query 'CREATE DATABASE ' + db, (err) ->
|
||||||
|
query 'USE '+ db, cb
|
||||||
|
|
||||||
|
getFields = (model, cb) ->
|
||||||
|
query 'SHOW FIELDS FROM ' + model, (err, res) ->
|
||||||
|
if err
|
||||||
|
cb err
|
||||||
|
else
|
||||||
|
fields = {}
|
||||||
|
res.forEach (field) -> fields[field.Field] = field
|
||||||
|
cb err, fields
|
||||||
|
|
||||||
|
it 'should run migration', (test) ->
|
||||||
|
withBlankDatabase (err) ->
|
||||||
|
schema.automigrate ->
|
||||||
|
getFields 'User', (err, fields) ->
|
||||||
|
test.deepEqual fields,
|
||||||
|
id:
|
||||||
|
Field: 'id'
|
||||||
|
Type: 'int(11)'
|
||||||
|
Null: 'NO'
|
||||||
|
Key: 'PRI'
|
||||||
|
Default: null
|
||||||
|
Extra: 'auto_increment'
|
||||||
|
email:
|
||||||
|
Field: 'email'
|
||||||
|
Type: 'varchar(255)'
|
||||||
|
Null: 'NO'
|
||||||
|
Key: ''
|
||||||
|
Default: null
|
||||||
|
Extra: ''
|
||||||
|
name:
|
||||||
|
Field: 'name'
|
||||||
|
Type: 'varchar(255)'
|
||||||
|
Null: 'YES'
|
||||||
|
Key: ''
|
||||||
|
Default: null
|
||||||
|
Extra: ''
|
||||||
|
bio:
|
||||||
|
Field: 'bio'
|
||||||
|
Type: 'text'
|
||||||
|
Null: 'YES'
|
||||||
|
Key: ''
|
||||||
|
Default: null
|
||||||
|
Extra: ''
|
||||||
|
password:
|
||||||
|
Field: 'password'
|
||||||
|
Type: 'varchar(255)'
|
||||||
|
Null: 'YES'
|
||||||
|
Key: ''
|
||||||
|
Default: null
|
||||||
|
Extra: ''
|
||||||
|
birthDate:
|
||||||
|
Field: 'birthDate'
|
||||||
|
Type: 'datetime'
|
||||||
|
Null: 'YES'
|
||||||
|
Key: ''
|
||||||
|
Default: null
|
||||||
|
Extra: ''
|
||||||
|
pendingPeriod:
|
||||||
|
Field: 'pendingPeriod'
|
||||||
|
Type: 'int(11)'
|
||||||
|
Null: 'YES'
|
||||||
|
Key: ''
|
||||||
|
Default: null
|
||||||
|
Extra: ''
|
||||||
|
createdByAdmin:
|
||||||
|
Field: 'createdByAdmin'
|
||||||
|
Type: 'tinyint(1)'
|
||||||
|
Null: 'YES'
|
||||||
|
Key: ''
|
||||||
|
Default: null
|
||||||
|
Extra: ''
|
||||||
|
|
||||||
|
test.done()
|
||||||
|
|
||||||
|
it 'should autoupgrade', (test) ->
|
||||||
|
userExists = (cb) ->
|
||||||
|
query 'SELECT * FROM User', (err, res) ->
|
||||||
|
cb(not err and res[0].email == 'test@example.com')
|
||||||
|
|
||||||
|
User.create email: 'test@example.com', (err, user) ->
|
||||||
|
test.ok not err
|
||||||
|
userExists (yep) ->
|
||||||
|
test.ok yep
|
||||||
|
User.defineProperty 'email', type: String
|
||||||
|
User.defineProperty 'name', type: String, limit: 50
|
||||||
|
User.defineProperty 'newProperty', type: Number
|
||||||
|
schema.autoupdate (err) ->
|
||||||
|
getFields 'User', (err, fields) ->
|
||||||
|
test.equal fields.email.Null, 'YES'
|
||||||
|
test.equal fields.name.Type, 'varchar(50)'
|
||||||
|
test.ok fields.newProperty
|
||||||
|
if fields.newProperty
|
||||||
|
test.equal fields.newProperty.Type, 'int(11)'
|
||||||
|
userExists (yep) ->
|
||||||
|
test.ok yep
|
||||||
|
test.done()
|
||||||
|
|
||||||
|
it 'should disconnect when done', (test) ->
|
||||||
|
schema.disconnect()
|
||||||
|
test.done()
|
Loading…
Reference in New Issue