Dynamic (S)CSS in rails 3.1 - ruby-on-rails

I'm trying to allow the user to customize my application using YML files.
When the user updates certain things the CSS needs to be updated as well.
I'd like to solve this problem using dynamic CSS instead. The way I was planning on doing this is to have a settings SCSS file which the other css files import and use.
Here is what I have so far:
settings.scss.erb:
$width: <%= Rails.application.config.width %>px;
main.css.scss:
//= require settings
#import "settings";
#main {
width: $width;
}
But I get this error:
Invalid CSS after "$width: ": expected expression (e.g. 1px, bold), was "<%= Rails.appli..."`
So It seems that the settings are not being passed through the erb parser before being handed off to the SCSS parser, is there any way to solve this.
I'd rather not put everything in .erb files since my text editor doesn't support (syntax highlighting and commands) when having scss in erb files

Side stepping the problem where ERB is not being parsed, you are not going to be able to customize the CSS dynamically (I think the main file may require an erb extension). The asset pipeline is designed to serve assets in a way that tells browsers that the are static and are not going to change.
Assuming the erb parsing was working, the width would be rendered during the precompile phase, or on the first request. If you are using Sprockets far-future headers are set to tell the remote clients to cache the content for 1 year. And Sprockets only picks up changes if the timestamp of the file changes, so would never get any new values.
You could force Sprockets to dynamically serve every request, and to not send any headers, but it is not really designed for this and there are significant performance risks in doing so.

Related

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.
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.

Evaluate SCSS (SASS) templates in ruby (Rails project)

I am working on a sort of CMS project where I am leveraging SCSS. I would like to allow the user to specify properties of a stylesheet in a simple way (enable a few color customization), and then generate a CSS file based on SCSS templates, and substitute some variables in the SCSS file using mustache or ERB evaluation.
Basically, I want an ERB file to be rendered as scss file, and then generate a css in my application, upload it to S3, and include in the user's layout.
If possible I would like to avoid using css.erb files :-)
I fact (I am answering my own question). What I am trying to do is really easy. I've made this really simple script:
#!/usr/bin/env ruby
#processs.rb file
require 'sass'
result = Sass.compile open(ARGV[0]).read
puts result
And it generates css out of a scss file she invoked like this:
ruby process.rb myfile.css.scss
And this works perfectly. The code documentation in sass source code helped me to find this out.

Avoid *.js.erb files by building all the asset_path values

So I want to avoid processing JavaScript files with ERB just so I can get a proper asset path to, say, an image.
Currently, this seems like the popular approach:
var myImage = "<%= asset_path('my_image') %>";
Which, of course, requires the filename be changed to "*.erb" so that it'll be processed.
I'd much rather isolate the ERB ugliness to one point in my project making a single manifest file (say, "assets.js.erb") that computes and makes available all the asset paths my JavaScript needs.
I can certainly do it WETly by tackling it case-by-case:
ASSETS =
"my_image": "<%= asset_path('my_image') %>"
window.assetPath = (path) -> ASSETS[path]
But, I'd really rather just write some ERB to recurse through all of my asset_paths.asset_environment.paths and build a big object literal manifest for me, so that my real application JavaScript can confidently call:
var myImage = assetPath('my_image');
Any ideas on (1) if there's an easier way to do this that I missed, or (2) how I'd accomplish a search of all the potential valid arguments to asset_path?.
An easier way :
Get the assets prefix in your .js.erb : <%= Rails.configuration.assets.prefix %>. If an absolute path is needed, you can also get the application URL (it's more complicated to get it from rails, so you can just hardcode it in your .js.erb ?)
If you are working with precompiled assets, get the fingerprint of your file which is stored in manifest.yml (at <%= Rails.configuration.assets.manifest %>). The manifest contains a list with all your assets and their respective fingerprints (documentation)
Make assetPath just prepending the application URL + prefix to your image name or fingerprint
An inconvenient is that you have to specify the full image name (included the extension).
Old question, but there is nice way to accomplish this. Just to explain the context of my solution: I need to display markers in a map, which have different possible icons based on the JS dynamic variables. Strangely, using the <%= asset_path('" + somefunction(raw_value) + "') %> was not working. Then, I've looked for the solution bellow.
Concretely, the solution I am using has only one js.erb file which stores the values of the images, and their fingerprinted names, which can be get by a function, image_path. After that, all my other JS files can be free of the asset_path and, consequently, of the .erb
Create a file images.js.erb in your_application/app/assets/javascripts with the following content:
<%
imgs = {}
Dir.chdir("#{Rails.root}/app/assets/images/") do
imgs = Dir["**"].inject({}) {|h,f| h.merge! f => image_path(f)}
end
%>
window.image_path = function(name) {
return <%= imgs.to_json %>[name];
};
Require this file in your application.js, which is normally in the same directory as above:
//= require ...
//= require ...
//= require images
//= require_tree .
Then, inside the JS that you've been using <%= asset_path('image.png') %>, you will use instead image_path('image.png');
Credits to this blog post for posting a Coffee script version from which I've based mine.
It depends on the context of where this image is used.
Use Case 1: The image is decorative and needs to be dynamically swapped.
Example: Spinner, while data is loading.
In this case, I refer to is in my sass and java script.
.spinner
background-image: url(image_path("spinner.png"))
Then I would operate with classes in java script and not images.
$.addClass('spinner')
Use Case 2: Image is part of an object.
There are many situations, when an image actually belongs to a object. In this case, I create a json file, which stores the data and the image reference like this. Then I use erb to unwrap the image reference - my_object.json.erb:
{
"icon" : "<%=image_path("icons/my_icon.png")%>",
"label":"My label",
"description":"My description"
}
Use case 2 requires more work on javascript side to load the json files, but it opens very powerful extensibility options.
Asset pipeline handles both cases famously.

Rails Asset Pipeline: Precompile Assets outside of manifest?

I have certain page-specific js and css files that I either:
Don't want running on development (ie tracking code)
Don't want running on every page
or both.
In Rails 3.0.x I would simply call them from the view where they are needed like so:
- if some_condition
= javascript_include_tag 'page_specific'
Now, using the asset pipeline, it looks like I must include these in the manifest (and therefore in every page for the application) or else they won't be precompiled. As I'm using Heroku for deployment allowing lazy compilation is not an option.
How can I manually precompile every file from the assets directory without having to include them all in the manifest? Is this possible?
Just don't put the page specific asset in the manifest and include them when you need it like you did in rails 3.0.x, the precompiler will compile those page specific as separate files.
Personally I just check for a certain element I know is in the dom in that page. If the element isn't found the rest of the code isn't executed.
$(function(){
if( $("#page_specific_element").length !== 1 ) return;
//page specific functions
});

How can I access Rails objects in Sass?

In a Rails 3.1.0 project, I have Companies with a few customizable attributes like background_color and link_color. I want to be able to set some Sass variables like so:
$background_color: <%= #company.background_color %>
$link_color: <%= #company.link_color
...
This doesn't work because #company is nil when Sass does its thing. I'm not sure how to go about solving this in a way that's dynamic (companies can be created and colors can be changed and the views update immediately). Any suggestions?
I can think of a couple approaches off the top of my head:
Serve your stylesheet through a controller.
Use CSS classes to configure the colors and serve just that CSS through a controller, inlined partial, or a CSS #import.
Serving your stylesheet through a controller is pretty straightforward so there's not much to say. This might be a bit ugly and cumbersome.
For the second one, you'd add a couple extra CSS classes:
.custom-bg {
background-color: some-default-bg;
}
.link-fg {
color: some-default-fg;
}
/*...*/
Then any element that need to use the custom background color would need their usual CSS classes and custom-bg; similar shenanigans would be needed for the other configurable values. To supply the customized CSS, you could inline a <style> element into your HTML using a standard ERB partial or you could serve the CSS through a controller (either through <style src="..."> or #import). So you'd fake the SASSy goodness with old school multiple CSS classes in your HTML.
There's also JavaScript. You'd need some way to identify the elements that need their colors adjusted and then adjust them directly with things like this:
$('.need-custom-background').css('background-color', '...');
I think you might be able to do something just like what you have there, but you need to change the extensions of the files to '.css.scss.erb'
To follow up on this, I did create a stylesheet controller but it was rather contrived to get Sass parsing and asset pipeline load paths all working correctly. I ended up dumping that and reorganizing the styles so I could generate a static stylesheet for each company which gets regenerated and uploaded to S3 on company update.
Well, if you mean a dynamic object like a model loaded via a controller, you can't really, at least not very easily. This is because unlike HTML ERB templates, the SASS ones are generally rendered once and served statically unless something changes in the code or they are re-precompiled via rake (depending on your environment configs). But you can access some helper methods, global objects, and use some ruby in there by renaming the file with an "erb" extension e.g. application.css.scss.erb. See
https://guides.rubyonrails.org/asset_pipeline.html#coding-links-to-assets
How can I use Ruby/Rails variables inside Sass?)
If you need styles based on dynamically loaded objects, like models, you can...
Write CSS styles literally in the template
Compile the stylesheets dynamically. See the top-rated answer here: How do I create dynamic CSS in Rails?
For some use cases you might accomplish the same thing by leveraging Rails/SASS's import path hierarchy (i.e. SASS #import 'partial_name_with_no_path' will search the importing SASS files folder first and then fall back to the top level - You can configure this as well).

Resources