11 February 2010

ActionMailer and Multiple SMTP Accounts (useful with gmail)

ActionMailer works with only one smtp account; a single google apps account is limited to 500 (or 2000) mails per day; my app legitimately needs more. Besides, I'd rather notifications come from an appropriately named account rather than a generic "no-reply@example.com" address. Here's what I came up with. Firstly, config/smtp.yml describes my various accounts - I settled on one per mailer class. Secondly, a patch to ActionMailer::Base enables switching smtp accounts based on the mailer class.

Here's an example with four mailers: an account mailer, an exception notifier (works with the lovely ExceptionNotifier plugin), a "share this" mailer so your site can be all viral and stuff, and a prize mailer for the good news.

config/smtp.yml

defaults: &defaults
  address:        smtp.gmail.com
  port:           587
  domain:         example.com
  authentication: !ruby/sym plain

account_mailer:
  <<: *defaults
  user_name:      accounts@example.com
  password:       "pw1"

prize_mailer:
  <<: *defaults
  user_name:      winner@example.com
  password:       "pw2"

exception_notifier:
  <<: *defaults
  user_name:      dev_team_obviously_sucks@example.com
  password:       "pw3"

share_this_mailer:
  <<: *defaults
  user_name:      share_this@example.com
  password:       "pw4"

ActionMailer::Base patch

require 'smtp_tls'

module ActionMailer
  class Base
    cattr_accessor :smtp_config

    self.smtp_config = YAML::load(File.open("#{RAILS_ROOT}/config/smtp.yml"))

    def smtp_settings
      smtp_config[mailer_name].symbolize_keys
    end
  end
end

I put this in config/initializers/action_mailer.rb. And that's it! No changes required to your mailers or email templates or anything else in your application. As you can see, the patch merely overrides ActionMailer's smtp_settings class method and replaces it with an instance method that decides at sending-time which smtp configuration to use.

We needed to do this because sendmail was failing erratically - some users weren't getting any mail from our app at all - presumably due to hypersensitive spam filters somewhere on the chain, maybe related to my not understanding how SPF records are supposed to work.

You could easily fancify this to switch SMTP config based on the time of day, or based on your user's locale (so you can use a nicely localised "from" address - Google overrides the "from" address sent by ActionMailer), or even based on whether the Moon is in Scorpio if you cared. Just replace the call to mailer_name with a call to your config-switching method.

I understand that rails 3 is beautifuller in many ways including the way ActionMailer works so this might well be obsolete in a few months except for you suckers working on legacy systems. I hope this helps, let me know one way or the other.

07 February 2010

it wasn't git actually

Ouch! Panic!

$ git push origin master
fatal: unable to fork

and later,

$ git push origin master
fatal: git-pack-objects failed (Resource temporarily unavailable)

But it had nothing to do with git or a corrupted repository as google seemed to be trying to suggest; it was rather my trusty mac running out of something. The solution: shut down itunes and try again.

30 January 2010

Upgrading Gutsy to Hardy

My slicehost slice was running Gutsy (Ubuntu 7.10), but when I switched my projects from my personal svn server to github, gutsy only had an early version of git that doesn't support submodule ... the only way to upgrade git was to upgrade my ubuntu.

After a quick mysql backup, these are the instructions that worked:

sudo vi /etc/apt/sources.list
# [ replace each "gutsy" with "hardy" ]
sudo apt-get update
sudo apt-get dist-upgrade

I found this solution on ubuntugeek.com. It worked first time, like a charm. Congratulations ubuntu team ... linux has come a long, long way

29 January 2010

Back up your mysql database with mysqldump

I don't use this often so I end up googling it every time I need it.

mysqldump -u root -p database_name > sql_dump_file

Now I know exactly where to find it and I don't have to scan a whole article just to get the syntax.

Change "root" to the user you normally use; you an also specify -ppassword (no space between -p and the password) so you don't have to enter the password interactively ... security issues etc but if you're running this from a script I'm not sure what the alternative is.

28 January 2010

What kind of World do You want to inhabit?

Whatever you think of Stallman, go have a look at this quick dystopia, and imagine what kind of world you would like to live in. Then head over to boingboing and learn about ACTA. I preferred the days when Russia and China were the bad guys, and they were far away.

27 January 2010

It should have no missing translations!

I'm a big fan of rspec and of rails' I18n, and I don't like having to study yml translation files over and over to make sure every key has a translation in every language; so I wrote it_should_have_no_missing_translations to test my templates for missing translations.

Previously, I needed this for every template:

  it "should have no missing translations in fr" do
    I18n.locale = "fr"
    do_render
    response.should_not have_tag("span.translation_missing")
  end

  it "should have no missing translations in en" do
    I18n.locale = "en"
    do_render
    response.should_not have_tag("span.translation_missing")
  end

Where do_render knows how to render the template I'm testing. If you're like me, and I presume you are, you're thinking the duplication up there is a bit annoying and someone should do something about it. Well, here you go:

  it_should_have_no_missing_translations

You like? Obviously, it_should_have_no_missing_translations needs a bit of context, like an implementation of do_render, and any other setup you need. Here's the implementation, under the WTFPL. Copy it into your specs or make a helper out of it that you include in your spec, or publish it in a gem and become famous.

  def it_should_have_no_missing_translations
    INSTALLED_LANGUAGES.each do |lang|
      it "should not have translations missing in #{lang}" do
        I18n.locale = lang
        do_render
        response.should_not have_missing_translations
      end
    end
  end

have_missing_translations is defined thus:

  INSTALLED_LANGUAGES = [:en, :fr] unless defined?(INSTALLED_LANGUAGES)

  class TranslationsMissing
    def initialize(scope)
      @scope = scope
    end

    def matches? response
      if response.is_a? String
        root_node = HTML::Document.new(response, false, false).root
      else
        root_node = HTML::Document.new(response.body, false, false).root
      end
      m = @scope.css_select root_node, ".translation_missing"
      @missing_translations = []
      m.each do |mt|
        @missing_translations << mt.children.first
      end
      m.size > 0
    end

    def failure_message
      "expected that response would contain a translation_missing element, but it didn't"
    end

    def negative_failure_message
      "expected that response would contain no missing translations, but it contained these \n#{@missing_translations.join("\n")}"
    end
  end

  def have_missing_translations
    TranslationsMissing.new self
  end

17 November 2009

FOODOPI - coming soon to a nation near you!

IP news from France. My translation probably isn't perfect. There are other things to worry about, too.

Watching the evilly smug faces of recording industry executives following France's recent adoption of Hadopi, restaurant owners have decided they deserved a slice of the IP pie, too. Watch out for the new law to be introduced later this year: FOODOPI!

Restaurant managers, owners, and chefs who have dedicated years of their talent, skill, and secret sauce to creating irresistible mouthwatering dishes have watched in dismay as food pirates illegally copy their ideas, recreating such classics as Ratatouille, Chicken Curry with Rice, and Spaghetti Bolognese with impunity in their own homes.

This is all going to change and the impoverished actors of the restauration industry will finally see their hard work rewarded and protected. The new FOODOPI law, if adopted, will allow restaurant industry executives name individuals suspected of recipe pirating and, after three warnings, those individuals will be prohibited from cooking for a length of time varying from six months to ten years.

Although the lack of judicial review during the prohibition process has raised concerns among civil liberties groups, a spokesperson for a French restaurateur's association observed "the justice system is already overstretched and it makes no sense to burden it even further. This law is a huge win for the public, ensuring continued innovation in the food service industry. It would be a disservice to the public and a drain on limited taxpayer resources to push this through the courts."

A spokesperson for a US-based organisation conducting cutting-edge research on genetic improvement of popular crops said while they would vigourously defend their IP in France under this new law, "currently we have no intention of prosecuting the most widespread violation of our intellectual property: the use of sodium chloride as a food additive for flavour enhancement". [A lawyer friend has advised me that this patent may not be applicable in France anyway as the use of "table salt" (to use pirate jargon) is a popular custom in this country, dating back centuries - ed.]

While the details of the new system have yet to be worked out, a leaked document obtained by this site indicates some of the strategies being considered -

  • Government-mandated cooking equipment for all new domestic kitchen installations with remote sensing equipment for ambient atmospheric analysis, allowing FOODOPI investigators detect potential violations of food industry IP by comparing chemicals and food traces in the air with a database of protected recipes.
  • Food retailers will report purchases to a central database that will apply sophisticated pattern-matching algorithms to identify individuals who may be planning IP violations. As an example, the document describes a hypothetical shopper in the process of acquiring 500g basmati rice, 8 chicken thighs, unflavoured yoghurt, turmeric, ginger, and garlic. The proposed pattern-matching software would flag this shopper as a potential pirate about to prepare Chicken Curry with Rice for 4.
  • Repeat offenders would ultimately have all kitchen equipment confiscated and perhaps have a camera installed in their homes to deter future violations

The document also noted some concerns of IP holders, including the threat of violations by picknickers and people using obsolete or camping equipment, where monitoring systems are less feasible.

Put that in your pipe and smoke it.