Automigrade/update
This commit is contained in:
parent
e72db08ad0
commit
4cb7af139d
2
Makefile
2
Makefile
|
@ -1,5 +1,5 @@
|
|||
|
||||
test:
|
||||
@./support/nodeunit/bin/nodeunit test/common_test.js
|
||||
@ONLY=memory ./support/nodeunit/bin/nodeunit test/*_test.*
|
||||
|
||||
.PHONY: test
|
||||
|
|
|
@ -85,6 +85,10 @@ function AbstractClass(data) {
|
|||
this.trigger("initialize");
|
||||
};
|
||||
|
||||
AbstractClass.defineProperty = function (prop, params) {
|
||||
this.schema.defineProperty(this.modelName, prop, params);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param data [optional]
|
||||
* @param callback(err, obj)
|
||||
|
@ -412,10 +416,9 @@ AbstractClass.prototype.reset = function () {
|
|||
AbstractClass.hasMany = function (anotherClass, params) {
|
||||
var methodName = params.as; // or pluralize(anotherClass.modelName)
|
||||
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
|
||||
// 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 () {
|
||||
var x = {};
|
||||
x[fk] = this.id;
|
||||
|
|
|
@ -27,9 +27,14 @@ MySQL.prototype.define = function (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) {
|
||||
var time = Date.now();
|
||||
var log = this.log;
|
||||
if (typeof callback !== 'function') throw new Error('callback should be a function');
|
||||
this.client.query(sql, function (err, data) {
|
||||
log(sql, time);
|
||||
callback(err, data);
|
||||
|
@ -191,3 +196,118 @@ MySQL.prototype.disconnect = function disconnect() {
|
|||
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.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) {
|
||||
this.freeze();
|
||||
if (this.adapter.automigrate) {
|
||||
this.adapter.automigrate(cb);
|
||||
} else {
|
||||
cb && cb();
|
||||
} else if (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;
|
||||
function done(fail) {
|
||||
asyncFail = asyncFail || fail;
|
||||
|
|
|
@ -68,6 +68,14 @@ function testOrm(schema) {
|
|||
// user.posts.create(data) // build and save
|
||||
// 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'});
|
||||
// creates instance methods:
|
||||
// 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) {
|
||||
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