Friday, September 30, 2005

Rails Migrations for Dummies, and Jeff

On a mailing list I belong to (<nerd/>), Jeff wrote:

How hard was it to get going with Migrations?

Very easy. Instead of creating your table or column or whatever through traditional means, do this:

  1. Run script/generate migration products_table. The script tells you it creates a file called db/migrate/1_products_table.rb.
  2. Edit the file. Initially it looks like this:
    class ProductsTable < ActiveRecord::Migration
      def self.up
      end
    
      def self.down
      end
    end
    Change it so it looks like this:
    class ProductsTable < ActiveRecord::Migration
      def self.up
        create_table :products do |t|
          #id column is add automatically when you use create_table
          t.column :name, :string
          t.column :price, :integer  #anybody who chooses :float deserves what they get
          t.column :date_added, :date
        end
      end
    
      def self.down
        drop_table :products
      end
    end
    Full docs at http://ar.rubyonrails.com/classes/ActiveRecord/Migration.html.
  3. Run rake migrate. This opens up your DB and looks for a table called schema_info. If not found, it creates the table. If found, it pulls the single row from the table containing the database "version number." It then runs all the available migrations (in the db/migrate directory) with a number greater than the DB version number, in numerical order, and finally sets the new DB ver # in the schema_info table.

That's all!

  1. Obviously, there's a way to do rollback, else we wouldn't have wasted the time to write the down method. From the looks of the Rakefile, it's VERSION=0 rake migrate.
  2. I'm not sure what happens if Alice generates 5_quantity_remaining.rb and commits, and Bob generates 5_system_settings.rb and commits without noticing and renaming his to 6_.... The simple answer is "read your svn up log" anyway.

I Saw an Awesome Movie Preview

I saw a movie preview this morning. It was awesome. Seriously, it was like Good Will Hunting on Soderbergh. Friendship, murder, emotionally disturbed geniuses, nontraditional editing, a silver Corvette... Then the alarm went off.

Apparently, my subconscious is a freaking awesome writer.

Wednesday, September 28, 2005

Over There

Seriously, though. Over There. Can't recommend it enough.

Dear Devin: Why Is Rails So God-Damned Hot?

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:

  1. 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.
  2. 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.)
  3. 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?

  1. 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.
  2. This way supports rollback.
  3. 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!

Tuesday, September 20, 2005

My Two Favorite Current TV Shows

Barbershop is funny. It is on Showtime.

Over There is not funny. It is on FX.

If you don't have FX or Showtime, then I guess you're SOL.

Dear Daniel Berger,

I had promised you I would take a look at your LISP-with-commas+ pre-processor one day. I still haven't. I apologize. I really do. I should just try it out. Right now. It's not like it won't interoperate with Rails or something. I'm not sure why I'm not doing that. Probably just gets shoved to the back of my brain behind too many other things. Sorry.

There. Does that get me out of the requirement, now? :) (Just kidding...)

Hugs and kisses,
Devin

Sunday, September 18, 2005

Mainstream languages: you may now bow to Ruby.

require 'rubygems'
require 'binding_of_caller'
module Kernel
  def debug
    Binding.of_caller do |b|
      var_types = ['local','instance','self.class.class'] #,'global']
      var_types.each do |var_type|
        puts "#{var_type.split('.')[-1].capitalize} variables:"
        eval(var_type+"_variables",b).each do |var|
          puts "#{var} = #{(eval var,b).inspect}" unless var =~ /gempath_searcher/
        end
      end
    end
  end
end
Perfect for script/breakpointer in a Rails app.

A snippet of the running monologue in my head (polished up a bit)

I figured this made more sense here than sitting around in some unsaved window in TextMate.

... I mean, look no further than the English language. It has the reputation for being one of the hardest languages to learn. Not just as a second language, but as a first language. How many people do you know who speak with proper grammar most of the time? How many high school students? How many people actually understand the rules of grammar that they're applying subconsciously? The English language has a Usability rating of "total piece of shit."

On the other hand, the same language has a reputation for being one of the most expressive. Shakespeare, Joyce, _why... The language has an impressive ability to let us cull from the innumerable expanses of thought, and to enable us to distinguish between the slightest differences in inflection. It is an advanced language, horribly difficult to learn, but incredibly powerful for those that master it.

The HCI school of thought that says that says "documentation is a smell, preferences are evil, etc." is bullshit. There's a reason vi and emacs exist today, and it's not nostalgia. These editors fall under the category of "advanced," allowing power users to edit like a mother-fuck. There is room for Notepad, vi, and everything in between in the zoology of text editors...

I hate you, Jerry.

I thought this deserved its own blog, though.