9

I'm building a site in Rails 3.2. Its been 3 years since I've touched Rails or Ruby, so I'm rusty on both, plus the last time I used rails is was Rails 2.3. Needless to say, please excuse any "simple" questions below.

Here are the specs

  • Multi Tennant CMS/Store Site
  • Each "Store" (aka sub-domain) can have its own look, feel, etc. through CSS customizations
    • The customizations can be performed in a UI within the app allowing the user to change basic variables of Bootstrap (i.e. @textColor, @bodyBackground, etc.)
  • I'm using the less-rails-bootstrap gem to the Twitter Bootstrap look/feel, etc.

Here are the challenges

  1. I need to be able to dynamically output the variables for the CSS into a file that gets mixed in to Bootstrap so the variables are picked up to create the final CSS
  2. When a user changes a variable for the CSS, the existing style is basically invalidated. I need the full CSS recompiled and written back out to disk, memory stream, or some other location where I can get my hands on it (remember this is using less)
  3. I need different CSS to spit out per sub-domain. Any suggestions on how to approach this?

Further complicating the matter...

...given that I essentially will have to find some way to compile the CSS on the fly, that means I have to include GEMS I typically would not in a production environment. Performance will be very important. Is there a way to isolate this? Once the CSS has been invalidated and regenerated, I could take the content and either write it out to disk or store is in some memcached/redis/etc. instance for performance.

Any comments, even if just to point me in a general direction would be appreciated.

Thanks!

Ryan Griffith
  • 1,591
  • 17
  • 41
  • you could add rules like `!important` in a dynamically added script for those things that change. Then you wouldn't have to do all this recompiling – AJcodez Dec 13 '12 at 16:28
  • Well part of the goal here is to be able to leverage the advantages of variables in CSS. Those variables get reused in the Bootstrap source files. An example of this is the variable `@purple`. This allows the user to specify what color (hue / saturation, etc) of "purple" that we want to use. This is then used within Bootstrap in multiple places. Regular CSS will not work here unfortunately. – Ryan Griffith Dec 13 '12 at 17:15
  • Ryan, I'm looking for the same answer as you are since I want to leverage the Bootstrap variables. Of course it works in development since things are compiled on the fly. I am doing something similar in that I'm saving my customers' less files in lib/assets/stylesheets/customers. Then in my application.html.erb file I'm checking to see if the site was accessed with a subdomain, then serving up the appropriate file (I'm naming the .less files after the subdomain). Did you ever get this figured out? If not, I'll add a bounty of my own points on your question so I don't dupe it. – RubyRedGrapefruit Feb 21 '13 at 21:54

1 Answers1

3

Here is the solution I finally landed on:

  • I ended up switching over to bootstrap-sass instead https://github.com/thomas-mcdonald/bootstrap-sass
  • Made the following changes to my application.rb file to ensure that the :asset group is always included despite the environment:

    if defined?(Bundler)
        # If you precompile assets before deploying to production, use this line
        # Bundler.require(*Rails.groups(:assets => %w(development test)))
        # If you want your assets lazily compiled in production, use this line
        Bundler.require(:default, :assets, Rails.env)
    end
    
  • Used the concepts provided by Manuel Meure (Thank you Manuel!) of Kraut Computing found at http://www.krautcomputing.com/blog/2012/03/27/how-to-compile-custom-sass-stylesheets-dynamically-during-runtime/ .

    • I made some adjustments to suit my own needs, but the core concepts illustrated by Manuel were the foundation for my compilation process.
  • In my model (lets call it "Site"), I have a snippet of code that looks like this:

    # .../app/models/site.rb
    ...
    
    BASE_STYLE = "
      @import \"compass/css3\";
    
      <ADDITIONAL_STYLES>
    
      @import \"bootstrap\";
      @import \"bootstrap-responsive\";
    ".freeze
    
    # Provides the SASS/CSS content that would 
    # be included into your base SASS content before compilation
    def sass_content
      "
      $bodyBackground: #{self.body_background};
      $textColor: #{self.text_color};
      " + self.css # Any additional CSS/SASS you would want to add
    end
    
    def compile_css(test_only = false, force_recompile = false)
    
      # SassCompiler is a modification of the information made available at the Kraut Computing link
      compiler = SassCompiler.new("#{self.id}/site.css", {:syntax => :scss, :output_dir => Rails.root.join('app', 'assets', 'sites')})
    
      # Bail if we're already compiled and we're not forcing recompile
      return if compiler.compiled? && !force_recompile && !test_only
    
      # The block here yields the content that will be rendered
      compiler.compile(test_only) {
        # take our base styles, slap in there some vars that we make available to be customized by the user
        # and then finally add in our css/scss that the user updated... concat those and use it as
        # our raw sass to compile
        BASE_STYLE.gsub(/<ADDITIONAL_STYLES>/, self.sass_content)
      }
    end
    

I hope this helps. I know its a deviation from the original post, but its deviated because this seemed to be the most attainable solution to the problem.

If I haven't answered a specific question you have, feel free to comment so I can expand where possible.

Thanks!

Ryan Griffith
  • 1,591
  • 17
  • 41
  • I'm curious if you knew a way for me to deploy this to Heroku since they insist that all deployments have to be `initialize_on_precompile = false` – Trip Mar 19 '14 at 18:25
  • @Trip, wish I had a good answer for you on that one. Its been a little bit since I've been in Rails land and cannot recall if I had come across this piece - Sorry. if you feel like you can expand on this, feel free to edit my answer. – Ryan Griffith Mar 24 '14 at 17:51
  • 1
    Ah so I did find an answer, and it is possible to deploy with it set to true, you simply need to use Heroku's default buildpack.. Which should technically be running by default. My mistake was two years ago, the same app had a custom one built that a few developers were tinkering on -- and I had failed to update it. – Trip Mar 24 '14 at 17:56
  • 1
    Hi @beydogan, according to the link above used for the basis of this (from Kraut Computing), this does NOT work in Rails 4. Here is an excerpt from one of the comments on that page: _"This method does not work in Rails 4 since Sprockets::StaticCompiler is no longer part of rails...."_ I would suggest giving the comments of that page a read as there is some discussion regarding potential Rails 4 workarounds. – Ryan Griffith Dec 19 '15 at 16:39