Upgrading to Capistrano 2.0

February 20th, 2008

Jesus, scary.

This past week I finally took the time to update my deploy recipe to the new Capistrano 2.0 format. It’s amazing how DRY the new namespaces feature is. I was able to take all of my custom tasks used for our Apis Networks shared hosting packages and separate them into their own plugin. This way, I’m no longer clobbering the default Capistrano deployment strategy.

It’s as easy as creating a two folders in your vendor/plugins directory. One for the name of the plugin, and one dubbed ‘recipes’. Here’s a typical directory tree:

1
2
3
4
5
-- rails_app
 -- vendor
  -- plugins
   -- your_cap_plugin
    -- recipes

Then, create a file in the recipes directory using any filename you like. Capistrano 2.0 will automatically load any tasks inside this file. Begin naming your deployment recipe inside it’s own namespace, like so:

recipes/tasks.rb

1
2
3
4
5
6
7
8
9
10
namespace :my_tasks do
  namespace :deploy do
    desc "Restart the FCGI ruby process"
    task :restart, :roles => :app do
      puts "Restarting the ruby processes..."
      # Ignore this confusing unix syntax, it's just for examples sake
      run "ps lx | grep ruby | grep -v grep | awk '{print $3}' | xargs --no-run-if-empty kill -9"
    end
  end
end

To hook your custom tasks into your deployment strategy, add before and after filters to your config/deploy.rb.

config/deploy.rb

1
2
3
4
5
6
7
8
9
10
namespace :deploy do
  # After restarting the ruby process, run the default cleanup task
  # and run my custom 'post_live' task
  after "deploy:restart", "deploy:cleanup", "my_tasks:deploy:post_live"

  desc "This overrides the default Capistrano deploy:restart task"
  task :restart, :roles => :app do
    my_tasks.deploy.restart
  end  
end

Re-usable plugin code, fun for every project!

Series: Plug-in arsenal - Paginating Find

February 15th, 2008

Books and recordsets!

Oh, pagination. How you have been tormented over the years in the Rails community. For those of you who have no idea what the devil pagination is, run a search on Google and take a look at near the bottom of the page – You should see “previous” and “next” links, alongside numbers of pages with which to jump to. That’s pagination!

I believe the majority of current opinion on paginating through records is to avoid using the Rails 1.0 built-in Pagination class. In Rails 2.0, it’s been removed and requires the install of an additional plug-in to receive the previous functionality. Cue Paginating Find, a plug-in written by Alex Wolfe of Carboard Rocket. I first learned of Paginating Find via Ilya Grigorik’s blog, as he had written some Helpers & View Partials to support it. Let’s jump in, shall we?

You can install Paginating Find here:
1
ruby script/plugin install http://svn.cardboardrocket.com/paginating_find

Paginating Find overrides the default ActiveRecord::find method, and allows a new set of options dubbed :page to accompany the familiar :conditions, :order, etc. We’ll use a Bulletin model for our example:

bulletin.rb (Model)

1
2
3
4
5
6
7
def self.find_all_using_pagination( options = {} )
  return self.find( :all,
    :page => { 
      :size => options[:number_of_records],
      :current => options[:current_page]
    } )
end

These options are typically provided by GET variables passed through the URL string to the controller. The new :page option requirs two vars, :size & :current. Use :size to dictate how many Bulletin’s should be pulled from the database per page, and :current to state which page we’re currently viewing. For example, if :current is set to 2, and :size is at 10, we would receive Bulletin’s 11-21.

The best part about Paginating Find is that it works transparently. If you don’t provide the :page option in the find() call, Rails will enact a regular find() call and ignore the fact that Paginating Find is even installed.

Further readings:

A Variation On Rails' Verbose Time Helper

February 5th, 2008

Oh tax season. Does anyone else find that January is the busiest month of the year?

A client of ours recently wanted a custom version of the built-in Rails helper distance_of_time_in_words. Basically, they were looking for a countdown that would dynamically let the customers know how much time they had left to order.

For those unfamiliar with distance_of_time_in_words, you provide it with Date & Time objects and it will state the duration between them in a more human readable format. For instance:

index.html.erb (View)

1
2
3
4
<%= distance_of_time_in_words( Time.now, 45.days.from_now ) %>
  # => about 1 month
<%= distance_of_time_in_words( Time.mktime( 2008, 02, 05 ), Time.mktime( 2008, 03, 29 ) ) %>
  # => about 1 month

This default behavior is great for informal purposes, such as how long it’s been since a blog article was posted or a comment made. However, I’ve come up with something a little more accurate:

1
2
3
4
<%= distance_of_time_in_words( Time.now, 45.days.from_now ) %> 
  # => 1 month, 14 days
<%= distance_of_time_in_words( Time.mktime( 2008, 02, 05 ), Time.mktime( 2008, 03, 29 ) ) %>
  # => 1 month, 22 days

application_helper.rb (Helper)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def distance_of_time_in_words( from_time, to_time = 0, include_seconds = true )
  from_time = from_time.to_time if from_time.respond_to?(:to_time)
  to_time = to_time.to_time if to_time.respond_to?(:to_time)
  distance_in_minutes = (((to_time - from_time).abs)/60).round
  distance_in_seconds = ((to_time - from_time).abs).round

  case distance_in_minutes
    when 0..1
    case distance_in_seconds
      when 0..59 then "#{pluralize( distance_in_seconds, 'second' )}"
      else             "1 minute"
    end

    when 2..59           then "#{distance_in_minutes} minutes"
    when 60              then "1 hour"
    when 61..1439        then
      hours, minutes = (distance_in_minutes.to_f / 60.0), (distance_in_minutes.to_f % 60.0)
      "#{pluralize( hours.floor, 'hour' )}, #{pluralize( minutes.floor, 'minutes' )}"      
    when 1440..1500      then "1 day"
    when 1501..2879      then "1 day, #{pluralize( (distance_in_minutes.to_f / 60 - 24).floor, 'hour' )}"
    when 2880..43199     then "#{(distance_in_minutes / 1440).round} days"
    when 43200..44641    then "1 month"
    when 44641..86399    then "1 month, #{pluralize( ( distance_in_minutes / 1440 - 30 ).floor, 'day' )}"
    when 86400..525599   then "over #{(distance_in_minutes / 43200).floor} months"
    else                      "over #{pluralize( (distance_in_minutes / 525600).round, 'years' ) }"
  end
end
Alternatively, we could use the method alias time_ago_in_words to skip the need for a first argument, and assume we’re comparing the current time with the provided distant time:
1
2
<%= time_ago_in_words( Time.mktime( 2008, 03, 29 ) ) %>
 #=> 1 month, 22 days (assuming current time is February 5th, 2008)

Welcome to 10.5! Migrating Your Tiger MySQL Databases to Leopard

December 28th, 2007

After quite a bit of deliberation, I’ve decided to move up from Tiger to Leopard. Thankfully, the path to Rails w/ MySQL has been much easier than it ever was on Tiger.

So far, my greatest obstacle has been installing MySQL and bringing my old databases over from my backup. The gracious folks over at Hivelogic have created a brilliant, line-by-line tutorial for building MySQL on Leopard.

After getting MySQL up and running successfully, I completely overlooked the fact that my databases would be wiped clean. I realized the easiest thing to do would be to copy the databases from my Tiger backup.

Note: Both of my MySQL installs are Version 5, so I crossed my fingers hoping they’d be compatible and so far there’s been no problems.

Step 1. Make sure to turn off your current Leopard MySQL install. The command for this in the tutorial is: sudo launchctl unload -w /Library/LaunchDaemons/com.mysql.mysqld.plist which sends the unload command to your plist file.

Step 2. Locate the “data” or “var” folder in your MySQL backup directory. For instance, mine was in /usr/local/mysql on my Tiger install.

Step 3. sudo cp -rf /Volumes/Backup/usr/local/mysql/data /usr/local/mysql Or substitute the paths for the proper source and destination. This will copy the database folder from your Tiger backup to your fresh Leopard MySQL install.

Step 4. sudo chown -R mysql /usr/local/mysql/data mysql Give ownership of the database directory to the mysql user.

Step 5. Start up the server and you should be good to go!

Happy Leopard’ing.

To Trick 'to_xml'

December 14th, 2007

XML Knowledge!

Recently, I’ve been sprinkling more and more XML into my projects. There’s a terrific helper method in Rails dubbed to_xml, which you can run over a collection of records.

At first, I had trouble using this because I was limited to outputting every typical AR attribute for the given model. (ie. I’d receive , , , etc. XML tags) After a bit of digging I found some options for to_xml which provides you with control over which attributes it XML’izes.

Here they are in action:

xml_controller.rhtml (Controller)

1
2
3
4
5
6
7
8
9
def compile_slides
  @slides = Slide.find( :all )
  
  # The :only call is necessary to omit all of the typical attributes
  render( :xml => @slides.to_xml(
    :only => [],
    :methods => [ :name, :cover_photo_url, :xml_url ]
  ) )
end

The :only option is overriding the to_xml’s default behaviour of spitting out every attribute, while :methods is stating which custom methods for the AR Model I want to include. For instance, instances of Slide repond to a method named ‘xml_url’:

slide.rb (Model)

1
2
3
4
5
6
7
def xml_url
  return "" if self.url.blank?
  return "http://#{self.url}" unless self.url[/http\:\/\//]
  return self.url
end

...

This will give us something like:

slides.xml (XML)

1
2
3
4
5
<slide>
  <xml-link>
    /articles/1-how-to-search-google
  </xml-link>
</slide>

Flexible!

Rails hosting

November 22nd, 2007

My buddy just sent me a link to a hosting company I haven’t seen before: Site5. I’m curious as to how their customer support is, and how much control you get over your hosting package. (e.g. SSH? Multiple domains? etc.)

We at Carbon Media have always trusted our sites to Apis Networks, which we’ve had nothing but exceptional customer service from. Plus, they were more than accommodating when starting off with Ruby on Rails and helping us get our applications up and running.

However, Site5 does support Rails. If anybody has experience with this company, please leave a comment or send an email to let me know what you think of them! I may just have to start up a personal project to give them a test run…

Series: My arsenal of plug-ins - Bundle-fu

November 12th, 2007

UPDATE: Looks like they’ve added functionality like this in Rails 2.0 using <%= stylesheet_link_tag( “main”, “forms”, “utilities”, { :cache => “frontend” } ) %>. Check out the latest release candidate w/ notes.

After writing about all of the nifty Firefox extensions I use on a daily basis, I had an idea to detail the Rails plug-ins and gems I’m in love with. This marks the beginning.

Bundle-fu fake logo

Tim Harper’s Bundle-fu plug-in was just the thing I was looking for while trying to cut down on the number of server requests per page. All you have to do is wrap both your stylesheets & javascripts in your layout file into separate code blocks, and bundle-fu will take care of the rest.

layout.rhtml

1
2
3
4
5
<% bundle( { :name => "styles" } ) do -%>
  <%= stylesheet_link_tag( "common", :media => "screen" ) %>
  <%= stylesheet_link_tag( "main",   :media => "screen" ) %>
  ... etc.
<% end -%>

This outputs a one-liner pointing to one lovely file all your css:

1
<link href="/stylesheets/cache/styles.css?20071111142032" media="screen" rel="Stylesheet" type="text/css" />

Here’s the install:

1
script/plugin install http://bundle-fu.googlecode.com/svn/tags/bundle_fu

Make sure to read the caveats page as there is a few (as always). I believe the largest is the fact that conditionally loaded stylesheets for Internet Explorer need to be outside of the code block.

Custom 404 Routes & Pages in Rails

November 3rd, 2007

Lately, I’ve been using a nice ‘catch-all’ trick in my routes.rb file to send users to a custom 404 page. It’s as simple as throwing this below the rest of your routes:

1
2
3
4
5
# If the user has typed an Invalid URL
map.connect "*path", :controller => "www", :action => "unrecognized"

# Make sure you don't have this default route in there:
# map.connect ':controller/:action/:id' # Comment or remove!

Now, if any invalid URLs are typed in, the app will respond by dishing up the action ‘unrecognized’ from the WwwController.

Here’s where the problem comes in. We recently redesigned a site using Rails, however I noticed that Google was continuing to index the old PHP version of the site just as it always had. Instead of removing the page from it’s index, it was displaying the content of my new custom 404 page. After some research, I realized the reason was my custom 404 page wasn’t a true 404, and was instead sending a 200 Successful HTTP response header instead. This turned out to be a quick fix, but I found it quite undocumented on the net:

1
2
3
4
5
class WwwController < ApplicationController
  def unrecognized
    render( :status => "404 Not Found" )
  end
end

Now our server will dish up the standard ‘404 Not Found’ HTTP response header. It’s just that easy.

Overriding class methods for migrations, a follow-up

October 16th, 2007

I realized I left one important point out from my previous post. I’m sure a few of you may have already picked up on this as well. When you override the class in the migration, you can add in whatever methods you would like the migration to use. We’ll use the same example as before:

004_add_live_pages.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class AddLivePages < ActiveRecord::Migration
  
  # Say for instance the Page.rb model has 20 instance methods in it
  # that we don't want to have affect our migration, but there is
  # 1 method we want to use below
  class Page < ActiveRecord::Base
  
    # The method 'stripped_name' below is an extension
    # of the String class
    def set_permalink
      self.update_attribute( :permalink, "#{self.id}-#{self.stripped_name}" ) if self.permalink.blank?
    end
  
  end
  
  def self.up  
    page = Page.create(
      :name => "About",
      :body => "Write your about page content here.",
    )
    page.set_permalink
  end
  
  def self.down
  end
  
end

Ta da! Now we can use the ‘set_permalink’ instance method anywhere inside our migration

Overriding class methods for migrations

October 12th, 2007

This trick is an oldy but a goodie. I’m not sure who was first to figure this out but it’s helped me in multiple scenario’s.

Sometimes, when you run rake db:migrate and you’re creating or editing or just messing around with models which already exist, you’ll run into weird errors thrown by the migration. In this case it’s possible that some ActiveRecord callback code (such as before_create :method) is interfering with what you intended. Looks look at an example:

page.rb

1
2
3
4
5
6
7
8
9
10
11
12
class Page < ActiveRecord::Base

  # AR Callbacks
  after_create :set_permalink

  def set_permalink
    last_inserted_record = eval( "#{self.class.to_s}" ).find( :first, :order => "id DESC" )
    last_insert_id = last_inserted_record.nil? ? 1 : last_inserted_record.id.increment( 1 )
    self.permalink = "#{last_insert_id}-#{stripped_name}" if self.permalink.blank?
  end
  
end

004_add_live_pages.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class AddLivePages < ActiveRecord::Migration
  
  def self.up
    # Pages
    create_table( :pages, :options => 'ENGINE=MyISAM DEFAULT CHARSET=utf8' ) do |table|
      table.column :name,       :string,  { :null => false }
      table.column :body,       :text
      table.column :permalink,  :string
      table.column :created_at, :datetime
      table.column :updated_at, :datetime
      table.column :deleted_at, :datetime
    end
    
    Page.create(
      :name => "About",
      :body => "Write your about page content here."
    )
  end
  
end

The easiest way to get around having to worry about Rails automatically filling in the permalink from the after_create call in Page.rb is to override the methods defined in the class just for this one migration. As follows:

004_add_live_pages.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class AddLivePages < ActiveRecord::Migration
  
  ### Overriding the Page.rb class file
  class Page < ActiveRecord::Base; end;
  
  def self.up
  
    ...
    
    Page.create(
      :name => "About",
      :body => "Write your about page content here.",
      ### Now you can define your permalink on create
      :permalink => "1-about"
    )
  end
  
end

It may seem like a bit of a hack, and I wouldn’t be surprised if there’s a better way to do this now, but it works for me!

Getting Inside Your Tests

October 9th, 2007

Having trouble tracing the output of your tests? Here’s a quick tip when using functional testing (testing your controllers). Just throw in a simple print or puts call in between your page request (using GET or POST) and before you run your assertions. For instance:

example_test.rb

1
2
3
4
  get( "index" )
  puts @response.body
  puts flash[:notice]
  assert( nil, flash[:notice] )

Snappy!

Testing formatted RESTful routes

August 7th, 2007

I was searching for a method to testing formatted URLs in Rails, and nothing was popping up in Google so I decided to write about it. All I wanted to check was if my .csv exporting was failing from any code I was refactoring. Here’s the gist of it:

Routes.rb

1
2
3
4
5
6
7
map.resources :reports, :controller => "admin/reports",
  :name_prefix => "admin_", :path_prefix => "admin",
  :member => {
    :artist => :get,
    :event => :get,
    etc.
  }

reports_controller_test.rb

1
2
3
4
5
6
def test_should_download_various_reports_as_csv
  [ "event", "showing", "artist", "box_office" ].each do |report|
    get( "#{report}", { :format => "csv", :id => 1 } )
    assert_response( :success )
  end
end

All the magic happens right there, by calling the regular action name defined in the controller, but adding :format => “csv” to the GET parameters hash. And now, my lovely controller will run through this piece of controller code:

reports_controller.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
def box_office
  @report = Reporting::BoxOffice.new( { :showing_id => params[:id] } )
    
  respond_to do |format|
    format.csv do
      csv_string = @report.generate_csv
      send_data( csv_string, {
        :type => "text/csv; charset=utf-8; header=present",
        :disposition => "attachment; filename=#{@report.showing.event.name.underscore}_#{@report.showing.id}_box_office_report.csv"
      } )
    end
  end
end

Easy as pie?

Let's send those users back!

July 3rd, 2007

I seem to be posting a collection of my ugliest code, but they’re the methods I find that aren’t documented too well in the world of Rails and are quite possibly some of the more useful tricks I use.

The following code is used to store which page the user was previously viewing. This is helpful if the user has to complete a few steps before viewing content they’ve requested, such as registering for a new account.

application.rb

1
2
3
4
5
6
7
8
9
before_filter :create_referral_url

def create_referral_url
  unless request.env["HTTP_REFERER"].blank?
    unless request.env["HTTP_REFERER"][/register|login|logout|authenticate/]
      session[:referral_url] = request.env["HTTP_REFERER"]
    end
  end
end

The first ‘unless’ condition makes sure that the global request variable used by Rails has an attribute named HTTP_REFERER, and that this attribute contains the last page.

The regular expression in the 2nd ‘unless’ condition filters out any pages you do not want to cache into the session as the referring page. This is helpful if the previously viewed page already has to do with the user’s account, and does not contain any content blocked behind the user’s need for authentication.

Finally, the previous page is cached into a session variable, which we can now redirect the user back to once they have logged in or created a new account. Here’s how we’ll handle this:

In your user registration controller…

1
2
3
4
5
6
7
8
9
10
11
def create
  ... create the user ...
  redirect_to_next_step
end

def redirect_to_next_step
  # If a referral URL exists, send the user to that page (The page they were previously viewing)
  redirect_to( session[:referral_url] ) and return if session[:referral_url]
  # Otherwise, off to the home page with 'em!
  redirect_to( :controller => "home" )
end

Of course, it’s always better to use named routes or REST instead of straight map.connect routes, but you already knew that. Right?

Custom exception handling

June 21st, 2007

Custom exceptions are a great way of raising errors which you expect to have occur, and which allow you to name and handle them accordingly.

This is ripped out of the wonderful Active Merchant e-commerce library:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module ActiveMerchant

  class CountryCodeFormatError < StandardError
  end

  def detect_format
    case @value
    when /^[[:digit:]]{3}$/
      @format = :numeric
    ... more 'when' statements, etc.
    else
      raise CountryCodeFormatError, "The country code is not formatted correctly: #{@value}"
    end
  end
  
end

For some strange reason, when I hit up the Pickaxe for info on exceptions I was met with a very basic usage, and nothing that delved into the use of Custom exceptions down through the stack. So I decided to ask Google for some other skilled Ruby users details of custom exceptions, and also found nothing along the lines of what I was needing. I watched a great video of Jamis Buck and Marcel Molina explaining that custom exceptions are definitely the better way of handling errors, instead of using if, elsif, elsif, else, etc. Jamis has previously written about utilizing custom exceptions when writing everyday code on his blog, but it didn’t delve into enough detail for me again.

After piecing together bits and pieces from all of my resources, and playing around with a ton of various combinations of exceptions I believe I’ve come up with something that definitely makes sense to my coding style. Above is an example of my custom exceptions in action, and I’ve thrown in a bunch of other usages below for the sake of example:

In one of your controllers

1
2
3
4
5
6
7
8
def check_show_status
  # This forces a fallback to the corresponding
  # rescue if the show is currently locked
  raise Showing::NotOnSaleError if @showing.locked?
rescue Showing::NotOnSaleError
  flash[:notice] = "This show is currently locked, but will be on sale soon."
  redirect_to( ticketing_event_url( @showing.event ) )
end

My Showing.rb model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Showing < ActiveRecord::Base

  class ShowingError < StandardError
  end
  
  class NotOnSaleError < ShowingError
  end
  
  class SoldOutError < ShowingError
  end
  
  ... etc. etc.
  
end

Copying a recordset from one table to another

June 17th, 2007

Recently, a client requested a quick addition to one of our software packages that would let them copy content from an existing record to a new one. Essentially, they wanted the option of copying previous ticket prices and dates for a showtime to a new showtime. There’s a multitude of ways to implement this functionality, and for our purposes I found the best method was to use a bit of AJAX.

Read the rest of this entry