Ruby Weekly is a weekly newsletter covering the latest Ruby and Rails news.

Columnized text datasets in Rails

By Peter Cooper / June 12, 2006

A few days ago, Courtenay of Caboose posted an article called 'pretty tables for ruby objects' that give a MySQL-command-line-client style textual view of data stored in your Rails database. The syntax worked like this:

puts Invoice.pretty_table(nil,{:max_width => 50, :line_spacer => '-', :find => { :order => 'created_at desc' }})

For an output like so:

| created_at                   | updated_at                   | name       | due_at                       | p |
---------------------------------------------------------------------------------------------------------------
| Fri Jun 09 14:17:00 PDT 2006 | Fri Jun 09 18:34:55 PDT 2006 | Website    | Sun Jul 09 14:17:00 PDT 2006 | 1 |
| Fri Jun 09 14:01:00 PDT 2006 | Fri Jun 09 18:34:55 PDT 2006 | May        | Fri Jun 23 14:01:00 PDT 2006 | 1 |
| Sat Jun 03 12:25:00 PDT 2006 | Fri Jun 09 18:34:55 PDT 2006 | Invoice 3  | Mon Jul 03 12:25:00 PDT 2006 | 3 |

It didn't make sense to me and I suggested developing a version that worked in this way instead:

Something.find(:all, :conditions => 'whatever').pretty_print

This way you can use your regular model accessors and simply use the 'pretty' output on any result set whatsoever. I didn't hear anything back so I wrote it myself and am releasing the code here (Note: it seems courtenay was listening, and he also developed the same thing - see the article previous - but mine is different enough for it to be worth posting it still):

class Array

  protected

    def columnized_row(fields, sized)
      r = []
      fields.each_with_index do |f, i|
        r << sprintf("%0-#{sized[i]}s", f.to_s.gsub(/\n|\r/, '').slice(0, sized[i]))
      end
      r.join(' | ')
    end

  public

  def columnized(options = {})
    sized = {}
    self.each do |row|
      row.attributes.values.each_with_index do |value, i|
        sized[i] = [sized[i].to_i, row.attributes.keys[i].length, value.to_s.length].max
        sized[i] = [options[:max_width], sized[i].to_i].min if options[:max_width]
      end
    end

    table = []
    table << header = columnized_row(self.first.attributes.keys, sized)
    table << header.gsub(/./, '-')
    self.each { |row| table << columnized_row(row.attributes.values, sized) }
    table.join("\n")
  end
end

class ActiveRecord::Base
  def columnized(options = {})
    [*self].columnized(options)
  end
end

To use:

>> puts Post.find(:all).columnized(:max_width => 10)
updated_at | title      | private | url | thumb      | metadata | movie      | id  | views | content    | user_id | created_at
------------------------------------------------------------------------------------------------------------------------------
Wed May 31 | tetwer     | 0       |     |            |          |            | 909 | 0     | video:xyzz | 1       | Wed May 31
Wed May 31 | bbbb       | 0       |     |            |          |            | 1   | 15    | // descrip | 1       | Tue May 23
Wed May 31 | cxzcxzx    | 0       |     |            |          |            | 906 | 19    | // descrip | 1       | Tue May 23
Wed May 31 | jklklkl;   | 0       |     |            |          |            | 907 | 35    | // descrip | 1       | Tue May 23

If you want to use it with your project, put the code into lib/columnized.rb, use require 'columnized', and you're ready to roll. Unlike courtenay's version, mine only supports max_width, but I didn't consider changing the column separator too important :)

Other Posts to Enjoy

Twitter Mentions