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
Related
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
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
I have a rails app that performs fine in development but as soon I as I push to production it breaks. It was working fine until I added a /gifs subdirectory to the /images directory.
I'm pulling a random gif in the directory and displaying it.
<%= image_tag "assets/gifs/#{rand(10)}.gif" %>
I've also tried
<%= image_tag "gifs/#{rand(10)}.gif" %>
and
<%= asset_url("gifs/#{rand(10)}.gif") %>
But nothing is working. How should I structure this?
Have you tried /assets? Like this:
<%= image_tag "/assets/gifs/#{rand(10)}.gif" %>
I would start without the interpolation just to make sure you can see the image and then add the variable.
EDIT: Sorry for the quick shot, I have it like this in mine and thought that might fix it. I think what you actually need is to tell your config/environments/production.rb that you want to precompile those folders also... in development, it will happily include everything for you, but in production, it needs to be more selective and rigid (there are various explanations for that elsewhere).
Try this:
open config/
look for this line: config.assets.precompile and add your subfolder to it.
config.assets.precompile += [ 'gifs/*.gif']
If you decided to organize your other assets into controller-related subfolders (I like that), you need to add those here too: (example: local, resources, projects, shared subfolders)
config.assets.precompile += [ 'local/*.css', 'resources/*.js', 'projects/*.js', 'shared/*.js', 'ui/*.js', 'admin.a pp.js', 'jquery.tablesorter.min.js', 'date-picker.js', 'date_extras.js', 'jquery.tablesorter.min.js']
Hope this helps!
I'm using Rails 3.2.8. When the app is deployed access the view that is including a javascript:
<%= javascript_include_tag "epiceditor" %>
Heroku fails with this log:
ActionView::Template::Error (/app/app/assets/javascripts/epiceditor.js.erb has already been required
I've checked some possible solutions, like checking for any reference that may trigger a circular dependency, or simply removing it in case it is being included somewhere else, which isn't. So, if I include it, I get this "has already been included error", if I don't , then the file isn't included at all.
My config/application.rg has this
config.assets.initialize_on_precompile = false
And applications.js has this:
//= require jquery
//= require jquery_ujs
//= require tabs
It might be important to note that the file the tag is referencing is "epiceditor.js.erb", since it has some embedded Rails code that I needed.
Thanks for your help
EDIT:
I believe this is a bug in Sprockets. If I update Rails to 3.2.9rc2, the error is now this:
ActionView::Template::Error (Asset logical path has no extension: epiceditor/.js
but of course the extension in epiceditor is epiceditor.js.erb, and I've tried being explicit about it in the javascript_include_tag as well.
I found the bug.
It turns out that inside the .js.erb file I'm calling
<% asset_path 'epiceditor/' %>
which should expand to the path where all the epiceditor file are placed, but instead is actually loading the file itself in recursive manner. This is expanding properly in the development environment but not in the production environment. Funny, right?
The reason for this is that is adding a digest. So I fixed the whole issue with this:
<%= asset_path 'epiceditor/', :digest => false %>
and now it does expand to the directory, and doesn't fall into the recursion trap.
Hope this saves some time for someone!
There are several ways to include controller-specific assets in Rails:
One option which is not DRY is to add = yield :head in the layout and content_for(:head) { ... } in every top-level view. If an asset is controller-specific, it should be specified only once per controller, not in each view. Of course, this approach is awesome for view-specific assets.
A second option which is not declarative is to add an asset corresponding to the controller name if it exists. Instead of checking whether something exists, I should simply say (where appropriate) that it exists and must be included. Also, I'm not sure if the response would be cached to avoid a runtime performance hit. On the positive side, this approach wouldn't require any changes to views or controllers, but it might open up the possibility for name clashes, especially with legacy models.
A third option is to include all assets of a type in a single file. Browsers shouldn't download assets they don't need, and this would make it more difficult to debug the application. This option would be fine if the total asset size is still manageable.
Is there some way to declaratively include a single controller-specific asset in a separate file in a DRY way without breaking the MVC model using very little code?
Rails will serve only the code in the controller specific asset files to the specified controller if you use the following include commands in your application layout:
<%= javascript_include_tag params[:controller] %>
<%= stylesheet_link_tag params[:controller] %>
I suspect if you do this you will need to also do the following:
Still include the <%= javascript_include_tag :application %> and <%= stylesheet_link_tag :application %> to get all your cross controller assets
Check how the require_tree . directives work to ensure that the controller specific assets are not being loaded both by the application.css and the <%= stylesheet_link_tag params[:controller] %> in fact you may need to remove the require_tree . and load any cross controller sheets directly into the application files
See the Rails Guide on the Asset Pipeline in section 2 for more information.