ActiveRecord: Migrating habtm to model table suitable for has_many :through
Oct/072
[Ha, ha, ActiveRecord has the last laugh, and this is much easier than I thought. The Rails Wiki must have been wrong, or it must have been that you couldn't add a primary key in an earlier version of AR.]
ActiveRecord will facilitate many:many relationships across a plain join table that doesn’t have a primary key. This is called “has and belongs to many.” It turns out that it is hard easy to convert this into a join table where the join table itself represents something.
The example here is a bookmarking application like del.icio.us: you have many users with many links (and vice versa), and model those things as a User class and a Link class. Each User has and belongs to many Links; each Link has and belongs to many Users. As a first cut, you might put the title of the URL in the Link class. So John and Amy might each have a reference to the same link. Note in this model, a catch is that since they share that same Link, it is problematic that the title is on the Link class. Because now they must share that title. Our model prevents one of them from saving his or her link for the NY Times with a personal title such as “The Yankee-Loving New York Times,” or some other appropriate moniker. So with that issue in mind, we would like to migrate our schema so that there is a Bookmark class in between User and Link. Now we will put the title in Bookmark, and let the User edit it. If we do this, Amy and John can have bookmarks for the same link (the URL being represented in the Link class) but different titles (in the Bookmark class).
Let’s take it from the top, shall we? In the original model, if you have a model User, and a model Link, and you want to have a many:many relationship, you can define an association for each one of the form has_and_belongs_to_many :links (and the reverse). Then you create a join table like the following (note the suppression of the primary key):
class CreateLinksUsers < ActiveRecord::Migration
def self.up
create_table :links_users, :id => false do |t|
t.column :link_id, :integer
t.column :user_id, :integer
end
end
def self.down
drop_table :links_users
end
end
As time proceeds, we discover that we are interested in adding some extra data that should go on the join table. For instance, we might want the time/date when it was added, or notes specific to the relationship between the link and the user… Or a title, as above. At this point we would need to model it as a first-class (so to speak) ActiveRecord class and use the associations has_many :through for the “endpoints” and belongs_to for the in-between class.
Sadly, if you are like me, you might read the Rails Wiki pages before you do anything, and you would read the following which is all wrong:
Unfortunately, this is tricky because ActiveRecord does not model the original links_users table: It is purely a vehicle for the habtm association. We might think we could rename the table to, say, :bookmarks, and then add a primary key . . . But ActiveRecord does not allow one to add a primary key column to an already-existing table:
Note: The API doc on add_column refers to column_types which say that it can be of type :primary_key. This is not true. add_column cannot use :primary_key as a type_ (see Rails Wiki, Using Migrations)
So . . . What to do? One way The answer is to create a new bookmarks table, and then model it inside the migration. Loop over your users and links to extract the ids, and add them to the bookmarks. The reverse is much easier: Now you can just drop the primary key and rename the table back to links_users:
class CreateBookmarks < ActiveRecord::Migration
class Bookmark < ActiveRecord::Base; end
def self.up
create_table :bookmarks do |t|
t.column :link_id, :integer
t.column :user_id, :integer
end
User.find(:all).each do |u|
u.links.each do |l|
Bookmark.create!( :link_id => l.id, :user_id => u.id )
end
end
drop_table :links_users
print "Change your associations for User and Link to has_many :through => :bookmarks"
end
def self.down
remove_column :bookmarks, :id
rename_table :bookmarks, :links_users
print "Change your associations for User and Link to has_and_belongs_to_many"
end
end
But it can be even easier than that: You might also just add the new primary key going up, and drop it going down:
class CreateBookmarks < ActiveRecord::Migration
class Bookmark < ActiveRecord::Base; end
def self.up
rename_table :links_users, :bookmarks
add_column :bookmarks, :id, :primary_key
print "Change your associations for User and Link to has_many :through => :bookmarks"
end
def self.down
remove_column :bookmarks, :id
rename_table :bookmarks, :links_users
print "Change your associations for User and Link to has_and_belongs_to_many"
end
end
Yet that’s not all. If you want to preserve data across this migration, you will likely find that information will be lost when you move from the newly-modeled table to the table without the primary key. This is because it is very common to enforce uniqueness on one of the keys in the habtm join table. But when you model a classic many:many join, you probably won’t do that. You may find that going “down” that you have added data to the has_many :through version that is incompatible with what you had in your habtm model; in which case you are going to have to define a rule to re-organize the data.
Listening to John Resig at JQueryCamp
Oct/070
I’m listening to John Resig’s presentation on the history of JQuery at JQueryCamp; he’s a very engaging speaker. I’ve been wanting to switch all my JavaScript to JQuery for some time now…
A few live pics (sorry about the exposure!):
Interaction Designer becomes military historian
Oct/070
How strange: Alan Cooper, noted interaction designer and author of About Face has become a military historian, at least according to Amazon:

One way to get a twitter ticker for lecture . . .
Oct/070
Alright: This RSS ticker for Firefox is solid; and if I subscribe it to http://twitter.com/redsoxcast, I’m fairly well covered.
Unfortunately, the ticker’s “mark all as read” only marks the items you’ve seen, not everything . . . But it’s better than nothing, I guess.
Google Feedfetcher: Please get http://twitter.com/statuses/user_timeline/3474891.rss more frequently!
Oct/070
Dear Google Feedfetcher,
You really need to start grabbing a new version of http://twitter.com/statuses/user_timeline/3474891.rss every 5 seconds (this is the feed for redsoxcast on Twitter).
Please, please, pretty please?
We love http://twitter.com/redsoxcast and like to “watch the game” in realtime via the Google Feeds API . . .
Thank you,
A Red Sox Fan
Product idea; or maybe it already exists?
Oct/070
I have a Wednesday lecture coming up for my Ruby class that will conflict with the first game of the World Series. While I was sleeping off a virus this afternoon, I awoke thinking: I should have a feed showing the game status in the footer of my slides.
Well, it was really easy. I got a key for the Google Feed API, then wrote my code to get a feed updated into the slides footer. The code was just this:
google.load("feeds", "1");
function getRedSoxInfo() {
var feed = new google.feeds.Feed("http://twitter.com/statuses/friends_timeline/3474891.rss");
feed.load(function(result) {
if (!result.error) {
var status = ""
for (var i = 0; i < result.feed.entries.length; i++) {
var entry = result.feed.entries[i];
if (status != "") {
status = status + " | "
}
status = status + entry.title;
}
var container = document.getElementById("copyright");
container.innerHTML = status;
}
});
}
Easy enough.
Unfortunately, the way Google Feeds works — and the way any third-party feed API would work — is to give you data from feeds the provider has crawled.
And thus one is dependency on the crawl frequency. Google says it can be up to an hour — more frequent for certain feeds.
Obvously for a baseball game, you want the feed to be updated as soon as possible. For the class, I’d be using http://twitter.com/redsoxcast but to see how rapidly Google is getting updates from Twitter, I used the “With Others” feed for /redsoxcast — these are twitters from friends of redsoxcast. You get a new one every few seconds.
But the Google Feed API just isn’t keeping up.
So here’s the product: An open source Feed API that provides for a simple feed reflector that would be run on one’s own server. I’d write this myself if I had a bit more time. Indeed, this would be a nice add-on to the Google Feeds API: A bit of server-side code so that you could point the Google Feed API at your own server instead of at Google’s. Your server would provide a non-cached dump from the feed.
I love ULINE
Oct/072
I just received my ULINE catalog. ULINE is the world’s greatest supplier of corrogated boxes. OK, there may be others, but I think ULINE is the best. I used to order fancy boxes to hold vinyl records, and I think that company was acquired by ULINE, and now I get the ULINE catalog. Click below to get your own, and then after the image I’ll say a few things about the catalog.
Now, what’s the big deal? Well, suppose you need a carton for a rifle. ULINE’s got it.

Oh, you need to ship a bottle of champagne? No problem!

“Glamour” mailers for that extra pizzazz? Sure.

A terrorist who’s tired of the boring old box cutters? Try something new.

Opening a Chinese restaurant?

Or maybe you just need to warn people that it needs to stay frozen . . .

Naughty words in license key?
Oct/070
I read recently a blog post that muses on the chances of a quasi-random license key getting generated with a naughty word.
Checking the parts of a license key against a dictionary of such words is all fine and good, but if you really want to do people a favor, exclude the following symbols which are very difficult to tell apart visually: o0OB8Il1t
There may be others.
I am sick and tired of typing in license keys only to find that what I am reading as a “B” (the letter) is actually an “8″ (the number), or vice versa. Even with glasses or a magnifier it is sometimes difficult.
Kendall Square: The Turkey
Oct/072
No, I’m not referring to your dot com.
There is a turkey who lives over by the Volpe Center. He’s (he?) been there for at least three years.

I wonder how he made it to Kendall Square? Perhaps he was a turkey at Stanford, and when a Stanford PhD came out to work here, he or she brought the turkey?
You see people doing a big double-take the first time they encounter the turkey: Many people snap a shot with their camera phones.

The Red Seat gets some Recognition
Oct/070
Nice to see our friends at The Red Seat in BostonNow. They have shirts like this one which is all too appropriate given the recent events in Cleveland:


