Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails asset pipeline - how to include asset not in asset path?

I've converted this multi skinned app of mine to make use of the assets pipeline introduced in Rails 3.1. For the most part it's been surprisingly easy and I'm in love with the preprocessing ability which allows you to use inline Ruby in your CSS/JS files.

I have run into a major problem though, which despite the power of Sprockets seems tricky to solve. My app can be run with any number of skins (or "identities" rather) which is chosen at runtime. This "identity" parameter sets up stuff like cache directory, database connection, view paths - and indeed asset paths. While all "identities" can have their own stylesheet there is also a shared one which is used across all instances. So the assets folder structure looks something like this:

In /app/assets/stylesheets/aplication.css.erb:

<% require_asset("shared.css") %>
<% require_asset("overrides.css") %>

This loads two stylesheets, and crucially it uses the configured asset paths to resolve them (that's why i use require_assets instead of the standard require and include directives, as they don't hit the resolver). It returns the first matches found and allows me to very easily override part or whole of the default styling. So

/app/assets/stylesheets/shared.css

can be overridden by putting a file with the same name in the instance assets folder

/app/assets/[identity]/stylesheets/shared.css

and if no such file exists it silently falls back to the default shared.css.

It all works brilliantly - I use the same technique for JavaScripts, images and fonts and everything gets neatly processed and packaged during precompilation. BUT. There is a type of (sideways) inheritance that I'm unable to achieve; sometimes the skin for an identity is so similar to another one that only a few dozen lines differ (e.g. identical layout but with a different color scheme) and I really want to be able to do something like this:

assets/stylesheets/application.css.erb:

<% require_asset("shared.css") %>
<% require_asset("overrides.css") %>

assets/current_identity/stylesheets/overrides.css:

<% require_asset("../../some_other_identity/stylesheets/overrides.css") %>
/* followed by the dozen or so lines that differ for this skin */
...

This FAILS because in the current context "some_other_identity" is not in the asset paths - Rails does not find the file in dev mode, and of course it's not included during precompilation either. And if I do include it in the assets path it loads the wrong overrides.css (there can be only one). So I've been experimenting with putting something like this at the top of overrides.css:

<%= File.read(Rails.root.join("app/assets/some_other_identity/stylesheets/overrides.css")) %>
/* rest of CSS */
...

And indeed that works just as expected. BUT. Because I'm now using the assets pipeline to serve all assets I can no longer reference images in the CSS with a fixed path - I have to use <%= asset_path("some_image.png") %> so that the path resolver can work its magic. This means my overrides.css is really overrides.css.erb, and of course the ERB preprocessing doesn't happen when you do File.read(). So, I'm stuck! Help! Anyone?

Edit: If I use

<%= ERB.new(File.read(Rails.root.join("app/assets/some_other_identity/stylesheets/overrides.css.erb"))).result %>

it does try to parse the ERB, but I get

undefined method `asset_path' for main:Object

which of course is due to me using asset_path("some_image.png") etc in the file I'm trying to include.

like image 245
John Schulze Avatar asked Jun 24 '12 15:06

John Schulze


People also ask

How do you Precompile an asset?

To compile your assets locally, run the assets:precompile task locally on your app. Make sure to use the production environment so that the production version of your assets are generated. A public/assets directory will be created. Inside this directory you'll find a manifest.

What does rake assets Clean do?

The clean it removes the old versions of the precompiled assets while leaving the new assets in place. Show activity on this post. rake assets:clean removes compiled assets. It is run by cap deploy:assets:clean to remove compiled assets, generally from a remote server.

What is Require_tree?

The require_tree directive tells Sprockets to recursively include all JavaScript files in the specified directory into the output. These paths must be specified relative to the manifest file.

What is assets in Ruby on rails?

The asset pipeline provides a framework to concatenate and minify or compress JavaScript and CSS assets. It also adds the ability to write these assets in other languages such as CoffeeScript, Sass and ERB. Prior to Rails 3.1 these features were added through third-party Ruby libraries such as Jammit and Sprockets.


1 Answers

Ok, after hours of searching I came upon the list of available helper methods in Sprockets - it would have saved me a lot of time had this been linked to from the Sprockets man page on GitHub (there is a link, but it points to #FIXME). From the Sprockets API docs:

  • (Object) evaluate(path, options = {})
  • Reads path and runs processors on the file.
  • This allows you to capture the result of an asset and include it directly in another.
  • <%= evaluate "bar.js" %>

Bingo! I changed my include directive to:

<%= evaluate(Rails.root.join("app/assets/some_other_identity/stylesheets/overrides.css.erb")) %>

and the CSS gets processed and the results inserted, just the way I wanted it to work.

like image 92
John Schulze Avatar answered Sep 29 '22 10:09

John Schulze