New Rails plugin - Quick Scopes

I’ve just release a new Rails plugin called Quick Scopes that adds some generic named_scopes to your models to quickly manipulate the results you receive from associations.

It’s great to have easy access to methods to get all associations but sometimes you need to manipulate how you receive those results.

Included named_scopes:

  • limit - to limit the number of results
  • order - to order the results
  • where - alias for conditions
  • with - alias for include

Examples

# Returns all the comments on the post
post.comments

# Using quick scopes, you will be able to do the following:

# Limit the number of results
post.comments.limit(5)

# and, order the results
post.comments.order('created_at desc').limit(5)

# and, add conditions to the query
post.comments.where(:approved => true).order('created_at desc').limit(5)

# and, include sub-associations
post.comments.with(:author).where(:approved => true).order('created_at desc').limit(5)

If you need all the manipulation of the last couple of examples, you probably should create a specific named_scope but this can be very useful to have available on the console.

Have a look and let me know how it works for you and what you would do differently.

Quick Scopes: http://github.com/internuity/quick_scopes

Install

./script/plugin install git://github.com/internuity/quick_scopes.git

How to protect downloads but still have nginx serve the files

I’ve just been working on a project where a number of downloads needed to be restricted to specific users. I needed to authenticate the user and then allow them access to the file. This is not too difficult in rails:

def download
  if authenticated?
    send_file #{RAILS_ROOT}/downloads/images/myfile.zip'
  end
end

The problem with this is that if the file is large, rails will spend a lot of time sending this file to the browser. The solution, hand it off to the webserver (in my case, nginx) to send the file once the authentication has succeeded.

nginx supports a header named X-Accel-Redirect. Using this header, you send a full path to the file to be downloaded:

def download
  if authenticated?
    #Set the X-Accel-Redirect header with the path relative to the /downloads location in nginx
    response.headers['X-Accel-Redirect'] = '/downloads/myfile.zip'
    #Set the Content-Type header as nginx won't change it and Rails will send text/html
    response.headers['Content-Type'] = 'application/octet-stream'
    #If you want to force download, set the Content-Disposition header (which nginx won't change)
    response.headers['Content-Disposition'] = 'attachment; filename=myfile.zip'
    #Make sure we don't render anything
    render :nothing => true
  end
end

You will need to add a location directive in nginx marked as internal which nginx will use along with your path to get to the physical file.

location /downloads {
  root /rails_deploy/current/downloads;
  #Marked internal so that this location cannot be accessed directly.
  internal;
}

Notes:

  • You need to include the Content-Type header because nginx won’t change it and Rails sets it to text/html by default.
  • The path you send in the X-Accel-Redirect header must be relative to a location directive within your nginx config.

You can also set additional control using the following headers:

X-Accel-Limit-Rate: 1024
X-Accel-Buffering: yes|no
X-Accel-Charset: utf-8

See the nginx documentation on X-Accel-Redirect for more information.

Updated: Thanks to rick’s comment below I’ve included information on the location directive within nginx to ensure the ‘downloads’ are not accessible from outside

How to make a custom form builder in Rails

A Form builder is a great way to remove repetative code from your form views or to include some custom code into the existing helpers.
I wanted to change how I displayed error information in my forms and decided to do it in a form builder and this is how.

Overview

There are two common ways of displaying error information, in an error block at the top of the page via error_messages_for :model or inline above or below the field using error_message_on :model, :field

When using error_message_on :model, :field I find that I always have to use the :prepend_text to get the error message to display nicely. So my error handling always looks like this:

<%= form.label :name %>
<%= error_message_on :user, :name, :prepend_text => 'Name ' %>
<%= form.text_field :name %>

and the form is displayed as:

Name
Name can’t be blank

I then thought that the label is already displaying the “prepend_text” so why not inline the error message directly after the label. With a custom form builder (code below), my code just looks like this and the errors will still be displayed:

<%= form.label :name %>
<%= form.text_field :name %>

and the form is displayed as:

Name can’t be blank

The code

This form builder is going to be very simple because I’m only changing the handling of the label helper but the concepts carry through to any of the form helpers you want to extend.

Because I need to get to the errors object on the model, I had to bring in some repeat code from the label_tag helper. The default Rails form builder can be found in [rails/gem path]/actionpack/lib/action_view/helpers/form_helper.rb starting just after line 700.
Unfortunately the FormBuilder seems to be undocumented so you won’t find documentation in the Rails API docs.

I’ve written comments throughout the code to explain what I’ve done.

[lib/error_form_builder.rb]

class ErrorFormBuilder < ActionView::Helpers::FormBuilder
  #Adds error message directly inline to a form label
  #Accepts all the options normall passed to form.label as well as:
  #  :hide_errors - true if you don't want errors displayed on this label
  #  :additional_text - Will add additional text after the error message or after the label if no errors
  def label(method, text = nil, options = {})
    #Check to see if text for this label has been supplied and humanize the field name if not.
    text = text || method.to_s.humanize
    #Get a reference to the model object
    object = @template.instance_variable_get("@#{@object_name}")

    #Make sure we have an object and we're not told to hide errors for this label
    unless object.nil? || options[:hide_errors]
      #Check if there are any errors for this field in the model
      errors = object.errors.on(method.to_sym)
      if errors
        #Generate the label using the text as well as the error message wrapped in a span with error class
        text += " <span class=\"error\">#{errors.is_a?(Array) ? errors.first : errors}</span>"
      end
    end
    #Add any additional text that might be needed on the label
    text += " #{options[:additional_text]}" if options[:additional_text]
    #Finally hand off to super to deal with the display of the label
    super(method, text, options)
  end
end

Using your custom form builder

If you want to use your custom form builder for a single form, include this in your form declaration:

<% form_for :object, :builder => ErrorFormBuilder do |form| %>
<% end %>

If you want to make your custom form builder the default builder for your project, you can add the following line to your config/environment.rb file:

ActionView::Base.default_form_builder = ErrorFormBuilder

If you have any questions on how this works, feel free to ask in the comments.

How to migrate a Rails app from using Attachment_fu to Paperclip

I’ve just had to migrate a Rails app from using Attachment_fu to Paperclip and I decided I’d better document the process.

Most importantly, the app has a couple of thousand photos and I’m going to have to make the live transition as smooth as possible so it’s important that I can do all the development without affecting the live app and then I need to move all the Amazon S3 files using access to the original Attachment_fu model information and then go live with the Paperclip code.

Creating a copy of the Amazon S3 bucket

First step is to create a copy of the live S3 bucket which is important for testing. I have written a set of rake tasks to do most of the heavy lifting.
All of the S3 work is done using right_aws which is needed by Paperclip and can be installed by running:

gem install right_aws

To create a copy of your S3 bucket, run

rake utils:attachments:copy_s3_bucket FROM=production_bucket_name TO=backup_bucket_name

Moving the Amazon S3 keys from Attachment_fu scheme to Paperclips

Paperclip supports a completely configurable naming scheme while Attachment_fu is prescribed.

I wrote a rake task to physically move the keys around so that they matched my required Paperclip scheme. Initially I performed this task on the backup bucket so that I could begin writing code against the backup bucket.

The paperclip naming scheme I’m using is :id/:style.:extension

The migration is also handled by a rake task utils:attachments:migrate_attachment_fu_to_paperclip

This rake task is specific to the code being refactored and you will need to do some editing.

  • Line 63 - Change Klass to the class of your model, in my case it is Photo
  • Lines 75 & 83 - Change the format of the new_key_name if you want to use a naming scheme other than :id/:style.:extension

The rake task must then be run (do it first on your backup bucket)

rake utils:attachments:migrate_attachment_fu_to_paperclip BUCKET=backup_bucket_name

Changing the code

Now time to change the code itself. There are links to various gist files in the resources section at the end of this article.

Migration

First we’ll need a migration to change the model table format. The migration is specific to my Photo model so you’ll have to change both the model and table references to your specific project details.

The migration first renames the re-usable columns such as filename, content_type and size and then removes the columns that paperclip doesn’t use/support. Finally the migration removes all of Attachment_fu’s child entries from the database table.

Model

This is where everything gets tricky and we have to be careful not to mess up the order.

We have created a backup of our S3 bucket and now we have run a migration on our development database. The migration uses the Attachment_fu model information which we are now going to change. If you try to run the migration after you’ve changed the model, things won’t work so remember this for when the changes go live.

View the diff of my changes here: http://gist.github.com/42525

Controller

In your controller, Attachment_fu relies on a single attribute uploaded_data where Paperclip relies on an attribute the same as your attachment, in my case photo.

owner.photo.create(params[:uploaded_data])

becomes

owner.photo = params[:owner][:photo]
owner.save

or, simply

owner = Owner.create(params[:owner])

Going live

OK, so you’ve finished working on development and you’re happy that everything works as intended against your backup S3 bucket. Now it’s time for the nail-biting push to live.

Do everything in the following order (Please don’t try to  do any of these steps at the same time - I did and it bit me!):

  1. Backup your production database
  2. Upload just your rake task to your server
  3. Run the following rake tasks:
    • rake utils:attachments:clear_s3_bucket BUCKET=backup_bucket_name - Clears out all the files you’ve been testing with.
    • rake utils:attachments:copy_s3_bucket FROM=production_bucket_name TO=backup_bucket_name - Re-backs up the production files (just in case)
    • rake utils:attachments:migrate_attachment_fu_to_paperclip BUCKET=production_bucket_name - Does the migration on the production S3 bucket
  4. Upload your migration to the live server
  5. Run your migration
  6. Run your normal deployment to get the new files on the server
  7. Congratulations! You’ve done it.

Rolling back

Should anything go wrong, you simply do the following:

  1. Run the rake tasks
    • rake utils:attachments:clear_s3_bucket BUCKET=production_bucket_name - Clears out all the files that are in the wrong format.
    • rake utils:attachments:copy_s3_bucket FROM=backup_bucket_name TO=production_bucket_name - Restores the production files from the backup bucket (this is why we did all that work :-))
  2. Restore your production database
  3. Restore your previous code

Resources

Final words

This was quite tricky and I only hope that my code and examples help a little. If you have any questions, feel free to leave a comment and I’ll help where I can.

A Rails log parser to show how your actions are performing

I’ve been battling with some performance issues on a Rails project I’m working on and I wanted to see which actions could be the culprit.

I wrote a quick log parser which will read a log file and print out the call time information by controller#action. It supports sorting by various columns and outputs the results in pretty table format (similar to mysql)

View usage information by running:

ruby_log_parser.rb --help

Example

andrew:~$ ruby rails_log_parser.rb --sort median < log/development.log
+----------------------+-------+------------+------+------+------+--------+
| Uri                  | Calls | Total Time | Max  | Min  | Avg  | Median |
+----------------------+-------+------------+------+------+------+--------+
| TestController#index |     7 |       9316 | 1748 | 1053 | 1330 |   1221 |
| TestController#show  |     1 |        764764764764 |    764 |
+----------------------+-------+------------+------+------+------+--------+

The code

You can find the parser here:
http://github.com/andrewtimberlake/scripts/tree/master/rails/utils/rails_log_parser.rb

I have also stripped out the table pretty printing here:
http://gist.github.com/41938

Usage:

require 'tabalize_array'

arr = [
  ['John', 18],
  ['Jack', 21],
  ['Mary', 22],
  ['Jane', 24]
]
arr.tabalize(['Name', 'Age'], [:left, :right], STDOUT)

Automatically handle unexpected Ajax errors in Rails

I’m busy working on a project that uses a lot of Ajax to deal with updates and lookups etc. Occasionally I write rubbish code and one of these methods break.

When using normal HTML this is not a problem because Rails presents the user with a nice message saying “We’re sorry but something went wrong…”.
But, with Ajax, Rails returns the exact same page which is useless in an Ajax call. Because of this, users are generally left without knowing if their request is taking a long time or has failed.

I dug into Rails error handling and decided to deal with this so that errors pop-up an alert message with the same message as the HTML 500 error. I also wanted to get more error information if I get an error in development.
The two magic methods you need to override in your application controller are: rescue_action_in_public and rescue_action_locally.

I simply defined my own version of these methods and checked for an Ajax call with a respond_to block. If the request is for standard HTML, I delegate the call back to the superclass so everything continues as normal and if it’s a Javascript call, I send back an alert message.

class ApplicationController < ActionController::Base

...

private
  def rescue_action_in_public(exception)
    respond_to do |want|
      want.html {
        super(exception)
      }
      want.js {
        render :update do |page|
          page.alert "We're sorry, but something went wrong.\nWe've been notified about this issue and we'll take a look at it shortly.\n\nPlease check that any update you tried has not been successful before trying it again."
        end
      }
    end
  end

  def rescue_action_locally(exception)
    respond_to do |want|
      want.html {
        super(exception)
      }
      want.js {
        render :update do |page|
          page.alert "Oops! I made a mistake\n#{exception.class}: #{exception.message}\nCheck the logs for more detail."
        end
      }
    end
  end

...

end

Display your current GIT branch in your prompt

I spotted a customised prompt on someone’s computer and remembered that you can control what’s displayed in your prompt. This led to an immediate Ah-ha moment that I should display my current working GIT branch there to stop having to keep running git branch to remember which branch I’m working in.

I quickly Googled to see if someone else had the same idea (which they invariably have) and found a solution which I have adjusted to the standard Ubuntu prompt.

Add this to your .bashrc file in your home directory:

function parse_git_branch {
  git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e 's/* (.*)/(1)/'
}

PS1="${debian_chroot:+($debian_chroot)}u@h:w$(parse_git_branch)$ "

When done, save the file and run

. ~/.bashrc

to reload the settings and see your new prompt

Mine is: (outside of a git repository)

andrew@thinkpad:~$

and within a repository

andrew@thinkpad:~/dev/test(master)$

How to use the new templates in Rails

One of the new features in the upcoming Rails 2.3 is templates. I’ve been playing around with them and have documented some ways of using them below.

To start with, there’s a great write-up on most of the template methods on Pratik Naik’s blog

I want to play with some ideas around how you can further customise an initial rails project.

Initial template

#Install gems
gem 'mocha'
gem 'thoughtbot-shoulda'
gem 'thoughtbot-factory_girl'
gem 'thoughtbot-quietbacktrace'
gem 'mislav-will_paginate', :version => '~> 2.2.3', :lib => 'will_paginate', :source => 'http://gems.github.com'

rake 'gems:install'
rake 'gems:unpack'

#install plugins
plugin 'hoptoad_notifier', :git => 'git://github.com/thoughtbot/hoptoad_notifier.git'

#Delete all unecessary files
run "rm README"
run "rm public/index.html"
run "rm public/favicon.ico"
run "rm public/robots.txt"

#Setup git ignore file
file '.gitignore', <<-END
config/database.yml
db/schema.rb
log/*.log
public/stylesheets/*.css
*.swo
*.swp
tmp/
END
run 'touch tmp/.gitignore log/.gitignore'

#add to git
git :init
git :add => '.'
git :commit => "-a -m 'Initial commit'"

Writing files

I like to use HAML which uses a less than conventional way of generating the plugin. First, the gem needs to be installed via

gem install haml

and then the plugin is installed via

haml --rails 

The plugin file is verry simple so we can include it directly in our script.

gem 'haml'

file 'vendor/plugins/haml/init.rb', <<-END
require 'rubygems'
begin
require File.join(File.dirname(__FILE__), 'lib', 'haml') # From here
rescue LoadError
require 'haml' # From gem
end

# Load Haml and Sass
Haml.init_rails(binding)
END

Modifying existing files

Rails generates some existing files which you might want to modify using your template such as un-commenting a line or inserting a line. I’ll demonstrate both by making adjustments to the application controller.

gsub_file 'app/controllers/application_controller.rb', /(class ApplicationController.*)/, "\\1\n  include HoptoadNotifier"
gsub_file 'app/controllers/application_controller.rb', /#\s*(filter_parameter_logging :password)/, '\1'

In the above lines, I am adding the Hoptoad notifier include line and un-commenting the

filter_parameter_logging

line.

Asking questions

For a totally custom template, you can ask questions and use the results in your template output.

If you’re installing the HoptoadNotifier plugin, the initialization script requires the API code, we can retrieve that as follows:

hoptoad_api_key = ask('What is your Hoptoad API key?')

initializer 'hoptoad.rb', <<-END
HoptoadNotifier.configure do |config|
config.api_key = '#{hoptoad_api_key}'
end
END

The complete template

#Install gems
gem 'mocha'
gem 'thoughtbot-shoulda'
gem 'thoughtbot-factory_girl'
gem 'thoughtbot-quietbacktrace'
gem 'mislav-will_paginate', :version => '~> 2.2.3', :lib => 'will_paginate', :source => 'http://gems.github.com'
gem 'haml'

rake 'gems:install'
rake 'gems:unpack'

#install plugins
plugin 'hoptoad_notifier', :git => 'git://github.com/thoughtbot/hoptoad_notifier.git'
file 'vendor/plugins/haml/init.rb', <<-END
require 'rubygems'
begin
require File.join(File.dirname(__FILE__), 'lib', 'haml') # From here
rescue LoadError
require 'haml' # From gem
end

# Load Haml and Sass
Haml.init_rails(binding)
END

#Configure Application Controller
gsub_file 'app/controllers/application_controller.rb', /(class ApplicationController.*)/, "\\1\n  include HoptoadNotifier"
gsub_file 'app/controllers/application_controller.rb', /#\s*(filter_parameter_logging :password)/, '\1'

#Setup Hoptoad
hoptoad_api_key = ask('What is your Hoptoad API key?')

initializer 'hoptoad.rb', <<-END
HoptoadNotifier.configure do |config|
config.api_key = '#{hoptoad_api_key}'
end
END

#Delete all unecessary files
run "rm README"
run "rm public/index.html"
run "rm public/favicon.ico"
run "rm public/robots.txt"

#Setup git ignore file
file '.gitignore', <<-END
config/database.yml
db/schema.rb
log/*.log
public/stylesheets/*.css
*.swo
*.swp
tmp/
END
run 'touch tmp/.gitignore log/.gitignore'

#add to git
git :init
git :add => '.'
git :commit => "-a -m 'Initial commit'"

How to update the Rails scaffold generator to suit your own applications

Rails contains generators that often make your programming a lot easier. One of these is the scaffold generator which creates a base of code that allows you to work with your models immediately.

The code that the scaffold generator creates is quite basic and as such I found that very early on I stopped using it alltogether - in fact I don’t think I’ve ever used scaffold code in any project I’ve done.
Now a large number of projects later, I realise that I’ve been scaffolding my own code by hand for every controller in every action - not very DRY or efficient.

Today I decided to roll my sleeves up and put the effort into creating my own scaffold generator. It’s really not difficult and will speed up a lot of my development in the future.

The objectives

  • The generated views should use HAML
  • The generated tests should use Shoulda
  • The new and edit view should use the same form partial
  • No layout or css should be generated (I tend to use the main application layout except for special circumstances)

The process

Rails will look for user generators in the ~/.rails/generators/ directory and it will use any generators there before the built-in generators so this is where we will build our custom scaffold generator. First copy the original generator from (if you have the Rails source code) RAILS_SOURCE/railties/lib/rails_generator/generators/components/scaffold/ or (if you have Rails installed as a gem) GEM_SOURCE/rails-x.y.z/lib/rails_generator/generators/components/scaffold In my case it was:

mkdir -p ~/.rails/generators
cp -r ~/dev/rails/railties/lib/rails_generators/generators/components/scaffold ~/.rails/generators/

There are three parts to a generator:

  • The USAGE file - this contains information on how the generator works
  • The <generator_name>_generator.rb file - this is the actual generator code (in our case it’s named scaffold_generator.rb)
  • The templates directory - this contains template files that can be used during generation

To use HAML instead of Erb, I editted the template generation at line 56 from “#{action}.html.erb” to “#{action}.html.haml” so now all views are saved with the .haml extension but the views still need to be changed to HAML code.

for action in scaffold_views
m.template(
"view_#{action}.html.erb",
File.join('app/views', controller_class_path, controller_file_name, "#{action}.html.haml")
)
end

While I may be changing the view to HAML code, the template must still be in Erb as the template system will run Erb on the template to allow us to insert code such as class names etc. Here is an example view template after I’ve made changes:

-title "<%= plural_name %>"
.actions
  =link_to 'New <%= singular_name %>', new_<%= singular_name %>_path
%table{:cellspacing => 0}
  %thead
    %tr
<% for attribute in attributes -%>
      %th <%= attribute.column.human_name %>
<% end -%>
  %tbody
    -@<%= plural_name %>.each do |<%= singular_name %>|
      %tr{:class => cycle(:odd, :even)}
<% for attribute in attributes -%>
        %td= <%= singular_name %>.<%= attribute.name %>
<% end -%>
        %td= link_to 'view', <%= singular_name %>
        %td= link_to 'edit', edit_<%= singular_name %>_path(<%= singular_name %>)
        %td= link_to 'delete', <%= singular_name %>, :confirm => 'Are you sure you want to delete this <%= singular_name %>?', :method => :delete

If you are modifying scaffold views and want to continue to use Erb, you will need to escape all Erb code that should remain in the template with <%% (This is done in the original view templates) The rest of the modification are pretty simple modifications of the scaffold_generator.rb code and the templates.

I have put my code on GitHub and you can see a diff of my changes at http://github.com/andrewtimberlake/scripts/commit/095b8615dcff5d37ef94edcc9affed5396fe9731 Some of my modifications depend on the following:

  • Shoulda plugin being installed
  • HAML plugin being installed
  • An application helper method being present called title which allows me to set the page title in a view:
    def title(title_text)
    @title = title_text
    end

To make this really useful, I’m going to be working on a Rails template which is coming in Rails 2.3. I’ll write about generating a template in a future post.

Hello world!

Every good programmer needs to start with Hello World so this is it, making sure all works and then I can start with some real content.

   Newer→