Every repository with this icon (
Every repository with this icon (
Example Usage
The goal of Autobase is ease of use. More than anything else, it tries to make database migration scripts flow naturally with development. Hopefully this example shows that in progress.
Example Set-Up
Create an Example App and Do Autobase Installation
For our example, we’ll create a project called “Example”.
grails create-app Example; cd Example
In order to actually see something interesting going on, edit the file ./grails-app/conf/DataSource.groovy to change environments.development.dataSource.dbCreate from create-drop to update. You’ll also want to change environments.development.dataSource.url from the in-memory HSQL database to jdbc:hsqldb:file:devDB;shutdown=true.
Now, let’s install Autobase.
grails install-plugin autobase
This created a directory called ./migrations, and a file ./migrations/changelog.groovy. You shouldn’t have to edit that file, but it’s there if you want to.
Creating a Domain Class
Although you do not need to use a domain class for autobase migrations, the real power of Autobase is leveraging GORM and still having a manual migration capability. So let’s play with a domain class, which we’ll call Foo.
grails create-domain-class Foo
Let’s edit Foo and give it an int bar property.
The result is the following in ./grails-app/domain/Foo.groovy:
class Foo {
int bar
}
We will also want a controller to see what we are doing, so execute grails create-controller Foo, and then edit ./grails-app/controllers/FooController.groovy to read like this:
class FooController {
def scaffold = true
}
Exercising the Domain Class
Let’s give Foo a few values. Fire off grails run-app and then go to http://localhost:8080/Example/foo/create. Create a few Foo instances with varying bar values for good measure.
Autobase Usage
Breaking Our DDL
Now that we have a Foo with a bar, let’s change bar to baz in order to conform to an imaginary set of company coding standards (or whatever). So let’s change ./grails-app/domain/Foo.groovy to be:
class Foo {
int baz // was 'bar'
}
Now, if we go back to http://localhost:8080/Example/foo/list, you’ll hit a huge explosion: since we renamed the property from ‘bar’ to ‘baz’, we have elements in our database which don’t have values for elements that they should. What’s happened is that the new column in the database allows null values (because otherwise it couldn’t be added): it’s been left as null, though, and that’s just no good.
Fixing it via Autobase
So, let’s fix the explosion caused by renaming the column. When we fix it, we’ll commit both the rename in Foo.groovy and the migration itself. Thanks to atomic commits in our version control, we know that the next person who pulls down the Foo.groovy file will also pull down the migration to fix things up, and so they won’t run into the explosion that we saw.
Before we can get there, though, we need to start by creating our migration:
grails create-migration MoveBarDataToBaz
This creates a migration file in a subdirectory of ./migrations; the subdirectory that the file lands in is based on your user name, so that migration namespaces don’t conflict as much—this is in line with the id/authorship key that Liquibase uses (more on that here). Let’s open it up that file to edit. (If you would like to automatically have your editor of choice open up the file, vote for the JIRA issue.)
Your file will look something along the lines of this:
changeSet(id:'MoveBarDataToBaz', author:'Robert') {
// Place change set code here
}
Now, popping up the list of database refactorings, we see that we have a lot of options. There already exists a rename column refactoring, though, so we can use that. We’ll need to get Hibernate’s ‘baz’ out of the way first, though, so we’ll use the drop column refactoring to do that.
The Drop Column sample looks like this:
<dropColumn tableName="person" columnName="ssn"/>
The DSL we’re writing isn’t terribly clever—it’s pretty much just a Groovification of the XML:
changeSet(id:'MoveBarDataToBaz', author:'Robert') {
dropColumn(tableName:'foo', columnName:'baz')
}
The Add Column is slightly more complex because it requires another level of nesting and allows you to add more than one column at a time, however, it just follows the xml representation from the liquibase doc.
<addColumn tableName="person">
<column name="foo" type="varchar(255)"/>
<column name="baz type="varchar(255)"/>
</addColumn>
changeSet(id:'MoveBarDataToBaz', author:'Robert') {
addColumn(tableName:"person") {
column(name:"foo", type:"varchar(255)")
column(name:"baz", type:"varchar(255)")
}
}
Similarly, the Rename Column sample looks like this:
<renameColumn tableName="person" oldColumnName="fname" newColumnName="firstName"/>
So we implement that in our DSL like this:
changeSet(id:'MoveBarDataToBaz', author:'Robert') {
dropColumn(tableName:'foo', columnName:'baz')
renameColumn(tableName:"foo", oldColumnName:"bar", newColumnName:"baz")
}
Now, since this is a DSL, we can always do a bit of refactoring to remove the duplication, and say this:
changeSet(id:'MoveBarDataToBaz', author:'Robert') {
def tableName = 'foo'
def oldColumnName = 'bar'
def newColumnName = 'baz'
dropColumn(tableName:tableName, columnName:newColumnName)
renameColumn(tableName:tableName, oldColumnName:oldColumnName, newColumnName:newColumnName)
}
In this particular case, that doesn’t help out a whole lot—it’s mainly an instructional example. But you can see how we could start building up templates for common refactorings (and this case is already on the docket). Of course, since we’re just writing Groovy code here, we could be pulling values for those variables from anywhere you’d like: you could use this functionality to inject user ids or current timestamps onto columns, for instance.
Now that you’ve got the migration created, save it, close it, and fire your app back up. The migration will automatically execute, and your data will be all fixed up!
Hierarchical Changelog Files For Grouping Changes
If you would like to break up changes into logical groupings, like:
/migrations/v1
/migrations/v2
then you have the option of using the default autobase /migrations/changelog.groovy file, which uses <includeAll> to traverse the migrations folder and pickup changelog files. Although, you may want to guarantee the execution order of changelogs OR perhaps you’ve run into issues with the <includeAll> tag not handling Windows file paths very well. Either way, there is an alternative to using <includeAll> and that is to use the <include> liquibase tag. The <include> liquibase tag essentally allows you to be specific about what files should be loaded and can support nested changelog file includes. Below are examples that you can use to create your own hierarchy of changelogs.
Example 1
Folder structure:
/migrations/
changelog.groovy
/migrations/v1/
changelog.groovy
users.groovy
accounts.groovy
/migrations/v2
changelog.groovy
moreUsers.groovy
moreAccounts.groovy
/migrations/
changelog.groovy
/migrations/v1/
changelog.groovy
users.groovy
accounts.groovy
/migrations/v2
changelog.groovy
moreUsers.groovy
moreAccounts.groovy
Contents of /migrations/changelog.groovy:
databaseChangeLog(logicalFilePath:'MyApp-autobase') {
include('./migrations/v1/changelog.groovy')
include('./migrations/v2/changelog.groovy')
}
databaseChangeLog(logicalFilePath:'MyApp-autobase') {
include('./migrations/v1/changelog.groovy')
include('./migrations/v2/changelog.groovy')
}
Contents of /migrations/v1/changelog.groovy:
include('./migrations/v1/users.groovy')
include('./migrations/v1/accounts.groovy')
include('./migrations/v1/users.groovy')
include('./migrations/v1/accounts.groovy')
Contents of /migrations/v1/users.groovy:
changeSet(id:'v1-users', author:'BillyBob') {
insert(tableName:"user") {
column(name:"name", value:"Billy Bob")
}
}
changeSet(id:'v1-users', author:'BillyBob') {
insert(tableName:"user") {
column(name:"name", value:"Billy Bob")
}
}






