Prevent Rails 4 Sprocket Asset Pipeline from caching a specific file - ruby-on-rails

I have a file globals.css.scss.erb which only contains:
<%= Styles.output %>
Styles is a custom module to output formatted SCSS/SASS global variables based on a YAML file.
My issue is that when I change the YAML file, globals.css does not get updated (i.e., it's cached by Sprockets).
I want to disable caching on globals.css, not all of my assets. Is this possible? This only has to work for my development environment.
Thanks,
Erik
P.S., There is this post which does not solve the issue.

As I understood, you need always to recompile .erb assets. Here is the solution:
Sprockets::Asset.class_eval do
alias_method :orig_dependency_fresh?, :dependency_fresh?
def dependency_fresh?(environment, dep)
if dep.pathname.extname.eql? '.erb'
false
else
orig_dependency_fresh?(environment, dep)
end
end
end

Related

Generating list or paths of stylesheets and javascript files in Rails 4

I'm writing a Rails4 app that uses a custom cache manifest file which needs to include references to all the required Javascript and CSS files. Due to the nature of the application, the Rack Offline gem can't be used.
The stylesheet_link_tag and javascript_include_tag calls produce the correct list of files (as generated by the asset pipeline) but embed them in HTML tags.
Is there a way to get the paths to all the compiled javascript and stylesheet files in the controller?
eg.
/assets/custom.css?body=1
/assets/incidents.css?body=1
/assets/users.css?body=1
/assets/application.css?body=
/assets/jquery.js?body=1
/assets/bootstrap/affix.js?body=1
...
That one was fun! Had to go into the Sprockets source to figure it out.
asset_list = Rails.application.assets.each_logical_path(*Rails.application.config.assets).to_a
You can then go in a grep through the asset list, something like:
asset_list.grep(/\.(js|css)/)
EDIT:
If you want the hex digests, you could do something like:
environment = Rails.application.assets
asset_list = environment.each_logical_path(*Rails.application.config.assets).to_a
asset_list.map! { |asset| environment.find_asset(asset).digest_path rescue nil }.compact
Based on #kdeisz research, this code worked in the controller for the manifest file:
#assets = Rails.application.assets.each_logical_path(*Rails.application.config.assets).to_a
#assets = #assets.map{ |p| view_context.compute_asset_path(p) }
render 'manifest.text', content_type: 'text/cache-manifest'
The compute_asset_path function is needed to get the actual asset path.
Note: I haven't yet tested this in production mode. It works in development mode if you set config.assets.debug = false

How do you tell Rails / the asset pipeline that a js.erb depends on a YAML file?

I have a js.erb file that loads YAML from a config file. The problem is that Rails / the asset pipeline will cache the results and never invalidate that cache, even when I change the YAML file contents. I can restart the rails server and even reboot the machine to no avail. The only workaround I've found so far is doing a "rake assets:clean".
I would like to find a way to tell the asset pipeline that when the YAML file changes, it needs to re-compute my js.erb. Or, alternatively, tell it it can only cache the js.erb for the lifetime of the rails server / ensure somehow that re-generation occurs every time the rails server comes up or is restarted.
Any suggestions would be greatly appreciated.
Add this into a file under config/initializers and it will tell the asset pipeline to re-compute the js.erb file that loads the YAML data whenever one of the backing YAML files changes:
class ConstantsPreprocessor < Sprockets::Processor
CONSTANTS_ASSET = "support/constants"
def evaluate(context, locals)
if (context.logical_path == CONSTANTS_ASSET)
Constants.load_path.each do |dir|
dir.each do |yml|
next unless yml.end_with?".yml"
context.depend_on("#{dir.path}/#{yml}")
end
end
end
data
end
end
Rails.application.assets.register_preprocessor(
'application/javascript',
ConstantsPreprocessor)
If you're using Sprockets 3 (with Rails 5, for example), you can use // depends_on. For example, my-constants.js.erb:
//= depend_on my_constants.yml
angular
.module('services.myConstants', [])
.factory('myConstants', [
function() {
return <%= YAML::load_file(Rails.root.join('config/shared/my_constants.yml')).to_json %>;
}
]);
Just make sure the directory containing my_constants.yml is included in asset paths in application.rb:
config.assets.paths.unshift Rails.root.join('config', 'shared').to_s
I think you have 2 options:
Disable the asset pipeline and let Rails do the compilation on the go (bad for performance)
Create a daemon process, separated from Rails (look for Ruby Daemon) to look for any changes in that specific file and recompile the assets.
3 (extra!). Remove the js-YAML dependency and read the content of the YAML from a AJAX call to the app. The scenario is: the JS make a AJAX call, the controller read the YAML file and return the content of it to the JS file. So no need to recompile or watch for changes in the YAML file.
if you choose the 3, don't read the YAML in the controller, create a utility class to do it and let the controller ask that class to read the file and pass it's content.
You can add your own processor directive that works on files outside of the assets directory. = depend_on only works with asset files (https://github.com/rails/sprockets#depend_on)
In config/initializers/sprockets.rb:
Sprockets::DirectiveProcessor.class_eval do
def process_depend_on_project_file_directive(file)
path = Rails.root.join(file).to_s
if File.exists?(path)
deps = Set.new
deps << #environment.build_file_digest_uri(path)
#dependencies.merge(deps)
end
end
end
Usage:
//= depend_on_project_file "config/setting.yml"
See this comment on github for details: https://github.com/rails/sprockets/issues/500#issuecomment-491043517

Include Assets Only If They Exist

In our current rails app, we are following certain patterns for including assets such as scripts and stylesheets.
For instance, one such pattern is (code inside the layout):
= stylesheet_link_tag controller.controller_name
The problem here is that not all of the controllers are going to have associated stylesheets. What is the best way to check if an asset exists? Specifically, I know there is some trickery here due to the cache busting asset names.
Finally figured this one out. Asset existence can be checked as follows:
YourApp::Application.assets.find_asset("#{asset}.css").nil?
The answer would then be:
= stylesheet_link_tag controller.controller_name if YourApp::Application.assets.find_asset("#{controller.controller_name}.css")
I had found this answer before and we'd been happily using find_asset in our application (Rails 3.2.16), until one day it started bombing out.
After some digging, it turns out that even in a production environment, with asset precompilation enabled, the first call to find_asset will attempt to actually compile the asset it's looking for, even if that asset has been precompiled already. As you can imagine, this is not good news -- in our specific case, we were pulling in a Compass library file into a stylesheet, which worked in dev and during precompile, but not in production, where Compass was not in the assets load path.
Long story short, find_asset is not a bulletproof way to determine if an asset is available to include. You can read a bunch more about it in the issue someone tried to file about this, and which was subsequently closed as not a bug: https://github.com/sstephenson/sprockets/issues/411
The real way to determine if an asset exists, and which works in both compile and precompile modes is demonstrated in the hoops that the filer of the above issues needed to jump through. Here's the diff for his fix: https://github.com/fphilipe/premailer-rails/pull/55/files
I'm putting this here in hopes that other Googlers who find this don't fall into the same trap I did!
To include an asset based on controller name
<% controller_asset = controller.controller_name %>
<%= stylesheet_link_tag controller_asset if YourApp::Application.assets.find_asset(controller_asset) %>
To include an asset based on controller name and action name (was useful for me)
<% action_asset = "#{controller.controller_name}/#{controller.action_name}" %>
<%= stylesheet_link_tag action_asset if YourApp::Application.assets.find_asset(action_asset) %>
And of course it'd be better not to leave this code as it is, but rather place it in a helper.
= stylesheet_link_tag controller.controller_name if File.exists?(File.join(Rails.public_path, 'assets', "#{controller.controller_name}.css"))
Simple ViewHelpers
This is what I use myself. Add this to your ApplicationHelper:
module ApplicationHelper
def controller_stylesheet(opts = { media: :all })
if Rails.application.assets.find_asset("#{params[:controller]}.css")
stylesheet_link_tag(params[:controller], opts)
end
end
def controller_javascript(opts = {})
if Rails.application.assets.find_asset("#{params[:controller]}.js")
javascript_include_tag(params[:controller], opts)
end
end
end
and use them like this in your application.html.haml:
= controller_stylesheet
= controller_javascript
Note: This works with all .js, .coffee, .css, .scss even though it just says .css and .js

Always preprocess a specific Javascript file with Rail 3.1 asset pipeline

Is there a way to always run the ERB preprocessor on a Javascript file?
I'm using Mustache to use the same templates on the client and server. I'd like to include these templates in my application.js files so they're available on the client. So I'm preprocessing my Javascript file (templates.js.erb, which then gets required in application.js) with erb:
App.templates.productShow = <%= MustacheController.read("product/show").to_json %>;
This works great but when I edit the "product/show.html.mustache" template I need to also edit "templates.js.erb" so Rails knows to recompile this file which then picks up the latest changes from the mustache template.
There's no issue running this in production since the assets get compiled when I deploy, but it's annoying when developing. Ideally I could set the preprocessor to run on "templates.js.erb" every time I reload. My current solution is to inline the Javascript in the application layout but it would be nice to keep it separate.
I ended up writing a guardfile for this that adds a timestamp to the end of the file. Just touching the file is enough for sprockets to recompile but if you're using git you need to actually alter the file. Otherwise anyone else who pulls in the code won't get the latest preprocessed files. Here's the code...
#!/usr/bin/ruby
guard 'mustache' do
watch(%r{app/templates/.+\.mustache})
end
require 'guard/guard'
module ::Guard
class Mustache < ::Guard::Guard
def run_on_change(paths)
# file to be updated when any mustache template is updated
file_name = "./app/assets/javascripts/templates.js.erb"
# get array of lines in file
lines = File.readlines(file_name)
# if last line is a comment delete
lines.delete_at(-1) if lines[-1].match /\/\//
# add timestamp
lines << "// #{Time.now}"
# rewrite file
File.open(file_name, "w") do |f|
lines.each{|line| f.puts(line)}
end
end
end
end
This seems to be a common complaint with the pipeline - that you have to touch a file that references variables for those changes to be reflected in development mode. A few people have asked similar questions, and I do not think there is a way around it.
Forcing Sprockets to recompile for every single request is not really a viable solution because it takes so long to do the compilation.
Maybe you could set up guard to watch your mustache directory and recompile templates.js.erb when you make changes to it. Similar to how guard-bundler watches your Gemfile and rebundles on change.

Asset Pipeline Cacheing CSS?

I am working on a Rails 3.1 app. I have created an application.css.scss.erb file. The .erb is in the end because I want to load a variable from the config file as the color variable in the css:
$highlight1: #<%= COLOR.highlight1 %>;
$highlight2: #<%= COLOR.highlight2 %>;
Everything works fine, but the problem I am having is that whenever I change a value inside COLOR.highlight1, it doesn't reflect the change until I go in to my css file and change something (i usually add some spaces and save it). Thats when I see the change. Clearly rails is looking to see if the file was changed in order to update the change.
Is there any way that at least during development, this can be turned off and I can see the changes without having to also modify the css file?
Any critique/opinions on my technique are also welcome
The Sprockets depend_on directive is used to declare these kinds of dependencies. So at the top of your css.scss.erb file, with the other directives (require and friends), put something like:
//= depend_on "/path/to/colors.rb"
Then when the file /path/to/colors.rb changes, it will force the css to update too.
Unfortunately, I have never gotten this to work with a relative path to a file outside of one of the asset directories (javascripts/stylesheets/images) so there may be something in the way Sprockets resolves paths that prevents this, or else I'm missing something. That leaves you with the options of specifying an absolute path, which will almost certainly not work in across all your app environments, or putting the constants file into your asset directories (app/assets/stylesheets/colors.rb, for example).
For reference, here's the doc for the depend_on directive from the Sprockets (2.0.3) source, in sprockets/directive_processor.rb
# Allows you to state a dependency on a file without
# including it.
#
# This is used for caching purposes. Any changes made to
# the dependency file will invalidate the cache of the
# source file.
#
# This is useful if you are using ERB and File.read to pull
# in contents from another file.
#
# //= depend_on "foo.png"
#
If anyone does know a way to specify relative paths to other places like config/initializers or something, please let me know!
In addition to David Faber's answer. I needed to use relative paths too.
I wanted to generate a js file with the locale dictionary, which would update if the locale files were changed:
//= depend_on "../../../config/locales/en.yml"
//= depend_on "../../../config/locales/ja.yml"
var locales = <%= locales.to_json %>;
Turns out that currently (Rails 3.2.3) relative paths only work if the relative path is also in the assets path!
So the ugly solution is to add the path in config/application.rb:
config.assets.paths.unshift Rails.root.join("config", "locales").to_s
http://guides.rubyonrails.org/configuring.html
config.assets.compile is a boolean that can be used to turn on live Sprockets compilation in production.
might want to try that, I'm not sure if its getting compiled real time though, at least it should disable the caching.
maybe try:
config.assets.digest = true
in your development config file
I try this, it work
in application.rb
config.autoload_paths += %W(#{config.root}/lib/assets_variables)
config.assets.paths << File.join(Rails.root, 'lib', 'assets_variables')
in lib/assets_variables/color.rb
module Color
def self.default
'blue'
end
end
in app/assets/stylesheets/color.css.scss.erb
//= depend_on "color.rb"
$default_color: <%= Color::default %>;
.content {
color: $default_color;
}

Resources