Recently, I’ve been fighting with Ruby 1.9 and encoding in a Rails 3.1 app that we’re running on Heroku. The strange thing is that encoding was working fine on any local system we would try, but would break on Heroku while rendering views with the error
ActionView::Template::Error (incompatible character encoding: ASCII-8BIT and UTF-8)
There are suggestions sprinkled all around the web on how to deal with Ruby 1.9 and character encoding.1 Do a quick Google search and you see everything from “revert to Ruby 1.8 until they2 fix Ruby” to “Add ‘encoding: utf-8′ to the top of every file you’ve ever seen.” In the beginning of this all, I thought I at least understood encoding. By the end, after trying litterally everything I could find to fix the problem, I started to think it was all magic.
Here’s the deal: We’re using an RDS database plugin on Heroku, so the actual database is the same regardless of what system you’re on. Moreover, I was using the same database for production and development environments. Despite this, the app had encoding errors on Heroku and not on any local system.
We changed the LANG environment variable, the encoding settings for Rails, for Ruby. Everything. Nothing I did changed the encoding of things on Heroku. Nothing.
Many things I saw said to use the mysql2 gem. That was actually one of the first steps we took. Unfortunately, Heroku didn’t seem to support the mysql2 gem. If you had that gem anywhere in the production environment, the deployment would fail with a gem error. So, we had the mysql gem in production and mysql2 in development:
group :production do gem "mysql" ... end group :development do gem "mysql2" ... end
Despite this, in our database.yml file we were using
production: adapter: mysql2 encoding: utf8
This didn’t seem to cause any problems, no errors or warnings, so I figured Heroku must be honoring it. Wrong. I’d fire up the local console, grab something from the database, and see <#Encoding: UTF-8>, then I’d fire up the Heroku console and see <#Encoding: ASCII-8BIT> for the same object.
The clue came when I changed the adapter to just ‘mysql’ and then started the app locally in production mode. Suddenly, I saw the encoding as ASCII-8BIT on my local system too. Somehow, on the local system, despite ‘mysql’ two being set in the Gemfile, ‘mysql2′ was being used because of the database.yml file. However, the reverse was true on Heroku.
When you use the RDS plugin on Heroku, you add the URI to your account info. This is done on the Heroku site using a dialog box, which contains a very helpful bit of text describing the format of the database location as:
Now, when reading this, one would naturally assume that the ‘mysql://’ part of this URI is designating that you want to use MySQL to access this database. As opposed to, say ‘postgres://’ or something.
One however, would be quite wrong.
It turns out that the format is not as described, but rather:
Surprised? Yeah, me too. Suddenly, I can use the ‘mysql2′ gem in production if I use a database location of:
Now, I love Heroku, so I’ll give them the benefit of the doubt here. I’m sure someone thought it was very clever to use the <schema> portion of the URI to allow for changing of the gem. And sure, it is. But come on, folks, “The format is” when describing a URI is meaningful.
Interestingly, clicking on that little question mark brings you to a documentation page where ‘mysql://’ and ‘mysql2://’ seem to be used interchangeably, with no notes as to the consequences. If you were following this documentation, you’d be instructed to migrate your data using the ‘mysql’ gem, then you’d be instructed to use the database using the ‘mysql2′ gem. This would probably cause some confusion when suddenly your deployment fails telling you that your Gemfile is broken.
Moral of the story: If you’re on Heroku, and getting encoding errors that you can’t find with an external database, look well at the URI of your database and ensure that Heroku is actually using the gem you think it is. Unfortunate as hell that a URI set on their web app would break stuff you put in your code, but there you are.