5 Rails Tips

April 25, 2008

I have put together 5 rails tips which I think you might find useful.

1. Throw your own exceptions

Throwing and catching exceptions can be a good design pattern in your rails app. Especially when you want to be able to deal with the unexpected in a clean way. Overuse is a no no as with most techniques but it is nice here and there.

I find that a good idea is to make your own exceptions when writing a rails app. This means that standard low level exceptions which often should not be caught are not.

eg.

(in config/initializers/custom_config.rb)

class ApplicationError < RuntimeError
 
  end

This way in your application code you can do something like the following:

class User < ActiveRecord:Base
    has_many :articles
    before_destroy :check_articles_dont_exist
    
    private
    def check_articles_dont_exist
      unless articles.empty?
        raise ApplicationError, 'Cannot delete user, they have written articles.'
      end
      true
    end
  
  end
begin
    @user = User.find_by_password_reset_code(params[:id])
    @user.destroy
    flash[:message] = 'Deleted user successfully'
  rescue ApplicationError => msg
    flash[:warning] = msg.to_s
  end
  redirect_to users_url

2. Minimise your page load overhead in production

odern website often have lots of tiny javascript/css files, especially rails sites. It is a good technique to only include what you have to on a page. Eg. only load prototype if you need to use some of the functionality it provides.

You can often achieve as much as a 50% improvement in page load time by decreasing the number of HTTP requests that the client browser has to make.

Rails 2 has built in caching and joining of javascript and css files. API Documentation for Javascript Include Tag
and API Documentation for Stylesheet Link Tag

ActionController::Base.perform_caching_ must be set to true (default in production) for this to work. Also, you may want to setup some ignore rules in your version control to stop the combined files being stored.

If you are not using Rails 2 or want a slightly more advanced solution there is a plugin called bundle_fu. It is very easy to setup and works well. I have had issues with using this plugin when serving over HTTPS so test it works with your setup first.

3. Write your own capistrano recipes plugin

If you work on a number of projects and use capistrano for a lot of them, it is likely that you are duplicating a lot of deploy functionality in your recipes. There are a number of recipe gems out there, the most famous being the railmachine gem. However the disadvantage of these is that often they dont quite fit the environment you are working in.

The capistrano 2 peepcode screencast gives some good information on this technique. An example deployment recipies plugin with basecamp update support is available at http://iformis.svnrepository.com/svn/deployment_recipiez

This means you can seriously cut down the size of your deploy.rb and setup deployments for new applications in much less time.

# MAIN Config
  set :application, "test_app"
  default_run_options[:pty] = true
  set :repository,  "git@github.com:pyrat/test-app.git"
  set :scm, "git"
  set :server, 'tester.ath.cx'
  

  # Deployment specifics
  set :deploy_to, "/var/www/apps/#{application}" 
  set :user, "deploy"            
  set :rails_env, 'testing'
  set :mongrel_port, 3399

  # SSH Keys
  ssh_options[:keys] = %w(/home/pyrat/.ssh/ssh_key)
  
  # Server setup
  role :app, server
  role :web, server
  role :db,  server, :primary => true

  # Hooks into tasks

  namespace :deploy do
    task :restart do
      recipiez::single_mongrel_restart
      recipiez::nginx_restart
    end
  end
  after "deploy:update_code", "recipiez:rename_db_file" 
  after "deploy:restart", "deploy:cleanup"

Another tip with this is to set it up as an svn:external:

script/plugin install -x [plugin_location]

This allows your recipies to stay up to date across multiple projects. There are complications if you are using git for your source control system. A possible technique is explained here but I look forward to a simpler solution being suggested!

4. Bundle gem dependencies with your application

A technique which has been described by the err the blog guys as Vendor Everything

Basically it involves:

1. Setting up a vendor/gems directory
2. Running

gem unpack
commands in this directory to unpack gems.
3. Adding a hook in your environment.rb to load these:

config.load_paths += Dir["#{RAILS_ROOT}/vendor/gems/**"].map do |dir| 
    File.directory?(lib = "#{dir}/lib") ? lib : dir
  end

The main advantage of this is that you can deploy on different servers which minimal hassle as all the libraries that are required for your application are included with it.

This technique has been taken on board by the rails core and they are releasing gem dependencies functionality as part of the next big rails release. Info on gem dependencies and how they might work can be found here

5. Use of to_param for cheap seo

Often when developing restful controllers you will have a show url that looks like this:

article_url(@article)

This will likely translate into

http://myaceapp.com/articles/5
where the article has an id of 5.

def show
    @article = Article.find(params[:id])
  end

Now this id is not very friendly to the search engines. A very cheap way to generate more descriptive urls is to take advantage of the way that ruby parses numbers. It will strip everything which is not a number if the string starts with a number.

eg.

“5-sfsdfsdf-sdfsdf-fsdfs” => 5
“5dfjdfkljfdkj4444445lsjskldj” => 5
“5” => 5

This cheeky little snippet will help you out (in this case slap it in the Article model):

def to_param
    "#{id}-#{headline.gsub(/[^a-z0-9]+/i, '-')}".downcase
  end

You will need to change headline to whatever field you want to use. to_param is called on the model when the url is generated. So the example above will change to

http://myaceapp.com/articles/5-railscasts-rocks

There is a slight downside that

http://myaceapp.com/articles/5-ANYTHINGHEREATALL
will take you to the same page, but I think this is a minor issue not worth losing sleep over.

The End

Well I hope you have found my 5 rails tips interesting. Take it easy.