2010-03-25

New fb Release

The last release of the Firebird Ruby extension fb for Windows was 0.5.3. Back then, binary gems seemed to be more common than they are today. The One-Click Installer for Ruby 1.8.6 had taken the lead from the Cygwin-based versions that came before, due to better compatibility with various Windows libraries and a smaller footprint.

Building Ruby extensions required using the same version of MSVC that had been used to build Ruby and another compiler could not be substituted, because numerous bug fixes and workarounds for Windows were specific to that one compiler. Building the Firebird extension required three different options to specify the location of various libraries, which in the tradition of Windows, had no standardized locations.

The workstation with the magical combination of build tools and libraries has long been retired, so while the Unix version of the extension has seen bug fixes and improvements, the Windows version has languished. It was generally good enough for development and small utilities, though and since my company deploys on Linux servers, there was no strong incentive to invest in a solution. But I always felt bad for the Windows users left out in the cold.

I'm happy to say that the RubyInstaller Team has done great work with MinGW32-based build of Ruby 1.8.7 and the associated DevKit. It only took a couple tweeks to extconf.rb to recognize the mingw32 platform along with mswin32 and then everything "just worked." Well, almost. You still had to build by hand, specifying --with-opt-dir=C:/Progra~1/Firebird/Firebird_2_1 (or wherever your Firebird installation was). To make gem installs work better, it now looks in C:\Program Files\Firebird for the latest version of Firebird and uses that one. Suggestions for improving this logic to work with foreign language versions of Windows are solicited.

In a more-perfect world, every Windows machine might come with a proper development toolchain, but for now you'll need to install DevKit in the proper location and have Ruby's bin directory in your PATH.

fb-0.6.6, with the latest tweaks, is on RubyGems.org and github.

UPDATE: fb-0.6.7 with improved installation for non-English Windows versions is out.

2010-01-31

Marshalling MongoMapper Documents

Conventional wisdom has it you shouldn't store models in your Rails sessions. Conventional wisdom is right: Marshalling complex objects can be slow; Session objects can become stale; Sessions can balloon up too large for cookie storage, limiting your scaling options.

However, the rule that models don't belong in the session is trumped by the rules that POSTs should yield GETs and GETs should not mutate state. Every page should be reloadable, without an annoying pop-up asking if you want to resubmit a form. POSTs are allowed to be dangerous. POSTs are allow to charge your credit card, delete records or log you out. So, when creating or editing a model, if the model fails to validate, I refuse to display an error page as a direct result of a POST. Instead, the user's browser is redirected back to the new or edit page they were on and the form is redisplayed with helpful error messages. Indeed, this has become a standard Rails pattern.

Which brings us to my most-recent roadblock. MongoDB is my new favorite toy and MongoMapper is a promising stand-in for ActiveRecord. However, when I employed this standard Rails pattern:
def new
@user = flash[:user] || User.new
end

def create
@user = User.new params[:user]
if @user.save
redirect_to user_path(@user)
else
flash[:user] = @user
redirect_to new_user_path
end
end
I was stymied by this error:
TypeError: singleton can't be dumped
Some chain of references from the model evidently connects it to a singleton object. MongoMapper is still under heavy development, so I'm confident at some point this will be resolved, but meanwhile the following monkey patch appears to do the trick:
module MongoMapper
module Document
def marshal_dump
instance_variable_names.inject({}) { |m, name| m[name] = instance_variable_get(name); m }
end

def marshal_load(values)
values.each_pair { |k, v| instance_variable_set(k, v) }
end
end
end
I stuck this code in a file called mongo_marshalling.rb in config/initializers.