« November 2005 | Main | January 2006 »

December 28, 2005

How To Write eql?() in Ruby

Listen to this articleListen to this article

So, as I've been delving into Ruby more and more of late and I needed to write an eql?() method for one of my classes. (eql?() is the Ruby equivalent of equals() in Java.)

Understanding how to write an equals() method in Java is one of the most fundamental yet poorly understood practices (if you're not worried about equals() or hashCode() then I suggest you never use a HashMap) and I wondered if the same was true of Ruby.

The most common mistake looks like this:

public boolean equals(Object object) {
    if (this == object) {
        return true;
    } else if (!(object instanceof MyClass)) {
        return false;
    }

    MyClass other = (MyClass) object;

    return this.x == other.x && this.y == other.y;
}

Spot the mistake? It's subtle yet very VERY wrong. Thankfully—now that we've largely managed to get over our obsession with deep inheritence hierarchies—it's effects aren't felt that often. The problem is that equals() needs to be symmetric: a.equals(b) should return the same result as b.equals(a).

Now, imagine a class Person with attributes of name and address and another class Employee extends Person with an extra attribute of company. Given the previous implementation of equals, what do you think the chances that the following two statements will return the same result?

person.equals(employee);
employee.equals(person);

So anyway, unlikely or not, it's still no excuse not to be precise so, in Java the canonical form of an equals() method should look roughly—give or take some formatting and style—like:

public boolean equals(Object object) {
    if (this == object) {
        return true;
    } else if (object == null || getClass() != object.getClass()) {
        return false;
    }

    MyClass other = (MyClass) object;

    return this.x == other.x && this.y == other.y;
}

Now we're comparing based on the actual class and as such the implementation is symmetric; no more problems.

Assuming I still know next to nothing about Ruby, I decided I wouldn't let my Java habits get in the way of learning something new so I searched the 'net for some examples of how to implement eql>().

After failing dismally to find anything non-trivial, I realised that Rails probably had something in ActiveRecord::Base and I was rewarded with:

def eql?(object)
  self == (object)
end

def ==(object)
  object.equal?(self) ||
    (object.instance_of?(self.class) &&
      object.id == id &&
      !object.new_record?)
end

Nothing too outrageous: eql?() simply delegates to == (nice syntactic sugar); and == does pretty much the same thing as we would have done in Java only a little more terse. (object.instance_of?(self.class) is Ruby's way of saying getClass() == object.getClass(); the Ruby equivalent of Java's instanceof is kind_of?().) We could easily re-write this—although I wouldn't in practice—as:

def ==(object)
  if object.equal?(self)
    return true
  elsif !object.instance_of?(self.class)
    return false
  end
  
  return object.id == id && !object.new_record?
end

So it seems—I trust the Rails guys to get this stuff right— that the same rules apply in Ruby as in Java. (I had no real reason to suspect otherwise but hey, it s nice to have it confirmed.)

Now if only someone could explain to me why Hash#reject() and Hash#select() aren't symmetric: one returns a Hash; the other an Array.

December 21, 2005

Here Come The Minions

Listen to this articleListen to this article

Well, just as Jon (and others) predicted: M$ developers are apparently beginning the move to Rails. That's just Marvy!

I had taken comfort in the fact that .Net had disuaded a large proportion of developers from taking up Java and had hoped (nay prayed) that it would continue to keep them amused for some years to come but alas, the secret is out. Clearly the appeal of a language that gives every developer the ability to create a train wreck faster than ever before is too much resist.

I wonder how long it will take for M$ to get a patent on Ruby (and/or Rails for that matter). It seems like the only sensible thing to do ;-)

December 18, 2005

Patching Rails

Listen to this articleListen to this article

Last night I was writing some database migration scripts and I wanted to add a not-null column to a table. The migration stuff is so cool that I was able to write something like this:

class AddExternalIdToListings < ActiveRecord::Migration
  def self.up
    add_column :listings, :external_id, :string, :limit => 10, :null => true
    add_index :listings, [:external_id], :unique => true
    
    Listing.reset_column_information
    
    Listing.find(:all).each do |l|
      l.external_id = l.id
      l.save!
    end
    
    change_column :listings, :external_id, :string, :limit => 10, :null => false
  end
  
  def self.down
    remove_column :listings, :external_id
  end
end

This script first adds the new column allowing nulls (:null => true) then creates a unique index on it, updates each record to give the new column a value and finally modifies the column to make it not null (:null => false).

All well and good except for one thing: it didn't seem to modify the column as expected. Looking at the source code for the PostgreSQL adapter, I could see that the code change_column() has (at least) two bugs. (Ok, strictly speaking one bug and one omission.) The first bug means that it never actually executes the intended statement. The second (the omission) means that even if the statement were executed, most of the specified options (in this case the :null => false) would have been completely ignored.

Enter the wonder of Ruby and extended/modifying classes on the fly.

Disclaimer: I'm no Ruby nor Rails expert. I'm sure there's a "better" way to do this so if anyone wants to take this code and submit it as a proper patch, then please, please, please go for it. For my needs, this does the job nicely.

Rails allows you to add functionality via Plugins. In this case, I decided to make a "patches" plugin that will allow me to easily override or extend rails to get around any bugs (or omissions) that I encounter in my travels.

So I ended up with two files. The first vendor/plugins/patches/init.rb is executed by rails. In my case it contains only one line:

require 'postgresql_adapter_patches'

This simply loads the second file (vendor/plugins/patches/lib/postgresql_adapter_patches.rb) containing the actual code that performs all the black-magic (although it's actually fairly straight-forward):

module ActiveRecord
  module ConnectionAdapters
    class PostgreSQLAdapter
      def change_column(table_name, column_name, type, options = {})
        native_type = native_database_types[type]
        sql_commands = ["ALTER TABLE #{table_name} ALTER #{column_name} TYPE #{type_to_sql(type, options[:limit])}"]
        if options[:default]
          sql_commands << "ALTER TABLE #{table_name} ALTER #{column_name} SET DEFAULT '#{options[:default]}'"
        end
        if options[:null] == false
          sql_commands << "ALTER TABLE #{table_name} ALTER #{column_name} SET NOT NULL"
        end
        sql_commands.each { |cmd| execute(cmd) }
      end
    end
  end
end

In essence, all it really does is override the definition of the change_column() method in the ActiveRecord::ConnectionAdapters::PostgreSQLAdapter class. In my case, I just copied the code from add_column() and modified it to suit my needs. Of course this is a blatant violation of the DRY Principle but hey, shoot me :-).

All in all I'm impressed by how trivial the whole processs turned out to be and has made me think about writing some actual plugins; I'll leave it to the experts to fix it for good ;-).

December 17, 2005

The Ruby Way(tm)

Listen to this articleListen to this article

I can't help but be amused at the number of times I see the term "The Ruby Way" (or similar) used as a euphemism for "Better than {Java,C#,Python,Perl,etc.}" It seems to be one of those terms (like "Un-Australian" and "Un-American") that can be used to support or attack anything based largely on personal preference.

Case in point: The discussion and ensuing ruckus over Humane Interfaces. What seemed (to me at least) to be an interesting talk on the subject rapidly (as seems to happen all too frequently) into a "mine's better than your's" sledging match.

Unfortunately, Fowler did begin his discussion with "Hanging around the ruby crowd..." and followed that up with "...compare the list components in Java and Ruby" which amounts to pouring gasoline on a bbq. However, what almost everyone fails to see (or possibly I fail to see that they see) is that the concept has almost nothing to do with the underlying language and far more to do with the people (surprise surprise).

Java is full of Swiss-Army classes as is Ruby. Ruby seems to have some very intelligent and articulate pracitioners as does Java. In many ways, Ruby (like Perl) seems to almost encourage bad habits in those less versed in The Ways™. Java OTOH attempts to prevent these same problems and yet creates a whole slew of others.

Disclosure: I like Ruby, alot! but I don't hate Java as a consequence. In fact I find that besides some really nice language features that I really like, I don't find that I think that much differently in either. Then again, I've never liked Struts, I've put up with Hibernate and left the J2EE stack along time ago. Alas, ActiveRecord is not much better (yet). It lacks many things that I've come to appreciate in ORM tools but, in its defense, it's still relatively early days and what is there is relatively clean (from the point of view of one using the library) but take a look at the code and tell me there aint a whole lotta spaghetti in there!

Most people (probably myself included) don't get software development, don't get OO, don't get "it" in general. So let's not get sucked into thinking that somehow the language is to blame. Sure it has an influence but honestly now, horses for courses: People think differently, taste differently and smell differently. Get over it!

Maybe look at yourself first and ask the question "Do I get what they intended?" If you think the answer is yes, then ask again. If the answer is still yes, then how about taking aim at the people involved in creating the frameworks next. The underlying language is a very soft target.

First come up with a definition for "good" and then prove to me there is a direct correlation with the language.

December 04, 2005

Dojo or Prototype or ...?

Listen to this articleListen to this article

I'm about to start adding "funky" Look & Feel stuff to my Rails app and I was wondering what the general consensus (if there even is such a thing) is on JavaScript libraries.

I really don't care so much about the ease or otherwise of making XMLHttpRequest calls; this is the least of my worries—it's trivial to do manually. What I'm more interested in are things such as Drah & Drop, Accordians, Select-as-you-type, and other more general DHTML layout stuff.

Rails itself ships with prototype+scriptaculous which I'm lead to believe are quite good. My mate Andy on the other-hand has played a bit with Dojo and likes that too.

Anyone care to share their own experiences?

December 03, 2005

Don't Give Razor-Blades to Children

Listen to this articleListen to this article

I've no idea who first coined this phrase but I heard it from Steve.

This was to be solely about Rails schema migration but it turned into a partial rant (surprise surprise) about playing with Ruby and Rails. Never fear, the migration bits are still here; they're just at the end instead :)

Developing in Rails as a one- or two-man-band is very rewarding but I'd like to see how it scales to many developers and more importantly, to developers of radically different levels of experience. As you probably gathered from my previous post, my prognosis on this last point isn't particularly flattering.

I like convention over configuration so long as the conventions don't get in my way; and so far they haven't. I think the idea of "flexibility" in software is highly overrated. The whole J2EE stack is a perfect example of bloat in an attempt at being all things to all people.

Don't get me wrong, I love Java. I will always like it. It does pretty much everything I could want and do feel very productive with it—especially when compared with the pile of steaming turd that is Flash/Flex—but I have to admit that I do like The Ruby Way ™. I must have been a Smalltalk wanna-be in a previous life. It's not for all things nor for all people but it might just be for me on the kinds of projects I enjoy doing.

So far I've found Ruby/Rails to be easy to test; that it facilitates adding new behaviour VERY quickly; and that the teeny bits of SQL I do need every now and then aren't painful in the slightest. I love partials; caching; declarative relationships; validation; acts_as_xxx; components; etc.

I don't like fact that there doesn't seem to be a way for components to have relationships to domain objects—for example my address component needs a State which is an active record object. Tool support is pre-historic when compared with Java. I use TextMate on the Mac which is pretty darn good and there is RadRails of course but to be honest, right now—although the road-map looks very promising— it isn't actually much better than TextMate except for debugging.

Anyway, the upshot of that little rant is to say that whilst I'm still splashing around in the kiddies pool, the water still feels good and is tempting me to want to try swimming in deeper water.

So anyway, back to the topic I originally had in my head.

If anyone has been playing with rails then at some point they'll want to have a play with data/schema migration. I used it tonight and it worked as advertised.

In short, you create a number of files named 001_xxx, 002_xxx, etc. Rails then creates a table (schema_info) that contains the current database schema version. When you run a migrate, it (rails) will execute the scripts necessary to bring you from whatever version you are currently at (may be 0) to the latest version (by default) or a nominated version (using VERSION=#). You can even downgrade by specifying a previous version.

Besides migrations based on version numbers it essentially supports a DB independent schema description with the ability to escape to SQL if need be (like for those pesky little FK constraints that no one wants to talk about but should be locked-up for doing without). Honestly the "DB independence" thing isn't the biggest concern for me in the world but it is nice. It would be even nicer if rails extended the concept to include foreign key constraints (the model has declarative relationships anyway!).

You can also use your rich domain model to play with the new schema in the migration script too! So, for example, you can add reference data or munge the existing records to suit the new schema. Again you can even execute raw SQL if need be!

If you're interesting in how this really works, I can recommending reading this and maybe this and possibly this and the RDOC can be found here.

The only issues I've had are that transaction management within the migration is a little dubious: I can't seem to find a way to execute a batch of DML—as opposed to DDL— in a single transaction. Rather, it seems to commit each statement. The upshot of this is that it's possible to end up with a database in a weird state where some of the statements executed but the schema version hasn't been incremented. Thankfully you do get an error message so you know something screwed up and you can manually increment the version number and then have rails downgrade for you.

This only happened to me once and then only in development so it's not a huge issue but one to be mindful of.

So, my new shiny toy is still shiny but I'm not about to give up my day job just yet. I still haven't heard enough people bitching about it to make me feel comfortable that there is enough understanding of the technology in the main. I am however doing a real-life project using it and so far so good.