Search code examples
ruby-on-rails-3rakepassengersprocketscapistrano3

How to refresh Rails / Sprockets to be aware of new manifest on production server after assets:precompile


We have a use case where we need to run assets:precompile outside of the deploy/restart process, and therefore preferably without having to restart the Rails server processes. Is this possible in a Passenger environment?

I've been banging my head trying a bunch of stuff within Rake tasks and fiddling with Rails.application.config.assets stuff, but nothing makes the application pickup the changes to the digests except restarting Passenger with /usr/bin/env touch ~/project/current/tmp/restart.txt


Solution

  • We ended up going with a 2 part solution:

    Part 1 is to setup the app to hit redis to store an 'assets:version' (we just went with a timestamp). Then, whenever our process was done precompiling, we update this assets version with the latest timestamp.

    Part 2 was that we added a before_filter :check_assets_version in our main application_controller that all our other controllers inherit from. This method looks something like this:

      def check_assets_version
        @@version ||= 1
        latest_version = get_assets_version # wrapped call to redis to get latest version
        # clear assets cache if current version isn't the same as the latest version from redis
        unless latest_version.blank? || latest_version.to_s == @@version
          @@version = latest_version
          if Rails.env.production? || Rails.env.sandbox? || Rails.env.experimental?
            nondev_reset_sprockets
          else
            dev_reset_sprockets @@version
          end
        end
      end
    

    And those two reset methods look like this:

      def nondev_reset_sprockets
        manifest = YAML.load(File.read(Rails.root.join("public","assets","manifest.yml")))
        manifest.each do |key, value|
          Rails.application.config.assets.digests[key] = value
        end
      end
    

    The nondev reset "stuffs" each of the values into memory from the new generated manifest file

      def dev_reset_sprockets(version)
        environment = Rails.application.assets
        environment = environment.instance_variable_get('@environment') if environment.is_a?(Sprockets::Index)
        environment.version = version
      end
    

    The dev reset just kicks sprockets "version" value so that it thinks (correctly so) it needs to reparse and live recompile the latest assets.