Connecting Rails to legacy databases

01 November 2007

Connecting Rails to legacy databases isn’t actually that hard – depending on the database you start out with. Recently, we needed to perform some statistical analysis on a large Movable Type database. Rather than wrestling with endless SQL queries at the prompt, it made sense to abstract out a little and build some kind of modelled front end to the statistics.

The most obvious tool for me to use was Rails; I’m familiar with it, I like the way ActiveRecord works, and it means that I can poke around the database from script/console if I need to.

The reason this turned out not to be too hard is because whilst Movable Type doesn’t conform to Rails’ opinionated ideas of what a schema should look like, it is still a well-designed and normalised database. Because of this, we can teach ActiveRecord to understand the database.

First of all, we start by creating our models: for our needs, Blog, Comment, Post and Author. We generate them in the usual manner – script/generate model blog. Once we’ve done that, we delete the migration files in db/migrate it creates, because we’re not going to use them.

Next, we point config/database.yml to the Movable Type database.

And then, we build our relationships thus:

class Blog < ActiveRecord::Base
  set_table_name "mt_blog"
  set_primary_key "blog_id"

  has_many :entries, :foreign_key => "entry_blog_id", :order => "entry_created_on"
end

class Entry < ActiveRecord::Base
  set_table_name "mt_entry"
  set_primary_key "entry_id"

  has_many :comments, :foreign_key => "comment_entry_id"
  belongs_to :blog, :foreign_key => "entry_blog_id"
  belongs_to :author, :foreign_key => "entry_author_id"
end

class Comment < ActiveRecord::Base
  set_table_name "mt_comment"
  set_primary_key "comment_id"

  belongs_to :entry, :foreign_key => "comment_entry_id"
end

class Author < ActiveRecord::Base
  set_table_name "mt_author"
  set_primary_key "author_id"

  has_many :entries, :foreign_key => "entry_author_id"
end

The set_table_name method tells the ActiveRecord class what table to look at, and the set_primary_key method does exactly what it says on the tin. (It also makes sure that #to_param works correctly based on whatever our new primary key is, which is handy). Beyond that, we simply have to specify the foreign keys on our relationships and everything plays ball; we can now access blog.entries just as we do with a typical Rails setup. It’s now easy to write the rest of our Rails app – model methods, controllers, views – just as we normally would. We’re just using the MT database to pull out our data.

And if you’re wondering: yes, it made the manipulation a lot easier, and a few hours poking at the console began to yield some interesting algorithms to apply.

Pimping my iTerm: the TextMate edition

24 July 2007

Rob Orsini’s got a nice little shell script for opening iTerm with everything he needs for his app: a vim window or two, a dev server, the Rails console, and a final tab to tail the development logs.

Of course, I’m a TextMate man myself, and so a bit of tweaking this morning brought out my own version of it. The first tab opens the project in Textmate and puts your shell into the project root, all ready for running tests and specs (unless you’ve already caught the autotest bug); the second tab fires up Mongrel on port 3000; the third fires up the console; the fourth tails development.log . The changes in filenames/commands are mainly my personal environment, so tweak away. Hardly warrants a blogpost, really, but thought I’d share, because it’s really going to be useful in future. Requires iTerm, TextMate, and the mate command (which should be set up by default when you install TextMate).

#!/bin/sh

if [[ $# == 0 ]]; then
  PROJECT_DIR=$PWD
elif [[ $# == 1 && -d "$1" ]]; then
  PROJECT_DIR="$@"
else
  print "usage: railsterm.sh [rails project directory]"
  return 1
fi

osascript <<-eof
  tell application "iTerm"

    make new terminal

    tell the last terminal

      activate current session

      launch session "Default Session"

      tell the last session
          set name to "$PROJECT_DIR"
          write text "cd \"$PROJECT_DIR\""
	      write text "mate .;"
          write text "clear; ls;"
      end tell

      launch session "Default Session"
      tell the last session
          set name to "server"
          write text "cd \"$PROJECT_DIR\""
          write text "mongrel_rails start"
      end tell

      launch session "Default Session"
      tell the last session
          set name to "console"
          write text "cd \"$PROJECT_DIR\""
          write text "./script/console"
      end tell

      launch session "Default Session"
      tell the last session
        set name to "development log"
        write text "cd \"$PROJECT_DIR\""
        write text "cd log"
        write text "tail -f development.log"
      end tell

    end tell
  end tell
eof

Don't forget to chmod +x the file if you want to make it executable.

Another day, another release: updated calendar_helper

09 July 2007

I’ve just had my first patch accepted on an open source project. Quite chuffed with that! As of this weekend, the Rails calendar_helper plugin is now at version 0.21. My changes are very minimal, and only really to do with the markup.

Firstly, the default table markup’s had an overhaul. The date now goes into a %lt;caption> tag, outside the <thead>, as is appropriate. The <th>‘s in the thead now have scope="col" applied to them, again, as is appropriate.

The only other change is optional. If you pass in an option of :accessible => true, any dates in the grid which fall outside the current month will have <span> class="hidden"> monthName</span> appended to them. It could be reasonably inferred that plain numbers are dates that relate to the caption of the table, but the numbers outside the current month should probably be clarified.

You can come up with your own method of hiding content marked as .hidden; at NPG, we use the following:

.hidden {
	position:absolute;
 	left:0px;
 	top:-500px;
 	width:1px;
 	height:1px;
 	overflow:hidden;
}

but really, use whatever you’re most comfortable with.

You can get the plugin from Geoffrey Grosenbach’s subversion:

http://topfunky.net/svn/plugins/calendar_helper/

via the usual Rails plugin installation method.

Rails mistake of the day: validating a relationship before it’s created

03 April 2007

(I wasted about half an hour on this last night, and am now kicking myself about how simple it was. Given that… definitely worth documenting the problem, so that no-one else gets held up on it.)

I’m currently working on some forum software (before you yawn: it’s not your average forum, it’s not worth the effort forking Beast, and besides, it’s a learning exercise). In this forum, when you create a topic, you also create the first post in the topic at the same time. Here’s how that happens (I don’t think I need to show the form view – the controller will suffice):

@topic = Topic.new params[:topic].merge(:user => current_user,
                                :forum => Forum.find(params[:id]))

post = @topic.posts.build(params[:post].merge(:user => current_user))

Unfortunately, I was running into all sorts of problems – in that even though the forms were filled out and being passed to the controller, the topic wasn’t being created. This was because the post wasn’t validating. Turns out the problem was that in my post.rb, I’d said that a Post validates_presence_of :topic_id. This validation was clearly being run before the topic was created; given we’re using build to make sure the post is attached to the topic, I don’t think we need that validation. Once I stripped that out, everything worked fine.

A pretty trivial error, I know, but once my tests started failing, I had to take the application apart a little to find out what was going on. In a nutshell: I don’t think you always need to validate relationships that have to exist for the model to make any sense.

Of course, if there’s a way I can keep the validation of topic present but still build the topic/post combination as above… comments are very welcome.

0 errors, 0 failures

13 January 2007

I wrote my first proper Unit Tests in Ruby today. It felt good.

That probably sounds slightly gauche and hypocritical coming from a Rails developer. But remember – I’ve come from a front-end background; most of the time, other people make the tests; it’s up to me not to break them. I’m perfectly capable of editing tests to bring them in line with updated functionality; it just tends to have been the case that I’ve never really got my head around testing properly.

That changed recently, mainly thanks to Geoffrey Grosenbach’s excellent Peepcode screencasts. I’ve read a fair amount on testing up to now – Mark Pilgrim’s Dive Into Python is very good on the subject – but it was Geoffrey’s material that really clicked with me. (I’m watching his Capistrano one at the moment, and it’s certainly proving to be as good as the testing one).

I’ve always understood the point – and the utility – of test suites, but I’m pleased to have got my head around writing my own, starting with one (of several) projects on the back burner. Small steps at first, but it’s really satisfying to be working in a relatively test-driven manner.

And so I’ve been enjoying watching the dots fly by before those magic words come up: 0 errors, 0 failures. The Peepcode screencasts come strongly recommended, as well. Here’s to slightly more watertight code from now on.

Handling IE specific stylesheets with a Rails plugin

10 December 2006

Time to announce my first published Rails plugin: ie-specific. And, more importantly, time to explain it.

I work as a front-end developer for a large scientific publishing firm. We’re very much committed to standards compliance and accessibility, as such, make every effort to ensure our sites work in as many browsers as possible. And that includes Internet Explorer 5.

Now, prior to IE7, we did this (by and large) by producing standards-compliant stylesheets, and then adding browser hacks where they were the only solution, in order to achieve consistent look/feel in many browsers. Fast forward to IE7, and we’ve now got a problem: many browser hacks (notably * html) no longer work in IE7 and yet it still has its own raft of styling quirks.

So we’ve decided to change the way we implement browser-hacking. On the project I’m currently working on, we now have a main.css which contains all the standards-compliant, “correct” CSS. We then place the IE-specific styles inside IE conditional comments. Each browser version gets a css file of its own where necessary, named for the name of the relevant IE conditional filter – eg, lte-ie-6.css, ie-6.css, lte-ie-5.500, etcetera.

When I started work on the front-end integration for this Rails project, it was clear that this was obviously something that could be automated in Ruby – and, given that we were probably going to use it again, it made sense to package it as a plugin so that the next time I came to need it for a project, a quick script/install would be enough.

Enter ie-specific, currently hosted at Google Code given that a) it’s open-source and b) it’s an easy way to get anonymous public svn.

It’s really simple. First, install the plugin:

script/plugin install http://ie-specific-rails-plugin.googlecode.com/svn/ie-specific

Now, place any IE-specific stylesheets into #{RAILS_ROOT}/public/stylesheets/ie_specific, named with the appropriate conditional filter (eg: lte-ie-6.css). Similarly, you can place IE-specific javascript into #{RAILS_ROOT}/public/javascripts/ie_specific. We use IE-specific Javascript to simulate min/max-width capabilities in Internet Explorer.

Finally, in the <head> of your layout, include the following ERb:

<%= ie_specific_styles_and_scripts %>

Each file in the ie_specific directories will be automatically included inside appropriate conditional comments – CSS files will be imported via an @import directive, and JS files will be appropriately escaped as CDATA (important for us, because we write XHTML 1.0 strict). There’s no unnecessary duplication, either – lte-ie-6.css and lte-ie-6.js will automatically be placed inside the same conditional comment.

That’s it, really – it’s very simple, but it’s also a convenient way to keep markup and styles clean and let other developers know exactly what’s a hack and what’s not. For more info, check out the README. And let me know what you think!

More on multi-model search with acts_as_ferret

24 September 2006

So, Matt pointed out that when you do things like that, the scores for different Models are all ranked seperately because they’re coming out of different indexes. The trick, obviously, is to use the multi_search method, which generates a multi-model-index, and that’s probably a better approach. This approach is very well documented in the RDoc. So all it shows is: probably should have RTFM. The thing below isn’t by any means bad, it’s just a little like re-inventing the wheel.

Never mind, eh?

(This mail from Jonas is also worth a read on this matter).