First off, thanks to Khou Suylong of Mango Team for once again providing me with a bug find which led to a fix!
We’ve been busy once more, putting together JazzRecord 0.7, which incorporates manual migrations with all of the major features from Rails’ ActiveRecord migrations. While there are no official updated docs yet, here’s a brief list of changes:
ID must now explicitly be listed as a column for a model. If using automigrations, you must list it in the model definition. The explicit requirement for ID column may change going forward, but once we hit Version 1 (another month, perhaps) we will stand firm on the API. If using manual migrations, you may now leave out columns from the model definition entirely and rely on migrations setting the model’s column knowledge for you.
What this means is, no matter what changes you make to a db schema using migrations, the model is always aware of the current state of the schema and will allow you to create, destroy, and modify properties of records with no problem. The only thing to watch out for is validations if you have any, because they can cause problems if there are requirements which cannot be satisfied given a specific updated schema.
The following schema operations are available as of now:
JazzRecord.createTable(tableName, cols) – this creates a table using a columns object, identical the the (now optional) columns object from a model declaration.
JazzRecord.dropTable(tableName)– removes a table from the DB.JazzRecord.renameTable(tableName)– renames a tableJazzRecord.addColumn(tableName, colName, colType)– adds a column of given type to a tableJazzRecord.removeColumn(tableName, colName)– removes a column from a tableJazzRecord.renameColumn(tableName, oldName, newName)– renames a given column in a tableJazzRecord.changeColumn(tableName, colName, colType)– changes the type of a column. This can result in data loss, as determined by how the DB converts data types
The last three functions were the trickiest, because SQLite does not provide inherent capabilities for modifying columns once they exist in a table. In order to reproduce thesefeatures, then, we have to go about slurping up all data in a table, dropping the existing table, creating a new table with the new schema, and spitting the same data back into the database, with data types changed or columns removed or changed in type. To keep this (hopefully) relatively safe, we wrap the destructive DROP operation in a transaction.
Did anyone even know JazzRecord had a JazzRecord.runTransaction(func, bind) method? I’m not sure! Calling this and passing in a function full of operations will ensure that if something goes wrong and you or some function you call throws an exception, all will revert to its pre-transaction state. Pretty cool, huh?
Making use of the schema operations is as simple as calling them anywhere, though typically you’d organize them into migrations. Migrations are now a bit streamlined, as well.
To automigrate, simply call JazzRecord.migrate() with no options, or to refresh the DB to an empty state, pass in an object with refresh property set to true:JazzRecord.migrate({refresh: true}). To optionally load fixture data, assign a fixtures object (same as the old object format) to JazzRecord.fixtures, and if it exists it will be loaded. To run migrations, simply assign to JazzRecord.migrations an object literal with numerically-named properties (starting with the number 1) each containing an object literal which has an up function and a down function. These functions should be the reverse of one another, and for an explanation of proper migration usage consult any Rails tutorial or wait for the documentation. Here’s a simple sample migration which creates a couple tables and adds a column to one of them over a few schema versions:
JazzRecord.migrations = {
1: {
up: function() {
JazzRecord.createTable("military_dudes", {
id: "number",
name: "text",
title: "text",
base_id: "number"
});
JazzRecord.createTable("bases", {
id: "number",
location_name: "text"
});
},
down: function() {
JazzRecord.dropTable("bases");
JazzRecord.dropTable("military_dudes");
}
},
2: {
up: function() {
JazzRecord.renameColumn("military_dudes", "title", "rank");
JazzRecord.addColumn("bases", "state", "text");
},
down: function() {
JazzRecord.removeColumn("bases", "state");
JazzRecord.renameColumn("military_dudes", "rank", "title");
}
}
};
After setting up a migrations object like this, you can call JazzRecord.migrate() standalone (to migrate to the latest version), or you can pass in a version number to reach the specified migration number. To reset to pre-migration status, call JazzRecord.migrate(0). In this way, you can evolve an application’s schema over time and can almost effortlessly jump between schema versions to diagnose problems or revert when you’ve introduced more problems than you’ve fixed! You can pretty much use migrations exactly as in Rails: changing schema and inserting/removing/updating data for existing, already-deployed applications. Soon we will add additional tools for updating multiple columns and multiple rows simultaneously, which will further add to the usefulness of migrations.
The smaller, but still useful, function introduced in JazzRecord 0.7 is Record’s isNew() function. This function, when called, will return true if a record has not been saved yet, or false if it has.
As a previous blog post stated, we are extremely interested in having our documentation translated into other languages, so please contact me at thynctank@thynctank.com if you can help with that.
Enjoy!
