Lessons learned from upgrading Rails 2.1 to 2.3

Just my collection, so you do not need to find it out on your own 😉

  • :expire now is called :expires_in, update your cache calls or timed expiring will fail
  • get rspec edge/ rspec-rails edge
    detailed instruction
    also possible and easy: sudo gem install dchelimsky-rspec dchelimsky-rspec-railsyou may need to add this when you are on 1.1.99.9 and get
    no such file to load — spec/rails/example/routing_example_group
    from github
  • get webrat 0.4.2 (atm only by installing from github clone + rake gem + sudo gem install)
  • truncate(‘xxx’,3) ==> truncate(‘xxx’,:length=>3)
  • Model.find_by_xxx will now call Model.find with :conditions=>{:xxx=>something}, this broke some of my stubs/mocks, e.g. User.stubs(:find) worked before but now came in conflict with user login that used find_by_id
  • render :inline seems not to be testable, all my request.body from render :inline are blank now (rspec 1.1.12)
  • stub_model install rspec-rails-mocha — ./script/plugin install git://github.com/mislav/rspec-rails-mocha.git
  • count returns OrderedHash, so tests now look like xxx.to_a.should == [[‘name’,2]]
  • String.chars does not have the [] method anymore, so e.g. only “xxx”.mb_chars[1..4] will work
  • @template will no longer work in vie tests, use response instead
  • have_tag returns
    undefined method `id2name’ for {:instance_writer=>false}:Hash
    , upgrade the money gem!
  • controller tests are no longer able to call actions that are not defined (e.g. no action was defined because it only renders a view and does nothing or because the action was included from a module)
  • HopToad will silently fail, use this branch instead: http://github.com/pzingg/hoptoad_notifier (as long as thoughbot does not update their own of course)
  • work systematically e.g. rake spec:models, then rake spec:controllers … or you may get mad 😉

Helpful Error Messages in-direct from your App

Custom error pages directly from your app are far better than hand-made pages or those that rails gives you by default. But if they use your controllers/helpers they may send a user into a crash-loop (arriving on /500 and boom-> /500…)

So simple add an errors controller, build some templates(name is e500, since” def 500″ will not work), set them to page caching and ping them after each deploy.

#routes.rb as lat rule
  %w[500 404 422].each do |error|
    map.connect error, :controller=>'errors', :action=>"e#{error}"
  end

#errors_controller.rb
class ErrorsController < ActionController::Base
  helper :all
  layout 'blank' #new layout, without dynamic gimmicks
  caches_page 'e500', 'e404', 'e422'

  #no authentification, so just nil
  def current_user
    nil
  end
  helper_method :current_user
end

#e500.html.erb
<h1>BOOM!</h1>

#deploy.rb
task :ping_error_pages do
  %w[500 404 422].each do |error|
    run "wget -O /dev/null localhost/#{error} --quiet"
  end
end

after "deploy:restart", *%w(
  rs:ping_error_pages
  ...
)

How to Load Javascript Last with Inline JS

Load JS last, or it will halt the browser while loading… AMEN!

You may protest:
But i use some inline javascript and nothing will work, when jQuery / Prototype / Mootolls / … is not loaded already!!

Version 1: content_for :js

#in your body: (HAML example, works for ERB too...)
%h1 My great title
- content_for :js do
  :javascript
    $(function(){
      $('h1').html('inline js rocks!')
    });

#in you footer
  =js_tag 'jquery', 'application', 'whatever'
  yield :js #this will output all content_for blocks...

Instantly better load times, and no need to abandon inline js!!

Version 2: Caching !?
When action/fragment-caching, this can get problematic, since the content_for will not be call, and hence the js will be missing.
This can be fixed by introducing a ‘later’ method, which gets filled with everything that should be done once your js framework is loaded.

#in your views
later(function(){
  $('#xxx').do_something();
})

#in you footer
later(); //call all those methods that have been stacked...

#in your head
later = function(){
  var stack = [], i_ran=0;
  return function(execute_later){
   if(typeof(execute_later)=='undefined'){
     i_ran=1;
     for(var i=0;i<stack.length;i++)stack[i].call();//execute them all
   } else {
     if(i_ran)execute_later.call()//page is loaded already, just execute
     else stack.push(execute_later)//page not finished, store
   }
 };
}()

Simple Meta Tags with MetaOnRails

Simple meta tag rules (from google webmaster tools):

  1. do not add useless meta tags
  2. add site-wide-unique meta tags
  3. add max 10 keywords
  4. in doubt add no meta tags, search engines will grab something

Obey or be penalized.

For simplicity use: MetaOnRails Rails plugin

Usage

#in your head (no not that head...)
=display_meta

#in your views, add unique keywords/description that matter
set_meta(
  :keywords=> [@movie.category, @movie.genre,@user.name]*' ',
  :description=>@movie.description
)

Output

<meta name="description" content="my description" />
<meta name="keywords" content="my,keyword" />

A More Useful login_as Test Helper

I just got upset that i could not do login_as User.first or User.first.id, so i fixed that…

Usage

#in your tests...
login_as :quentin
login_as false
login_as User.first
login_as User.first.id + 1

Install

#authenticated_test_helper
  def login_as(user)
    id = case user
      when false,nil then nil
      when Symbol,String then users(user).id
      when Fixnum then user
      else user.id
    end
    @request.session[:user_id] = id
  end