FYI – I’ve been posting new content at spraints.github.com. I was holding off on posting this here until I customized the UI some and maybe put it on a custom domain. That’s not happening in the near future, so, for now, head on over and enjoy my latest posts!

If Ruby5 can have tales of drunkenness, so can my ruby blog.

On Sunday, on the way back home from town, we were driving down a four-lane city street. The guy just ahead of us in the other lane was swerving towards oncoming traffic. Every time a car would come the other way, he would drift across the yellow line. Anticipating a collision that would send his truck rebounding into my lane, I slowed and left plenty of space. This foresight proved prudent when the road narrowed to two lanes, as he swerved at the last second into my lane.

I continued to leave plenty of space, about 150 yards, which brought out the impatience in the drivers behind me. Two such drivers passed on the now dark country highway, only to find obviously-inebriated-dude just ahead. The driver-who-shouldn’t-have-been pulled part way off the road and stopped (on a state highway!) to let the rest of us pass. I tried to slow down enough that he’d go on without me passing, but he waved me on. (This was the wave that is featured in the title of this post.)

I reluctantly passed, and traffic ahead of me slowed. I slowed, and saw the lights behind me accelerate. Great, I thought, now I’m going to get rear-ended. I pulsed my brake lights, and that got his attention so he slowed. The cars ahead sped up, then slowed again. Repeat the pulsing lights to encourage Mr. Tipsy to slow down. At the first side street, I turned off the highway, turned around, and got back on the highway. This was plenty of time for the subject of the story to pass.

At the next major intersection, I was committed to turning a different direction from the other traveller. He went straight, down a gravel county road (county road 50 S), which suited me fine, as I was turning right (onto highway 29).

The rest of the story made the paper. Fortunately, nobody suffered permanent injuries.

This is a not that common of a problem, though there are other posts on the topic. But my solution’s a little different from what I found, so I figured it was blog-worthy. I used this in a rails 3.0 app. I’ve also only tried it with sqlite, so it may totally blow up when I push to heroku. YMMV.

Take, for example, some initial tables:

users – id:primary_key
replies – id:primary_key
liked_replies – user_id:integer, reply_id:integer

and some initial models:

class User < ActiveRecord::Base
  has_and_belongs_to_many :liked_replies, :class_name => 'Reply', :join_table => 'liked_replies'
end

class Reply < ActiveRecord::Base
  has_and_belongs_to_many :likers, :class_name => 'User', :join_table => 'liked_replies'
end

And, of course, there’s existing data that you don’t want to destroy.

So, I generated a migration:

bundle exec rails g migration add_id_to_liked_replies id:primary_key

If you’re curious, the migration looks like this:

class AddIdToLikedReplies < ActiveRecord::Migration
  def self.up
    add_column :liked_replies, :id, :primary_key
  end
  def self.down
    remove_column :liked_replies, :id
  end
end

Migrate as usual, and then the app stops running, because routes can’t be built, because User is a devise model, and devise_for tries to load User which has an association that generates one of these:

Primary key is not allowed in a has_and_belongs_to_many join table (liked_replies). (ActiveRecord::HasAndBelongsToManyAssociationWithPrimaryKeyError)

Hm. This will probably happen when we deploy, too. And this makes a db:rollback fail, too. !!!! So, how can we make the code work with or without a primary key? I’ve seen lots of people talk about making your code work with the before & after version of your db, and that seems like the right goal here, too.

Here are my new model classes that work with either the new or old liked_replies table:

class User < ActiveRecord::Base
  begin
    has_and_belongs_to_many :liked_replies, :class_name => 'Reply', :join_table => 'liked_replies'
  rescue
    has_many :liked_reply_records, :class_name => 'LikedReply', :dependent => :destroy
    has_many :liked_replies, :through => :liked_reply_records, :source => :reply
  end
end

class Reply < ActiveRecord::Base
  begin
    has_and_belongs_to_many :likers, :class_name => 'User', :join_table => 'liked_replies'
  rescue
    has_many :liked_replies
    has_many :likers, :through => :liked_replies, :source => :user
  end
end

class LikedReply < ActiveRecord::Base
  belongs_to :user
  belongs_to :reply
end

So, say you’re in a fantasy world, and the rif-raf is beating down your doors…

Or, maybe, you’re trying to write a sane json rendering of your model, and the model has a polymorphic belongs_to.

As a bit of review, let’s start with the easy case. You have some models with a simple association.

class Child < ActiveRecord::Base
  belongs_to :parent
end

class Parent < ActiveRecord::Base
  has_many :children
end

The rabl for a child is pretty easy. In app/views/children/show.rabl you might have:

object @child
attributes :id, :name
child(:parent) { partial "children/parent" }

As it turns out, you’re actually writing an app that’s used to track orphans left behind after UFO visits, so you need to support children of aliens, too. So your models look more like this:

class Child < ActiveRecord::Base
  belongs_to :parent, :polymorphic => true
end

class HumanParent < ActiveRecord::Base
  has_many :children, :as => :parent
end

class AlienParent < ActiveRecord::Base
  has_many :children, :as => :parent
end

And of course you need to output different information for alien parents (app/views/children/alien_parent.rabl) than for human parents (app/views/children/human_parent.rabl). So your rabl in app/views/children/show.rabl will look more like this:

object @child
attributes :id, :name
code(:parent) { |child|
  partial "children/#{child.parent.class.name.underscore}",
   :object => child.parent
}

I’m giving squid a spin as a local cache for gems. My goal is to speed up the time git clean -whatever ; bundle install --path .bundle takes.

This solution isn’t perfect. It doesn’t make the bundle install as fast as I’d like. Also, squid needs to be restarted (sudo launchctl stop squid) when I change locations and/or sleep my laptop, because it forgets how to look up DNS (it seems to be caching the DNS server list).

I’m using homebrew and on a Mac running Snow Leopard, so this will be most helpful for anyone running the same.

Install squid

brew install squid

This installed squid 3.1.9.

Configure squid

Open /usr/local/etc/squid.conf, and uncomment the cache_dir line.

Start squid

First, you need to initialize squid. Some of this stuff may be equivalent to throwing your machine under a bus, so please be aware of what you’re copying.

chown nobody /usr/local/var/cache
chgrp everyone /usr/local/var/logs
chmod g+w /usr/local/var/logs
sudo squid -z

I wanted squid to run any time the machine is running. So, I created /Library/LaunchDaemons/squid.plist to look like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>squid</string>
    <key>OnDemand</key>
    <false/>
    <key>ProgramArguments</key>
    <array>
      <string>/usr/local/sbin/squid</string>
      <string>-N</string>
      <string>-d 1</string>
      <string>-D</string>
    </array>
    <key>ServiceIPC</key>
    <false/>
    <key>RunAtLoad</key>
    <true/>
  </dict>
</plist>

Start it by doing sudo launchctl -w /Library/LaunchDaemons/squid.plist

Configure bundler/rubygems

Open ~/.gemrc and add:

http_proxy: http://localhost:3128

My .gemrc looks like this:

---
gem: --no-ri --no-rdoc
http_proxy: http://localhost:3128/

For a while, I’ve been keeping bundled gems local to each project. (It’s pretty easy to do: bundle --path .bundle/gems the first time you bundle a project. I like putting it in .bundle because it’s already gitignored.) This results in a lot of duplicate gems across my system, but it has the advantage of keeping each project very self-contained, and I avoid undesirable side-effects (like rake getting upgraded) just because I bundle some code from github.

This leads to a pretty sparse set of system gems. But it also makes it a little challenging to bootstrap a new rails project, since there isn’t a current rails executable! How to avoid installing the latest rails to start a new project?

It’s pretty easy, when you think about it, but it is a little less straightforward.

mkdir -p ~/my/new/awesome_project
cd ~/my/new/awesome_project
echo source :rubygems            > Gemfile
echo gem 'rails', '~>3.1.0.rc4' >> Gemfile
bundle --path .bundle
bundle exec rails .

It’ll ask if you want to overwrite Gemfile, which is, of course, fine, because it’s just going to add more gems.

Don’t do it.

And not just because you should be using rspec.

rake db:schema:load test does this:

** Invoke db:schema:load (first_time)
** Invoke environment (first_time)
** Execute environment
** Execute db:schema:load
** Invoke test (first_time)
** Execute test
** Invoke test:units (first_time)
** Invoke test:prepare (first_time)
** Invoke db:test:prepare (first_time)
** Invoke db:abort_if_pending_migrations (first_time)
** Invoke environment 
** Execute db:abort_if_pending_migrations
** Execute db:test:prepare
** Invoke db:test:load (first_time)
** Invoke db:test:purge (first_time)
** Invoke environment 
** Execute db:test:purge
** Execute db:test:load
** Invoke db:schema:load 
** Execute test:prepare
** Execute test:units
** Invoke test:functionals (first_time)
** Invoke test:prepare 
** Execute test:functionals

If you look closely, you’ll see that db:schema:load is in there twice. The first time, it’s loading into your dev environment. The second time, it’s loading into your test environment. Or, tries to: rake notices that db:schema:load was already run, so it skips it. You know, unnecessary extra work.

I am most likely to rake db:schema:load test (or spec) on a CI server, sometimes with a db:migrate mixed in. One-liner ci scripts are nice. But keep db:schema:load and test separated.

If you have db/schema.rb in source control like you should, and you keep it up to date, you still need to db:schema:load to avoid the ‘abort if pending migrations’ thing: rake db:schema:load ; rake test

If your db/schema.rb might be out of date, rake db:schema:load db:migrate ; rake test

If you’re a glutton for punishment: rake db:migrate test

Follow

Get every new post delivered to your Inbox.