UPDATE: Don't read this.
It's long-winded, boring, and useless. For history's sake, I leave it up, but suffice it to say: I'm glad somebody besides me cares about database versioning, and Rails is a compromise.
Dear Devin:
People have been developing webapps for 10 years, MVC-style apps since the 80s, and apps with user interfaces since the dawn of the terminal. Why the fuck does Rails think it's so special? Somebody just came out of the air with something so hot and new that had never been thought of before, not even by the industry giants or the academic greats? I don't buy it.
Sincerely,
Jammin' wit' Java
Dear Jammin',
Well, here's my bullshit opinion for what made Rails become such a "revolution" for so many people. There were three principle players:
- The increase of interest in Agile meant doing certain types of things like "smart defaults" (i.e. no explicit documentation of the wiring) don't seem anathema to most developers any more.
- Ruby means many more "smart defaults" and other time- and finger-savers are possible. (Yes, the same is accomplishable with LISP, but, hey... come on... LISP.)
- Before Rails, most frameworks of this sort fell into two categories:
- Do the right thing. (MVC frameworks)
- Do the simplest thing. (PHP)
Rails cleverly decides to merge these two philosophies into one devilishly pragmatic philosophy:
- Do the simplest thing that's right.
(Wait, was that sarcasm? I can't tell.)
I'll give you an example of #3 there, and it's with the Rails Migration API that I used for the first time today. (The canonical example is ActiveRecord, the stupid easy Model API, followed by the insanely helpful integration of the View and Controller. I am, however, 1)trying to be different, 2)trying to educate, and 3)trying to point out how well-rounded Rails is. If you haven't seen the 15-minute video on rubyonrails.com, though, maybe you oughta start there and come back here if you want more.)
But first, some background. I started on my first "real world project" about two years ago. In the context of this background snippet, this means that two years ago was my first experience with a database, and more specifically, my first experience dealing with the maintenance and development of an application whose behavior was strongly correlated to a specific database schema (phew!). Once of the problems that this project had, in a big way, was version control and integration, and I made it my goal to fix it, first with the code, and then with the database schema & data.
Tackling the code took a lot of time setting up CVS and educating people on how to use it, a lot of Ant coding, a lot of testing of the Ant build, and, mostly, a lot of patience making sure to transition only one thing at a time. It was painful at times, but ultimately successful. (Well, until I left, I'm told, but that's another story.) I managed to create a build script that successfully built from a CVS tag in one click, and managed to institute a weekly build to an integration server where people would test their changes (which I would pull from `cvs hi` or `cvs pa` and send out in a friendly little email). This is government we're talking about, so that's fuckin' A to the G-I-L-E.
Tackling the database was going to be my next task, and I never got to it, unfortunately. When I first started reading up on database versioning, well, I was appalled. I was shocked and dismayed that the industry DIDN'T GIVE A FUCK about database versioning. The accepted solution, in almost all cases, was to make the database the bottleneck. Surround it with tons of paperwork, make it take 7 weeks to actuate a change, and basically make it so people didn't want to change the database very often, so you didn't have to deal with versioning it very often.
I started finding little papers here and there in the pockets of Agile communities that were rapidly forming, where people were beginning to realize how stupid an idea this was, and were looking for solutions. "Well," they said, "it's so gosh darned easy with code. You just make a central repository, throw diff and patch at it, et voilà, CVS." But they cautioned, "It's not so easy with databases, though. You can't just diff and patch willy-nilly. You've got test and configuration data to deal with. You've got integrity constraints which restrict the ordering of 'patches,' and you've got hundreds of database dialects that make writing a general-purpose database differ next to impossible." I cried. I so wanted version control on our database, but I didn't want to have to write Oracle Diff 9i from scratch, especially since the DBAs weren't giving me any help.
The plan I had settled on, and that would've been implemented had I stayed about two months longer, was a cheap little thing that involved putting all the .sql artifacts in CVS, and never fucking with int, test, or prod except through doing "a build." Ah, change management... I would then sigh. The build was to be as follows: a shell script would run a `cvs pa` on two tags, sed the output to display only the added lines, and to error out if there were any removed lines (if you want to remove a column, you have to stick an ALTER TABLE yada yada.. in CVS), and pipe the result to a upgrade_tag1_to_tag2.sql file. The result would then be thrown at the database.
Except... wait! What about all that environment-specific configuration data, and those integrity constraints? How was the complexity of those being handled? Well, it wasn't. For that, my solution was, "Who do I look like, a DBA? Go hand-edit the upgrade.sql file already." But I wasn't really happy with that solution. It was simply the best I could come up with with the tools I had at hand.
Two months later, enter Rails Migration. I've been learning Ruby since mid-June, but until recently, Rails had been on the sideline for me. Whatever. Just another framework. Recently, though, I've been starting to go to a little weekly codefest started with a couple of people, with the intent to learn Rails. We're trying to establish a means (through sourceforge, sadly) to do distributed development the other 166 hours of the week, and the first hurdle we came across is the database. Surprise. How do we make sure that the 4 of us are all dealing with the same database schema locally? We could just serve up a database on the public internet, but that would royally suck (if only because we want to be Agile and have "private workspaces"), and nobody has the gear to handle that, apparently.
So I mention, "Hey, I've read a little about this Migration thing. It was included in Rails circa 0.12 or 0.13, so it's pretty new, but I think it might be what your after." Within 10 minutes of messing around, we've figured it out and committed two Migrations to CVS. By now, I've linked to Migrations five times, so you should've read up on it by now. For those too lazy to have clicked the link, it's a way to specify schema changes in Ruby. You can do:
class MyFirstMigration < ActiveRecord::Migration
def self.up
add_column :table_name, :column_name, :data_type
#you can also just shove in "straight" SQL via the execute method, if you like
end
end
And then run `rake migrate` at the commandline. The Rake task will notice the new migration, notice that it hasn't been applied yet on the database, and apply it. You may also provide a down method to support rollback.
So, yeah, big whoop. It sounds like what I was proposing, but with some Ruby thrown in for no good reason. Eh?
- My way required you not only to be rigorous about committing all database changes into CVS, but about tagging it obsessively, in order to pull off the `cvs pa`. This way, you still have to keep on top of CVS, but you can get away with just typing rake migrate -- Migration will figure out how much or how little to update.
- This way supports rollback.
What about those environment-specific configuration variables I talked about, like, seven pages ago? Well, here's the thing. By making the pragmatic leap to do this in Ruby ("Why am I doing SQL in Ruby? You're a total-fucking-nutcase," I hear all my readers shouting in unison, mostly because I know my only readers are a school of magical fish-elves that live in my head, and I really do hear them shouting in unison right now), you are afforded the ability to do whatever logic you so desire in your migration script. For example:
class AMuchCoolerMigration < ActiveRecord::Migration
def self.up
title = case RAILS_ENV
when 'development' then "Suck My Kiss"
when 'test' then "Jake's Automatic Underground Fishhead Emporium"
when 'production' then "A Title The Reading Of Which Won't Trigger My Customer To Fire Me"
end
#the following line creates a database row, you nincompoop
SystemSettings.create :param_name => 'application_title', :param_value => title
end
def self.down
row = SystemSettings.find_by_param_name('application_title')
#the following lines deletes it, foo
row.destroy if row
end
end
Just as important, though, is what it isn't: An overly complex solution that tries to do everything. It doesn't figure out how to properly order SQL calls so that integrity constraints don't barf. It provides a mechanism to make manual ordering of that stuff ass-simple, and leaves the actual thought process to you. Sure, the automatic ordering would be nice, but this is a much, much, much simpler way that gets me 90% of the way there. Seriously, you could have coded Rails Migrations in, like 2 hours, or maybe a day if you're, like, scared of code or something. You're not scared of code are you? No, you just didn't, you fucking moron. Seriously, what the fuck is wrong with you?
So, no, Rails is not a magical hoop-ah solution that cleans your chimney and wipes your nose. It is, however, a good balance of pragmatism and idealism.
(For that matter, so is Ruby, but that's a-whole-nother cake.)
Wow. I kind of veered there. Sorry. To answer your question, JwJ, no, I don't believe Rails did invent something new. I think the industry giants probably thought about it a couple times, but couldn't figure out how to make a profit off of it. I imagine it occurred to the academic greats on more than one occasion, but held their interest as tightly as any of their undergraduate students held onto his sobriety. I think the rest of us are either too god-damned lazy, or just fucking morons.
XOXO,
Devin
BTW, in TextMate, if you select some text and hit Ctrl-Shift-W, it'll wrap the selection in <p>...</p> for you. It then selects the letter 'p' in the start tag -- if you type over it, it automatically updates the end tag. Sweetness!