untrustworthy hashes in ruby
I ran into one of those annoying bugs where a couple different things conspired to have an annoying result. We use GMail at the moment as our SMTP server, and we put all sorts of automated notifications through it. GMail has a well-documented 500 message limit – that’s per-account, per-day. Upgrading to Premier bumps that to 2000, but we really don’t need to upgrade everybody on the domain just to get the extended limits for a system account. Ordinarily, we don’t send anywhere near that much email, but a bug that got our asynchronous mail-sending queue a little clogged up led to a glut of mails that needed to go out. Which put us over the limit. Bleh.
We’ll be switching to a sturdier mail provider soon enough, but for the time being, I decided to rig ActionMailer to load-balance between four accounts. So there’s a few different ways you could pull this off.
- Monkeypatch ActionMailer. I try to avoid patches like this if at all possible.
- Randomize ActionMailer’s user_name setting before each use. Well, AM’s settings hash is a class global. Changing it every time, especially when others might be poking at it, seems in poor taste.
- Something else.
“Something else” turned out to be hacking the settings hash itself. Ordinarily, when you set it up, it looks like this:
ActionMailer::Base.delivery_method = :smtp
ActionMailer::Base.smtp_settings = {
:address => "smtp.gmail.com",
:port => 587,
:domain => "mydomain.com",
:authentication => :plain,
:user_name => "somebody@mydomain.com"
:password => "mypassword"
}
Instead, I did:
ActionMailer::Base.delivery_method = :smtp
smtp_settings = {
:address => "smtp.gmail.com",
:port => 587,
:domain => "mydomain.com",
:authentication => :plain,
:password => "mypassword"
}
SYSTEM_ACCOUNTS = %w[system@mydomain.com system2@mydomain.com system3@mydomain.com system4@mydomain.com]
ActionMailer::Base.smtp_settings = Hash.new { |h, k| SYSTEM_ACCOUNTS[rand SYSTEM_ACCOUNTS.size] if k == :user_name }.merge!(smtp_settings)
The useful part is the employment of the version of Hash.new that takes a block. If a key lookup fails, the block is invoked, and is passed the hash and the requested key. It’s basically “method_missing” for hashes. So, when ActionMailer looks for :user_name, the block picks a random entry out of the SYSTEM_ACCOUNTS array. For lack of a better name, I called this approach an “untrustworthy” hash – as one generally expects to get the same key-value mapping from a hash every time.
This wouldn’t work in all cases – for example, has_key?(:user_name) would fail. I’ve got a more generic solution in mind, that being an add-on for Hash that lets you insert key/proc pairs as well as key/value pairs. More on that soon.








