Thursday, April 12, 2012

Saving your Ass-et Pipeline

Getting your Ass-ets in Gear

The Rails 3.1+ Asset Pipeline is a thing of beauty. Not only does it greatly improve the ability to cache forever these often expensive assets (javascript, css, images), but it compresses and delivers them far more efficiently.

It can also be a pain in the ass-et to ensure that the world does not come crashing down when you push your newly upgraded Rails app to production.

One of the great distinctions between previous versions of Rails and 3.1 is the high likelihood that though your development environment and test environment run without error, when it arrives in a production environment, the same code will fail.

This can be mostly blamed on pre-compilation. Ryan Bates does a great job of discussing this in a recent Railscast. He explains how to ensure that your capistrano scripts include steps to run pre-compile on your production server, or run it locally and send the results up with the rest of your code.

But there are other issues beyond getting production to pre-compile. You must also ensure that all of the javascript and css files you reference are set to be pre-compiled.

By default, Rails will pre-compile all non-CSS, non-JSS files, and all application.js and application.css files. But let's say you have something like this in your code:

- content_for :scripts do
  = javascript_include_tag 'signup_click'

Signup_click.js will not be pre-compiled.  So what, you say. Well, when a request is made to serve the signup_click.js file, you will get a nasty "signup_click.js isn't precompiled" error. And you will get this for any file that isn't named application.* and that is referred to directly by your erb/haml templates. If you are migrating up from 3.0, this can be a real gotcha.

So what to do.

Certainly, you should take some time to review your code, and look for specific references to css and js files and, if you can, refer to them in the application.* manifests. Invariably, you will miss some (I sure did).

So to catch them, I first attempted to run my application in production mode locally (Ryan Bates demonstrates how to do this). If you have a large site, that you have been building for a while (say, since 2.3.5), this could take a while to go through page by page.

Instead, or in addition really, I set up my cucumber environment to run in a simulated production environment. This required  a couple steps:
  1. Make sure that running all cucumber features in test mode returned no errors
  2. I added ENV["RAILS_ENV"] = "cucumber" to the top of my features/support/env.rb file (actually, within my Spork.prefork section - for those using Spork)
  3. I created a cucumber.rb environment file, copying everything from production, with the following changes. 
    1. config.serve_static_assets = true
    2. config.consider_all_requests_local       = true  
    3. config.action_controller.perform_caching = false
    4. Don't forget to disable mail 
    5. Make sure your config.assets.precompile matches production's
  4. I ran 'rake assets:precompile"
When I ran my tests, I got several failures,  all related to assets that were not precompiled. It was easy for me to find, and fix. In some cases, they were javascript/stylesheet includes/links that were redundant to what was in my application manifests, so I could just remove the include/link statements.

In other cases, they were controller specific css or js that I did not want to put into my main application manifests. At first, I included them in the config.assets.precompile statement in my production.rb environment file:

config.assets.precompile += %w( signup_click.js )

But after starting to load up this statement, I wanted to come up with a better, more flexible way.


Do these pants make me look fat (Ass-et reorganization)

Here is what I wanted to do:
  1. Not have to worry about adding files to the pre-compile list (because I know I will forget, and may not realize it until I get a production error notification)
  2. Be able to include controller specific stylesheets and javascript whenever I wanted. 
Knowing that anything listed underneath the app/assets folder named application.* would be precompiled, gave me the idea.

For each controller that I wanted a special file included when those pages were displayed, I added a folder named for the controller, and an application.css (or css.scss, or js, or js.coffee, or js.coffee.erb...but you get the picture) within that folder. I believe rails scaffolding does some of this for you, but I use rbates Nifty Scaffold, which does not.

So my file structure could look like this:
app
  assets
    javascript
      application.js
      blogs
        application.js
    stylesheets
      application.css
      blogs
        application.css

In my layout file (after the link/include tags for the base application files), I added the following lines:
= stylesheet_link_tag "#{controller_name}/application"
= javascript_include_tag "#{controller_name}/application"

This seemed to work great in development and test, but when I got to production, it failed whenever it encountered a <controller_name>/application.(js|css) that did not exist. What to do.

It occurred to me that I don't want these tags to be present if there is not an accompanying file. So I used the following helper methods to check whether the file exists.

def asset_exists?(filename, extension)
    asset_pathname = "#{Rails.root}/app/assets/"
    asset_file_path = "#{asset_pathname}/#{filename}".split(".")[0]
    !Dir.glob("#{asset_file_path}.#{extension}*").empty?
  end
  
  def js_asset_exists?(filename)
    asset_exists?("javascripts/#{filename}", "js") 
  end
  
  def css_asset_exists?(filename)
    asset_exists?("stylesheets/#{filename}", "css")
  end

and changed my include/link tags to the following:

= stylesheet_link_tag "#{controller_name}/application" if css_asset_exists?("#{controller_name}/application.css")    
= javascript_include_tag "#{controller_name}/application" if js_asset_exists?("#{controller_name}/application.js")

This worked like a charm. Whenever I wanted, I could add controller specific css and js by adding the folder and the appropriate file. I could include other files in their manifests, and/or directly add code.

If need be, a similar approach could be used for action specific files (I haven't needed to, but certainly could see doing so).

I am sure there are more elegant approaches, but this works well for me.

Any who, hope you found this helpful. And as my mother used to say "An ass-et in the hand, is better than two in the bush"

Thursday, April 5, 2012

Gems/Services to explore

Paper_trail - Versioning for database models
Globalization 3 - Multi-language translations of models
CopyCopter - User editing of web page text
Refinery - CMS engine

http://www.clickdesk.com/ - Chat and phone support

http://ckrack.github.com/fbootstrapp/ - Face Book iframe page using bootstrap

http://digg.com/newsbar/topnews/going_all_in_how_to_run_a_company_on_21_apps_in_the_cloud - List of 21 cloud based apps to run your business by

https://github.com/pokonski/public_activity = For tracking and displaying user activity

awesome_nested_fields

Wednesday, April 4, 2012

From heaven to hell and back again with Rails, Ruby, Rack, Capybara and sundry others

A few days ago, I started getting an odd error message while running some cucumber features. In a nutshell, the param values returned from a multiple choice selection box arrive as an array of arrays, instead of just an array.

"category_ids"=>["[\"\"]"] vs "category_ids"=>[""]


I posted an issue on the capybara git site. After a brief back and forth, I was graciously informed that I was running older versions of rack (using 1.2.5, current version 1.4.1), and that was likely the problem.

So off I go to upgrade rack. Unfortunately, rack 1.2.5 is a dependency of rails 3.0.11, which is what we are currently running.

Perhaps it is time to upgrade to rails 3.2?

But that requires ruby 1.9.3, which fails to install with rvm on a mac. Apparently, I need an updated version of Xcode. Or, I could use OSX GCC Installer.

If I was running Lion, I could have used Apple's newly released Command Line Tools for Xcode as described by Kenneth Rietz, but alas, I have not yet made the upgrade.

But after installing OSX GCC Installer, and following the recommendation that I delete the old Xcode, I got the following error when attempting to run 'rvm install 1.9.3"

/System/Library/Frameworks/Carbon.framework/Headers/Carbon.h:70:35: error: SecurityHI/SecurityHI.h: No such file or directory

So perhaps the OSX GCC Installer doesn't have SecurityHI support.

Adventures in moving from Rails 2.3.14 to 3.0: Controllers

Using textmate find and replace in project, changed 'integrate_views' to 'render_views'

Added a config.ru file with the following:

require ::File.expand_path('../config/environment',  __FILE__)
run MyApp::Application

Dealing with has_tag? undefined method error in controller specs
In Rspec 2, it is recommended to replace has_tag with has_selector? Unfortunately, this doesn't work with view specs.

I added the gem rspec-tag_matchers. In my spec_helper, I added a config.include(RSpec::TagMatchers) line. Has_tag? is now working

my .ackrc file. Adding Ruby on Rails specific files to ack

--type-add=ruby=.haml,.rake,.rsel,.feature
--type-add=css=.scss
--type-add=css=.less
--type-add=js=.coffee
--type-add=rack=.ru


mate ~/.ackrc to open the file