Sprockets cache not invalidated by file change - ruby-on-rails

I'm running a test Ruby-on-Rails app using Webrick in a test environment. The automated end-to-end test accesses an admin page which causes a JavaScript file to be updated which is used by another admin page. The problem is that the second admin page does not see the update, but instead gets the old copy of the JavaScript file. I can see the changed file on the file system, but even if use curl from the command line, I still get the old version of the file. The test used to work (at least with Rails 4.0, if not 4.1). It is just now that I am trying to update to Rails 4.2 that this problem has arisen.
Is there something I can do to to tell Rails/Sprockets to forget its old cached copy of the JavaScript that was updated? I know when I am updating it, and wouldn't mind even resetting Sprocket's entire cache if I couldn't do it selectively. What I can't do is restart the server each time a JavaScript file gets updated.

I tried many approaches to making Sprockets "forget" its cached copy, but it seemed very determined to remember, and I began to have the sense that I was fighting against a firmly entrenched design decision. In the end I decided that, just for my generated-on-the-fly JavaScript files, I would avoid sprockets altogether, which meant handling the compiling and fingerprinting of the files myself, and writing my own version of javascript_include_tag just for those generated files.
For reference, the compiling and fingerprinting is actually fairly easy:
require "uglifier"
require "digest"
minified_content = Uglifier.compile(File.read(my_generated_js_file))
fingerprint = Digest::MD5.hexdigest(minified_content)
fingerprinted_file = File.basename(basename, '.js') + '-' + fingerprint + '.js'
(and then just write out the fingerprinted file to public/assets).

Related

Force reload of static assets with embedded ruby in Rails

I have a js.erb file that uses an environment variable to set a string that will occasionally change. I would like to be able to change the environment variable and immediately be able to load an updated javascript file, but since the source erb file doesn't change, Rails doesn't know it needs to be compiled again. Is there any way I can force it to recompile?
Note: this is in a dev/test environment, not a production environment with precompiled assets.
Note #2: this feature has actually been addressed by sprockets, however the most recent release is older than the PR that adds the feature. I'm looking for a temporary solution that accomplishes the same thing (see https://github.com/rails/sprockets/pull/365)
I was able to accomplish my goal with the answer from this question How to add a rails asset dependency to an environment variable with sprockets?
However, first I had to upgrade sprockets to >= 3.0.0.

Rails 3.1 .css.less caching error

I am quite new with Rails and I am having some irritating problems with caching of css files.
I have a .css.less file with imports inside it. It's the only stylesheet the app includes, so the other files get imported only once and by this unique stylesheet.
One of those imported .css.less stylesheets seems to be cached somewhere, because does not change in the browser when I change it's source.
I can only see the changes I made if I change something in the root stylesheet.
I have the server in development mode, so the caching should be off. I have also used <%= stylesheet_include_tag "style", :cache => false %>
I tried with Chrome and Firefox, with and without clearing their cache too. Always the same result, if I work only on that file the css the page receives when reloaded doesn't have the changes...
I also stopped the server and rm everything in the tmp folder of the app. No changes.
I am using Rails 3.1 with Ruby 1.9.3, with the less-bootstrap-rails gem. Both the root stylesheet and the imported one have .css.less extension.
What am I missing?
Thank you!
This is an area where I think the asset pipeline is broken, but I don't think there's a good fix.
If I remember correctly, to get changes in files you've included/required in your .css.less file, you need to change the .css.less file itself.
I had this on Rails 4.0.8, infuriating. The config changes mentioned above didn't help. Here's what seems to have fixed it for me:
Ensure NO FILES share a base name. For example, you have a reports.css.less and a reports.js.coffee? Doesn't matter if they're in the same directory or not. Rename or delete one of them. (I changed it to reports-styles.css.less).
Blow away your cache: rm -rf tmp/cache
Restart your Rails app.
This appears to be a decent fix but, since I don't know what's actually going on, this could be totally false and it's just working by coincidence now. Sorry this answer isn't more rigorous!
I've just came across the exact same problem.
I found that if you rename your *.css.less file (the one with the imports inside) to *.less, then this weird cacheing problem gets resolved.
Add this to your config/application.rb
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
See more at: Ruby on Rails Guide: Asset Pipeline

When are compiled assets being cached in rails

When I precompile my assets for a rails 3.1 app with rake assets:precompile it spits out an old cached version if nothing changes in the asset files. I can tell because my erb is making use of a constant that I was trying to change elsewhere in my app. One work around is to alter one of the css files (eg by adding a space etc) before re-precompiling but this is a pain and I would like to try and disable this caching if it is possible. Any ideas???
This is the expected behavior of the pipeline - the ERB is evaluated only once when you precompile. The value at compile time is the value you get in the file.
The caching is based on the checking the timestamp of the files. You could run Sprockets in production without precompiling (this is called live compiling), but you cannot turn off the caching because the performance would be dreadful - every single request would require Sprockets to recompile all the files.
Sorry :-(

Static asset caching on Heroku with Jammit by changing ActionController::Base#page_cache_directory

I'm attempting to use Jammit for packaging CSS and JS for a Rails app deployed on Heroku, which doesn't work out of the box due to Heroku's read only file system. Every example I've seen of how to do this recommends building all the packaged asset files in advance. Because of Heroku's Git-based deployment, this means you need to make a separate commit to your repository every time these files change, which is not an acceptable solution to me. Instead, I want to change the path that Jammit uses to write the cached packages to #{Rails.root}/tmp/assets (by changing ActionController::Base#page_cache_directory), which is writable on Heroku.
What I don't understand is how the cached files will be used without hitting the Rails stack every time, even using the default path for cached packages. Let me explain what I mean:
When you include a package using Jammit's helper, it looks something like this:
<%= include_javascripts :application %>
which generates this script tag:
<script src="/assets/application.js" type="text/javascript"></script>
When the browser requests this URL, what actually happens is that it gets routed to Jammit::Controller#package, which renders the contents of the package to the browser and then writes a cached copy to #{page_cache_directory}/assets/application.js. The idea is that this cached file is built on the first request, and subsequent requests should serve the cached file directly without hitting the Rails stack. I looked through the Jammit code and I don't see how this is supposed to happen. What prevents subsequent requests to /assets/application.js from simply routing to Jammit::Controller again and never using the cached file?
My guess is that there's a Rack middleware somewhere I'm not seeing that serves the file if it exists and forwards the request on to the controller if it doesn't. If that's the case, where is that code? And how would it work when changing ActionController::Base#page_cache_directory (effectively changing where Jammit writes cached packages)? Since #{Rails.root}/tmp is above the public document root, there's no URL that maps to that path.
Great question! I haven't set this up myself, but it's something I've been meaning to look into, so you've prompted me to do so. Here's what I would try (I'll give a shot myself soon, but you are probably going to beat me to it).
config.action_controller.page_cache_directory = "#{Rails.root}/tmp/page_cache"
Now change your config.ru to:
require ::File.expand_path('../config/environment', __FILE__)
run Rack::URLMap.new(
"/" => Your::App.new,
"/assets" => Rack::Directory.new("tmp/page_cache/assets"))
Just make sure not to have anything in public/assets, since that won't ever be picked up.
Notes:
This is for Rails 3. Not sure of the solution under Rails 2.
It looks like Rack::Directory sets cache control headers to 12 hours so Heroku will cache your assets to Varnish. Not sure if Jammit sets this in its controller, but even if it doesn't, it will be cached quite quickly.
Heroku also sets ENV['TMPDIR'] now as well, so you can use that instead of Rails.root + '/tmp' if you wish.
This might be of use, it's for a different gem but the idea is similar and I'm trying to get it working with the plain asset helpers.
http://devcenter.heroku.com/articles/using-compass
Unfortunately it seems to be quite difficult to get rails to do this without patching/rewriting the asset helpers module (which resembles coupled spaghetti).

ActiveScaffold on Heroku's read-only file system?

ActiveScaffold apparently creates public/blank.html every time the server starts, even if that file already exists (so adding it to version control doesn't help). This is causing my application to fail to boot on Heroku, since they have a read-only file system.
Can someone please tell me how to prevent this behavior or work around it so I can deploy my app with ActiveScaffold on Heroku?!
In my rush to get this working I didn't even think to analyze the init.rb file within the ActiveScaffold plugin directory. Therein contains the require statement to a file containing the logic to copy files from the plugin's "public" directory on-server-load. Commenting out that functionality fixed my problem (after ensuring that I already had those files in their intended destinations).

Resources