Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to universally skip database touches when precompiling assets on Heroku

I'm deploying a Rails 3.1 app to Heroku's Cedar stack. With Heroku Cedar and Rails 3.1, you can compile the assets yourself locally, let Heroku compile them when you push (during "slug compilation"), or have them be compiled just-in-time while the app is running. I want to do the middle option, letting Heroku precompile the assets.

When Heroku runs the assets:precompile task, it errors with "could not connect to server" because the app is trying to connect to the database but no database is available at that stage of slug compilation. The lack of database connection is expected and unavoidable at this point. I'm looking for a way to move past it, because a database connection isn't crucial to asset precompilation.

The part of my app that's trying to connect to the database is Devise. There's a devise_for :users line in routes.rb that wants to look at the User model.

I could just write a rake task that stubs out devise_for and make it a prereq of assets:precompile. I think that would solve my problem, but I'm looking for a more universal solution that I could use on any Rails 3.1 app with this problem on Heroku.

Is there anything out there, or can you conceive of anything that silences database connection errors while still running the app enough to have route and asset path generation?

Obviously if an app needs to read/write data during startup, we can't stub that, but can we fake every ActiveRecord model automatically?

like image 285
jasongarber Avatar asked Sep 15 '11 12:09

jasongarber


4 Answers

add this to config/application.rb

config.assets.initialize_on_precompile=false                                                  

took me a while to hunt this down... adding it to config/environments/*.rb did NOT work

UPDATE: It doesn't work with rails 4

like image 119
fringd Avatar answered Nov 13 '22 07:11

fringd


Heroku now makes a labs flag available that'll make the runtime environment available during compilation time, which means your app will be able to successfully connect to your DATABASE_URL database.

First you need to install the labs plugin:

$ heroku plugins:install http://github.com/heroku/heroku-labs.git

then enable the user-env-compile labs feature:

$ heroku labs:enable user-env-compile --app your-app-name
like image 33
kch Avatar answered Nov 13 '22 09:11

kch


For me the problem is activerecord calling instantiate_observer in lib/active_record/railtie.rb:92. This will load the observers and the respective models. has_and_belongs_to_many then connects to the db.

I think I'll override this method when ENV["RAILS_ASSETS_PRECOMPILE"] is present, which is used by devise in the fix Bradley linked to.

EDIT: So this snippet fixed it for me:

namespace :assets do
  # Prepend the assets:precompile_prepare task to assets:precompile.
  task :precompile => :precompile_prepare

  # This task will be called before assets:precompile to optimize the
  # compilation, i.e. to prevent any DB calls.
  task 'precompile_prepare' do
    # Without this assets:precompile will call itself again with this var set.
    # This basically speeds things up.
    ENV['RAILS_GROUPS'] = 'assets'

    # Devise uses this flag to prevent connecting to the db.
    ENV['RAILS_ASSETS_PRECOMPILE'] = 'true'

    # Prevent loading observers which will load the models which in turn may hit
    # the DB.
    module ActiveModel::Observing::ClassMethods
      def instantiate_observers; end
    end

    # Prevent route drawing because certain gems might get called which will hit
    # the DB.
    class ActionDispatch::Routing::RouteSet
      def draw; end
    end
  end
end
like image 5
fphilipe Avatar answered Nov 13 '22 07:11

fphilipe


Workaround for Rails (4.2 edge):

Add the following as /config/initializers/precompile.rb:

module Precompile

  # Public: ignore the following block during rake assets:precompile
  def self.ignore

    unless ARGV.any? { |e| e == 'assets:precompile' }
      yield
    else
      line = caller.first
      puts "Ignoring line '#{line}' during precompile"
    end

  end

end

and use it in your routes.rb like this:

Precompile.ignore { ActiveAdmin.routes(self) }
like image 4
yawn Avatar answered Nov 13 '22 08:11

yawn