Howto: Make CSS Files Act Like Rails Partials

20 Aug 2006

Gone are the days when you could just throw a few CSS definitions in something called ‘style.css’ and call it a good day’s work. With the arrival of semantic html where none of the styling is done inline it has become important to practice good coding habits while creating CSS styles.

It was embarrasingly recently that I had the habit of putting all my CSS into one file. I didn’t realize that it’s the same bad habit of making programs with only one (usually massive) file of source code. Learning to partition the code properly is a natural development step and it’s just as important for CSS as Ruby, Java, C, or any other.

How I do CSS:

What I’ve found is I have a few default CSS files named after the following pattern: + application.css: usually will overwrite bad default values (like margins on forms) + structure.css: the layout of the blocks that make up my layout (body, #wrap, #head, #main, #foot, etc.) + fonts.css: font-family, font-size, etc. for all the popular styles + forms.css: whether using label/input pairs or definition lists I need lots of styling for forms + colors.css: super important to keep all color stuff together so you can both keep the colors in harmony and replace this file with some alternate color theme

These worked for a while but then I realized that I had little pieces of code that were needed that didn’t belong in any of these. I need to specify that a certain logo floats to the right or a users profile just happens to need a different background color.

The answer to this comes in using CSS files kinda like Rails uses partials. Some encapsulized code that has usefulness in a certain portion of your app. I did this at first by making stylesheets named after each of my controllers. This worked for about three seconds until I realized that I needed some conditional way to include them.

Conditionally including CSS files

Including stylesheets conditionally turned out to be a fairly easy task. I’ll outline the solution here using extractions from my code:

    class ApplicationController < ActionController::Base

      attr_accessor :stylesheets
      before_filter {|c| c.stylesheets ||= []; c.stylesheets << c.class.name.sub('Controller','').downcase }

    end

This adds an instance var @stylesheets that starts as an array. It is given, by default the name of the currently active controller. note: this prints the derived class’s name, not ‘application’.

This means that you can now add stylesheets to the soon-to-be-rendered view anywhere in your code you want.

    class UsersController < ApplicationController
      # if you have a stylesheet that governs just one action
      def show
        stylesheets << 'users_show'
      end
    end

And it’s surprisingly simple to output the required code to the browser. Just add one line to your app/views/layouts/application.rhtml file:

    <%= stylesheet_link_tag 'application', :media => 'all' %>
    <%= stylesheet_link_tag 'structure'', :media => 'all' %>
    <%= stylesheet_link_tag 'fonts'', :media => 'all' %>
    <%= stylesheet_link_tag 'forms', :media => 'all' %>
    <%= stylesheet_link_tag 'colors', :media => 'all' %>
    <%= @stylesheets.collect { |file| stylesheet_link_tag file }.join("\n") if @stylesheets %>
    <%= javascript_include_tag :defaults %>

This is so simple that there’s no real point in turning it into a plugin but if anybody wants it that way just let me know.

Update: The version I ended up using in my app did some testing for the existence of the file before it included it as a stylesheet. I decided it was more important to avoid the 404 errors (especially the way they cruft up the logfile) than it was to have the app disk-optimized. So here’s my new application_controller:

    class ApplicationController < ActionController::Base

      attr_accessor :stylesheets
      before_filter do |c|
        c.stylesheets ||= []
        klass = c.class.name.sub('Controller','').downcase
        c.stylesheets << klass if File.exists?("#{RAILS_ROOT}/public/stylesheets/#{klass}.css")
      end

    end
  • Ben Kittrell said: That's a great idea. I wish I would have thought about it 6 months ago.
  • jason said: I get the point of partitioning, overall this is a good approach. However, would it not be wiser to have the view somehow indicate which stylesheet to include vs. the controller? In my opinion, having the controller do this breaks the MVC principle of separating your business logic (controller) from your presentation logic (view). /$.02
  • Danger said: Hmmm, good point Jason. The view allows for this in that you can create separate layout files that have different code. I wanted to avoid this 'cause I just had a single line different. How might you go about putting this stuff in the view? I'm really curious as to how else this might be accomplished.
  • Ben Kittrell said: I think that Danger was right to put it into the controller. It's not just for business logic. You specify which view to use in the controller, as well as the layout. Constructing view components in the controller would be a no no, but not specifying layout components. At least in the Rails paradigm that is.
  • Ashley Graham said: Isn't having the server access six different css files might on every page load is a bad thing? Just a thought...
  • Ashley Graham said: Isn't having the server access six different css files might on every page load is a bad thing? Just a thought...
  • Daniel Azuma said: We had actually implemented something similar (though our "common" CSS files are getting large and aren't split up as well as Danger's) in which the view makes the determination of which CSS to call for. So my views (may) have some tags at the beginning of the form: <% use\_extra\_stylesheet('my\_specific\_stylesheet') -%> (And in fact we have something similar for javascript files.) I'm convinced that the view is the right place to put these, because of the MVC argument: the view should be the only entity that knows how it implements itself, and CSS is part of the view implementation. Yes, the controller can "choose" a view, but it's only that: _choosing_ a view. Once the controller says "I want view X", view X should have the control over "I need CSS A and B". Otherwise, when the controller changes its choice of view, it also has to know which CSS the new view needs. Why change two things and risk getting them out of sync? Then again, YMMV. We have enough different kinds of pages that we need different CSS fairly frequently. If you know a priori that all views called out by a controller will always want the same CSS, then heck, it doesn't matter I suppose, though I'm enough of a purist that I'll keep it in the view anyway. BTW, is this the Danger Stevens who used to hang out with IV at UW a few years ago? Congrats on your winning tutorial on RailsForum! I just read about you this morning. Daniel Azuma
  • John said: bq. I think that Danger was right to put it into the controller. It's not just for business logic. Actually, the controller isn't for business logic at all. That belongs in the model.
  • Danger said: I don't know if you'll read this Daniel but "Ahoy!" and it's good to hear from you. The way I've implemented this I more or less name the css files after the controllers (or individual actions). This still leaves the view with complete autonomy as it can change the code in the stylesheet or replace the stylesheet altogether by swapping themes. Now, if I were to put actual CSS in the controller things would start to get sticky.
  • Christop said: I do think the same, it doesn't belong into the controller after some time of thinking about this. If you put this into the controller it seems more like the themeing approach to me, you specify eg. 3 CSS files in the controller like base, typo and forms. Which you can then override in the view. But this restricts the view totally, in a passive way imho. Because youre bound to the guidelines the controller gives you, like the themeing in many applications, youre bound to some given files and styles. You could override them completely, but it is somehow restricting if you get some initial files from the controller. So I personally think the only thing a controller should do, is push the data for a specific request to the view. Nothing more. Unfortunately I have not yet found my 'best practise' for the partial integration of CSS in the view :) Danger; I like your blog, I'm often looking for 'best practices' in rails to dry things up, like your article about blocks, keep it up :)

Please if you found this post helpful or have questions.