Face it, you are a top developer, one of the best at your game, you eat lambda’s for breakfast, refactor whole apps as an afternoon snack and bad code just trembles when you approach. You are at the top of your game and nothing stands in between you and the code you desire. Yet, the Rails asset pipeline still trips you up, not once, not twice, but just about every time you try and use it.
Here’s a list of things that I have learnt that aren’t necessarily obvious, that can help you tame this wild and savage beast.
1. Two Options, Precompile OR Include, not Both
No CSS or JS files will be available to your app through the asset pipeline unless they are included in other files OR listed in the config.precompile
directive (see #2 and #3 below).
Only application.css and application.js are available by default of all the CSS and JS files.
For example, you make a file in the assets folder called:
> app/assets/stylesheets/site.css.scss
And you want to include this in your page. You have two options, either include it as one of the CSS files to be loaded up in application.css
with the following:
// = require_self
// = require ‘site’
And then it will be inside the compiled public/assets/application.css
which is included with:
stylesheet_link_tag “application”
OR you can add it to the precompile list like so:
config.assets.precompile += %w( site.css )
and then reference it with:
stylesheet_link_tag “site”
The reason this happens is that when you run
rake assets:precompile
Rails looks through your assets folder and copies over everything that is not Javascript or CSS intopublic/assets
. It then createsapplication.js
by readingapp\assets\javascripts\application.js
, andapplication.css
by readingapp\assets\stylesheets\application.css
, loading up all the “require” files it finds in there, and does not look at any other file unless you explicitly tell it to.
2. File Extensions Matter in the Precompile Directive
Another common mistake is forgetting to put the file extension on the precompile directive, for example:
config.assets.precompile += %w( site.css )
Will do what you expect, make public/assets/site.css
available on compile, but
config.assets.precompile += %w( site )
Will fail during the compilation phase as the site
file doesn’t exist.
3. The Asset Pipeline is Not quite your Assets Folder
Every file that is not a Javascript file or CSS file that is in the app/assets
folder will be copied by Rails into the public/assets
folder when you compile your assets.
This is a source of much confusion, because note that this does NOT apply to Javascript or CSS.
So if you want to add some web fonts, you could make an app/assets/fonts/
folder and put your fonts in there, these will then be copied to public/assets/fonts
folder when you compile your assets. Note that your app/assets/stylesheets/fonts.css.scss
file that references those fonts will NOT be copied over unless you either added it to the config.assets.precompile
directive or required it from your application.css
file as per #1 above.
4. Get ‘/assets’ does not resolve to ‘/app/assets’ for stylesheets and javascript
This is a common mistake I see, people put a stylesheet into the app/assets/stylesheets
folder or a javascript file into app/assets/javascripts
folder and then reference it in development from their layout file and it all works, but when they deploy to production, everything blows up.
This is because, as per #1 above, these files won’t get copied over into the public/assets
folder unless you explicitly tell them to. Common gotcha. There is no hard and fast rule here, except having a staging environment and following #5 below.
5. Don’t Fall Back in Staging or Production
First of all, you need to understand what the directive config.assets.compile
does.
If it is set to “true” (which it is by default in development) then Rails will try to find a Javascript or CSS file by first looking in the public/assets
directory and if it can’t find it, will hunt through your app/assets
folder looking for the file. If it finds it in app/assets
it will go ahead and compile on the fly and then serve this asset up.
The problem with this is that you don’t notice it happening in development, then you commit everything and push to production and BOOM, everything is broken with 500 errors because production has config.assets.compile
set to “false”.
Why don’t you just have this set to “true” in every environment? Well, because it is sloooooow. And you don’t want slow in production.
One of the first things I do on an app is make sure my config/environments/production.rb
and config/environments/staging.rb
have the following set:
Don’t Fallback to Assets Pipeline if a Precompiled Asset is Missed
config.assets.compile = false
This prevents the app from “falling back” and trying to load the file directly instead of using the asset pipeline. This might mean that you get failures, but it is better to fail fast than to find our later that something has been broken for months.
It also means you know if your assets and site works in staging, then it is going to work in production, this is a good thing.
Note, Heroku for quite a while will override this setting to true
automatically if your asset compilation fails, I believe this creates more confusion than benefit and I believe they deprecated this as of the 20th of September 2013.
6. Deploying with Precompile Can Suck
If you use the I18n gem or need to load up your environment with asset precompilation it means your deploy process will take longer. This can suck and lead to various problems, missing databases and all sorts.
One place where this can be a pain is on Heroku. During the app deploy process, the database has not been configured which means that loading the environment during assets precompilation just doesn’t work.
Two possible solutions to this problem are to either precompile locally and commit the assets folder before you push, or use Heroku’s labs feature which may or may not exist in the future.
To precompile locally, you need to do the following:
- Setup a dummy production database on your local machine
- Create a production entry in database.yml
- Add in any other dummy settings to load a production environment
- Run
RAILS_ENV=production rake assets:clean assets:precompile
- Wait
- Wait some more
- Commit the results in the
public/assets
folder - Deploy this to Heroku
To use the Heroku labs precompile feature, follow their guide page Note: I don’t mean to harp on about Heroku (I’ve mentioned it twice so far) but their platform means you need to change the way you operate a little bit, other platforms might also have problems as well :)
7. Semicolons Matter
Another thing that can catch you out is that various minify tools for Javascript might miss required semicolons, we had an interesting problem in one application where a javascript file didn’t have a trailing semicolon on the last function block. When the files were all munged together to form application.js
we got Javascript syntax errors because we were missing a semicolon.
So just put them in there, they are good for you.
8. Clean Up After Yourself
If you are trying to debug an asset pipeline problem, try running rake assets:clean assets:precompile
first. This removes all old assets and rebuilds them from scratch, meaning you get a clean new set to play with.
9. It’s all Relative, Mostly
In the Stylesheets and Javascript files you can use require
, require_tree
and require_directory
to reference other files to include. The thing to remember is that all of these reference different things.
require
will look for any file that has the same name that is in in your assets load path. Don’t know what your assets load path is? Well, fire up Rails console in the environment of your choice and run: Rails.application.config.assets.paths
.
require_tree
will look for any relative directory path starting from current directory (usually app/assets
folder) and then load every file under it recursively, so: require_tree './my/directory'
in the application.js
file will attempt to load every file in the app/assets/javascripts/my/directory
as well as every file in every directory nested within app/assets/javascripts/my/directory
. Also, something like require_tree '../../../something'
will do crazy things, just don’t do that :) If you find yourself putting lots of require_tree
declarations, consider instead adding those directories to the assets.paths
config settings like so:
config.assets.paths « File.join(Rails.root, ‘/my/special/path’)
require_directory
will look only for a directory name starting at the current directory. So require_directory "."
will load every file in the current directory in a non recursive manner. require_directory 'special'
will load every file in the app/assets/special
directory, but will not look at or load any files in the directories nested within the special
directory.
Obviously this will assume a file extension that matches the type of file you are loading - “js” for javascript and “css” for stylesheets.
10. Don’t Require Self
A common thing I see in application.js
and application.css
is the require_self
directive. This special command tells the rails app to include any Javascript or CSS that is in the application.js
or application.css
file, which is bad form. These files should function as an index of the assets below, and should be kept free of clutter. Just move that stuff into another file and require it using the require commands listed in #9 above.
11. Use the Helpers!
Another gotcha is that you should no longer do things like:
<img src="assets/my-image.jpg" />
Because doing this will serve up the image but not append the special cache-busting timestamp that Rails appends as part of it’s assets precompile. Instead use the image_url
and image_path
helpers to make sure you get the right URLs.
12. Read!
The last tip is to go and read the excellent Rails Asset Pipeline Guide which does contain all of the above, in parts :)
Footnote
If you find anything in the above that is incorrect or have a suggested tip to include, please email me and I’ll get it fixed up :)