Rails 2.2.2: Noisy Translation Errors

[This is one of several posts on upgrading a Ruby on Rails app from Rails 1.x to 2.2.2.]

Rails 2.x added some nice support for internationalization. I’m using this to “Canadianize” the UI of my app by translating a few words from the EN-US locale to EN-CA. “Favorite” becomes “favourite”, “huh?” becomes “eh?”, “Coors Light” becomes “water”, and so on.

One gotcha I ran into is that translation errors don’t manifest themselves as exceptions. The default I18n implementation rescues translation errors and returns the failed translation keys as a string. This behavior is nice in the development environment, because you can see the strings showing up in your application’s UI. On the other hand it makes testing dangerous, because translation errors can easily go unnoticed, especially when running automated tests.

I prefer my tests to fail noisily. (Hmm… perhaps this should be the default behavior?) To that end, here’s a patch, also available on Pastie.

# The code below patches I18n to raise exceptions for all errors, including translation errors.
# The default (unpatched) behavior in Rails 2.2.2 rescues translation errors and returns the failed 
# translation keys as a string. This behavior is undesirable in test, because it makes it too
# easy for translation errors to go unnoticed when running automated tests. Instead, we want to fail noisily.
#
# == Usage
# (within test_helper)
# require File.dirname(__FILE__) + '/i18n_patch'      

# Patch translation within views

module ActionView
  module Helpers
    module TranslationHelper
      def translate(key, options = {})
        options[:raise] = true
        I18n.translate(key, options)
      rescue I18n::MissingTranslationData => e
        raise e if RAILS_ENV == 'test'  # <<< this line is the patch. everything else in this method is original.
        keys = I18n.send(:normalize_translation_keys, e.locale, e.key, e.options[:scope])
        content_tag('span', keys.join(', '), :class => 'translation_missing')
      end
      alias :t :translate

      def localize(*args)
        I18n.localize *args
      end
      alias :l :localize
    end
  end
end

# Patch translation in models and controllers
module I18n
  class << self
    def raise_all_exceptions(*args)
      raise args.first
    end
  end
end

I18n.exception_handler = :raise_all_exceptions

A Recipe for Protecting Your Rails App Secret

I’ve spent some time over the last few weeks upgrading 5 Blocks Out to Rails 2.2.2. One of the things I’ve been pondering how to integrate is the new protect_from_forgery feature which aims to deter cross-site request forgery attacks.

By default, Rails 2.x creates a random forgery protection secret string when generating a new app, and hard-codes the secret into  environment.rb. As with database passwords, this isn’t the sort of thing you want in your source code repository, especially if your code will be open source, or exposed in some other way to a lot of people over time. So, what to do?

I found two useful ideas on how to deal with this. Both rely on storying the secret in a file distinct from environment.rb. You store this file on your web server, and not in your source repository. This way, your secret key is as secure as your app server.

Here’s a scrap of code I cooked up to do this: http://pastie.org/369075.  It looks for a file named config/session_secret.txt and tries to load the key directly from the text in the file. When running in environments such as production or staging it raises an error if it can’t find the file. When running in dev or test environments it silently falls back to using a hard-coded key. Since I use Capistrano to deploy, I’ve added an after-deploy task that links config/session_secret.txt to a central copy of the file.

This is simple and I think it should work pretty well for me. I hope someone else finds it helpful.

Follow

Get every new post delivered to your Inbox.

Join 401 other followers

%d bloggers like this: