Active Record migration dependencies 2

Posted by Paul Pagel Sat, 14 Jun 2008 02:38:00 GMT

We had a new developer join our project recently, and he needed his computer to be setup with the project. “Here is the svn repository, when you check it out, run these rake tasks.” It unfortunately, is never that easy. This project setup revealed something about Active Record and migrations that I didn’t know about.

When I create a migration, I will often do data manipulation on the database, or pre-populate some fields with data needed for a lookup table. Lets look at a sample migration from a trivia game.

class CreateQuestions < ActiveRecord::Migration

  def self.up

    create_table :questions do |t|
      t.column :text, :string
      t.column :answer, :string
      t.timestamps
    end

    Question.populate
  end

  def self.down
    drop_table :questions
  end
end

I want to add some sample questions, so that even if you don’t have your own questions, you will still be able to play the game. I added the method to the populate model, because I use it elsewhere in the code, and I try to keep it DRY. The populate method on the question model looks like this:


class Question < ActiveRecord::Base
  belongs_to :game
  has_many :answers

  def self.populate
    Questions.create(:name => "What is your favorite color?", :answer => "I don't know")
    Questions.create(:name => "Who was the first President", :answer => "George Washington")
    Questions.create(:name => "Who was born Samuel Clemens?", :answer => "Mark Twain")
  end

end

So, later on, I decided to add a degree of difficulty to the questions, so the players can get more points for answering harder questions. Here is what the migration looked like.

class CreateQuestions < ActiveRecord::Migration

  def self.up
    add_column :questions, :rank, :integer
    Question.destroy_all #In case there are any old ones
    Question.populate
  end

  def self.down
    remove_column :questions, :rank
  end
end

Of which I had to change the populate method on the question class to:

class Question < ActiveRecord::Base
  belongs_to :game
  has_many :answers

  def self.populate
    Questions.create(:name => "What is your favorite color?", :answer => "I don't know", :rank => 1)
    Questions.create(:name => "Who was the first President", :answer => "George Washington", :rank => 3)
    Questions.create(:name => "Who was born Samuel Clemens?", :answer => "Mark Twain", :rank => 8)
  end

end

Then I ran my migrations, and continued development. Then when developer number 2 came across and checked out the project and ran the migrations, he got the error.

undefined method rank= for class Question (…or something very similar)

The problem is the old migration is dependent on the new model. All models in rails are just a mirror of the database, so the new model has a forward definition of the data. The code in the model knows about the rank field, but the schema of the database hasn’t caught up to create that portion of the mirror yet. This creates a little bit of a catch 22. The rails wiki (http://wiki.rubyonrails.org/rails/pages/UsingMigrations) about migrations tells you to redefine the class to stop name conflicts. This would require me to make my migrations model agnostic, inserting straight to the database. As a spoiled brat when it comes to databases and rails, I refuse to let go of my Active Record sugary syntax. Another solution I thought of is to just make the last change to question do the populate, and remove it from the previous versions. This will become a maintenance nightmare.

I came to the realization that I want to make a distinction between form and content when it comes to migrations. Form in this case is schema form, the changes to the database which reflect the data which the Active Records can potential hold. Content is the specific data which is in the database. This distinction allows for me to use the power of my model classes in my data migrations, which is the place it is useful. It maintains backwards compatibility, because before I go touching the data, I have to make sure my schema is right.

What does this look like in Rails? I am not sure yet. Possibly db/migrations/schema and db/migrations/data. Possible saving the data migrations in each migration as a block and executing those at the end, only when you have the schema is correct. I am going to try it out!

Code Less: A Language Keystroke Expirement 3

Posted by Paul Pagel Sun, 08 Jun 2008 18:06:00 GMT

When I first started writing code in ruby, it was a breath of fresh air after writing C# code for a year. Ruby had a thesis, a clear purpose, rather than a hodgepodge of features strung together. It was a language obviously written by someone who cared about what the code looked like. So I jumped into it and loved writing ruby code (still do!). Recently I had to write a project in Java using IntelliJ, and the powerful IDE was also a breath of fresh air after using text editors to write ruby code. The IDE helps me in the same way ruby helped me. I write less code that does the same thing, without loosing expressiveness or transparency of intent. Less code in this case meaning less keystrokes.

So, I am going to do a little experiment using two editors and languages: ruby with textmate and java with IntelliJ. I want to see how many lines of code I have to write. The application I am going to start is a simple baseball at bat scorer. First lets see the ruby version.



We have about 27 lines of code written, and other than the describe method, which I have a macro for, I typed them all by hand. Lets look at the java code. This is just the JUnit test I wrote, there is no production code yet.



It is about 16 lines of code, which I wrote all of but the import statement. By doing a few alt-enters on the squiggly lines, I can get a stub of a class looking like this.



That is 13 more lines of code, without really typing, just hitting enter a few times. Now, lets start to make the method pass. I am going to write the algorithm without worrying about all the type definition java wants. Here is what it looks like.



So the code written is around 4 lines of code. Lets use the IntelliJ autocomplete features to help us make a passing test, which looks like.



So roughly, the ruby version was 23 lines of written code and the java version was about 20 lines of written code. There is an expense to doing all of the alt-enters to auto-generate the method and variable stubs, but IntelliJ is pretty smart about what it generates. Also, I understand a single test doesn’t tell the whole story, but it is a good indication. I found as I wrote more tests for this application, a smaller amount of keystrokes was needed for the java version, and the same amount was needed for ruby. This is due to the refactoring tools, and intellisense. As applications get larger, I find the IntelliJ refactorings become more useful. Inversely, ruby refactorings become more painful, as they are mostly done by hand.

So, in the end the constraints on a static language allows the IDE to make the refactoring tools better. Specifically, when writing java code, I can lean on the IDE to generate all the uninteresting stubs for me. All I do is fill in the algorithms: the fun part. When I start to see places where my code can be cleaned up a bit, I can lean on the IDE again to do those refactorings for me once I recognize the need for them. Removing and optimizing the code is something which is easily deducible in static languages, as it is mostly pattern matching, with no monkey patches, meta-programming, and evals to worry about. This is a limitation which frustrates me as a developer who wants to have a large set of tools in my bag, but is helpful when it comes to developing a powerful IDE.

The number of lines of code I need to type isn’t the reason I choose a language over another. In fact, it would be pretty low on the list of deciding factors. However, it is interesting to see what each language and its sets of tools do best. Hopefully the ruby community can take some notes from them. I would rather solve the other end of the equation, get a powerful ruby IDE. I know Eclipse and Net Beans have some preliminary refactoring tools, but they are still aways from being as seamless as their java counterparts.

Announcing Limelight 1

Posted by Micah Mon, 02 Jun 2008 20:42:00 GMT

I’m pleased to announce the open source Limelight project: A thin client and application framework written in Ruby (JRuby).

Older posts: 1 2 3 4 ... 16