6

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.

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
John Schulze
  • 2,198
  • 3
  • 20
  • 22
  • I thought maybe adding the .erb extension to the `File.read()` CSS filename would solve it (since it's being included in a file which is also an .erb file) but it's never that simple, is it? – John Schulze Jun 24 '12 at 15:42
  • And it's not possible to use `render()` either - [see this question](http://stackoverflow.com/questions/8370399/rails-js-erb-file-cannot-find-method-render) – John Schulze Jun 24 '12 at 18:28

1 Answers1

5

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.

John Schulze
  • 2,198
  • 3
  • 20
  • 22