"Свинья везде грязь найдёт"

Сегодня я зашёл в блог известного в узких кругах автора Struts Framework и Java Server Faces Крэйга Мак-Кланагана.

Своим некачественным кодом этот человек портил жизнь сотням тысячам программистов, а если учесть, что он ещё и автор Catalina, то миллионам.

Недавно он заинтересовался Ruby on Rails. Берегитесь, рубероиды!

The other big obstacle to Ruby's adoption is ActiveRecord

I have been using Ruby for a few months now, doing development of small projects of up to 1000 lines. But I still bite into the naming clashes with ActiveRecord regularily. They appear at random places and it takes a lot of experience to figure out what is happening. Here's an example with the type attribute clash. Of the two code snippets below, the first one does not work (thing.type equals nil, after execution), and the second one works.

thing = Thing.new(:type=> Type.new)
thing = Thing.new
thing.type = Type.new

Another example is having an attribute named transaction.


Calling .save on a model with such an attribute will produce no output in the logs and leaves the developer in frustration about what happens.


ActiveRecord as an implementation of a design pattern straight out of the book

ActiveRecord has been built straight from the Martin Fowler's Single Table Inheritance design pattern. Period. This is fine for simple applications with that can live with the database design imposed by the application framework.

Once the developer tries to think throughly the database design, he immediately hits the limits of the STI paradigm. Here's an example.

One common method to define a hierarchical relation in Object-Relational Mapping where a child can have only one parent is to put the reference to the parent in the child table. This is a common and very efficient way to map a class hierarchy into database tables in Hibernate, for instance. The resulting tables can then look as follows:

  `id` int(11) NOT NULL auto_increment,
  PRIMARY KEY  (`id`)
  `id` int(11) NOT NULL auto_increment,
  `foo_id` int(11) NOT NULL,
  PRIMARY KEY  (`id`)
  `id` int(11) NOT NULL auto_increment,
  `foo_id` int(11) NOT NULL,
  PRIMARY KEY  (`id`)

Note that this table structure allows to efficiently retrieve bars and bazs from foos, as well as ascend to foos from bars and bazs.

If you are bound to ActiveRecord, it imposes a solution with one table having one additional id to identify the parent and a string to identify the type of the child. All fields belonging to bars and bazs will appear in the same table, intermixed.

Some more ActiveRecord bashing

This time, it took me around 8 working hours since the start of a new ruby-based project before I stumbled upon what seems to be a serious bug. The first time I tried to extend an association, activerecord failed with
associations.rb:1368:in `const_set': wrong constant name FooBar::FooBarsAssociationExtension (NameError)
which can be easily reproduced running the following code: {syntaxhighlighter brush:ruby} require 'rubygems' require 'activerecord' module FooBar class Foo

ActiveRecord and the DRY3 principle

ActiveRecord is a beautiful piece of software. But just step aside of the prescribed road, and you are guaranteed to have problems.

This time, my problems began when I started to design the model first. I quickly laid out a couple of screens of objects, wrote the connection line and ran the code.

class SubjectLocator  "String"

# a dozen more objects follow...

  :adapter  => "mysql",
  :host     => "localhost",
  :username => "test",
  :password => "test",
  :database => "activerecord"

It has not come as a surprise that ruby replied with an exception.

Table 'activerecord.subject_locator' doesn't exist: SHOW FIELDS FROM `subject_locator` (ActiveRecord::StatementInvalid)

What surprised me is that after half an hour of search on the internet, I found no way to boostrap a DB schema out of the model. There were discussions on the rails mailing list. They even went as far as suggesting non-destructive automatic migrations, a feature that exists in Hibernate for at least 5 years already. None dared to implement either.

I am now left with one solution. Apply the DRY3 principle, aka "Do Repeat Yourself 3.times" and start writing the migrations script for my models (repeat!), as well as an SQL schema for my MySQL database that would keep indexes, views and triggers in addition to what I will boostrap from migrations (repeat again!)