noizZze

Rails 3 Asset Pipeline on Heroku When Using Mongoid

Mongoid is an excellent piece of work and makes MongoDB a first-class citizen in the Rails world, but in its present state it has one nasty trait – it attempts the connection to DB even when you do something as innocent as precompiling your assets. Here’s what you’ll see if you try to run bundle exec rake assets:precompile with your MongoDB being shut down:

Failed to connect to a master node at localhost:27017

I don’t want to delve into explanations of how Railties initialisation works and why connection is attempted here, but the fact is the fact. How does it affect Heroku deployments? Directly.

There are three options for assets when you deploy to Heroku:

  • Compile them on your side and add to the version tracker
  • Let Heroku precompile the assets in your “slug” upon the deployment
  • Let Heroku compile your assets at runtime

The flaws of the first and the third options should be obvious. In the first case, you just have many versions of the same stuff in your repo and the commits are polluted with unnecessary buzz. In the last case, you don’t have optimal performance since Heroku has to parse and package your assets on client requests.

If you are reading this, odds are you are seeing the failed connection attempt error above in your Heroku deployment logs. The story with it is exactly the same – they don’t have MongoDB running for you just to build assets. How do we fix this?

Basically, what we need is overwriting the ::Mongoid.load! method to do nothing, like this:

1
2
3
4
5
module ::Mongoid
  def load!(config_file)
    puts "Skipping connection to Mongo DB"
  end
end

Where do you put this business depends on the version of Rails you are patching. Rails 3.1.0 and 3.1.1 are quite different in this respect. The idea is to override the Rake task assets:precompile (Rails 3.1.0) or assets:environment (Rails 3.1.1) with a piece that patches Mongoid and only then pass control to the original Rake task.

Rails 3.1.1 version:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pt = Rake::Task['assets:environment']
Rake.application.send(:eval, "@tasks.delete('assets:environment')")

namespace :assets do
  task :environment do
    module ::Mongoid
      def load!(config_file)
        puts "Skipping connection to Mongo DB"
      end
    end

    pt.execute
  end
end

Rails 3.1.0 version:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pt = Rake::Task['assets:precompile']
Rake.application.send(:eval, "@tasks.delete('assets:precompile')")

namespace :assets do
  task :precompile do
    module ::Mongoid
      def load!(config_file)
        puts "Skipping connection to Mongo DB"
      end
    end

    pt.execute
  end
end

Put the correct one into your lib/tasks/assets.rake file and you should be alright.

Bonus tip: If you don’t see Heroku compile your assets it’s either:

  • You don’t run your Rails 3 app on the Cedar stack (action: create new Heroku app, no migration)
  • You have an error in your sources (action: run assets:precompile Rake task locally and see what happens)

Comments