.

Ruby Series (”A”..”Z”)

One of the nice features of Ruby is the ‘range’ object that can represent a series of objects ranging from one object to another. A numerical series like [1, 2, 3, 4, 5, 6] can be represented as (1..6). This works well and is quite useful in many cases. Any object that implements a ‘<=>‘ operator and a ’succ’ method can be implemented as a range.

Ranges of letters don’t work as well.

  1. ("A".."C").map #=> ["A","B","C"]
  2. ("Z".."AB").map #=> []

What? Why doesn’t the second example work? That’s a valid series, it should return [ "Z", "AA", "AB" ].
The problem here seems to be that these strings don’t sort the way that ‘range’ expects them to. (Thanks to Robert Dober for pointing this out)

  1. "Y" <=> "Z"  #=> -1
  2. "Z" <=> "AA" #=> 1

So one solution to this problem is to simply create a class that implements the correct semantics and sorts properly.
Here’s one implementation.

  1. class LetterSeries
  2.   include Comparable
  3.   attr_accessor :value
  4.  
  5.   def initialize(n)
  6.     @value = n
  7.   end
  8.  
  9.   def self.from_i(n)
  10.     b = "A"
  11.     (n-1).times {b.succ!}
  12.     LetterSeries.new(b)
  13.   end
  14.  
  15.   def to_i
  16.     (0…@value.size).map.inject(0)
  17.         {|sum,n| sum + (@value[-1-n]-64) * 26**(n)}
  18.   end
  19.  
  20.   def <=>(other)
  21.     self.to_i <=> other.to_i
  22.   end
  23.  
  24.   def succ
  25.     LetterSeries.new(@value.succ)
  26.   end
  27.  
  28.   def to_s
  29.     @value
  30.   end
  31.  
  32.   def +(n)
  33.     LetterSeries.from_i(self.to_i + n)
  34.   end
  35.  
  36.   def -(n)
  37.     LetterSeries.from_i(self.to_i - n)
  38.   end
  39.    
  40. end

Now some tests….

  1. z = LetterSeries.new("Z")
  2. ac = LetterSeries.new("AC")
  3. (z..ac).map {|n| n.to_s} #=> ["Z","AA","AB","AC"]
  4. (z..z+3).map {|n| n.to_s| #=> ["Z","AA","AB","AC"]

Feel free to use this code if you find it handy.
See http://www.ruby-forum.com/topic/67300?reply_to=83726#83612 for the original discussion that inspired this code.

Polymorphic partials

Versions of Ruby on Rails greater than 1.1 have support for polymorphic associations. This is an extremely handy feature, which allows objects of arbitrary types to be associated with each other. There are a few things about them that can get a bit thorny. One of these has to do with displaying associated items.

Normally, if one wanted to list all objects associated with a parent object, one would just create a partial template to display a list of the objects. A typical one might look like this…

  1. # _object.rhtml        
  2.   <li><%= object.name %></li>

The problem with this is that you need to know what kind of object you are going to render before you render it, which makes it difficult to list polymorphic data in a single list. So what do we do? We write a helper.

In this case, it’s pretty short. All it does is figure out what kind of object is being rendered and then looks up a partial template with a name identical to the model.

It looks like this…

  1. # belongs in helpers/application_helper.rb
  2. # finds the correct partial to use for a
  3. # particular object type (for polymorphic lists)
  4. def to_partial(object)
  5.   model_file = object.class.to_s.underscore
  6.   render :partial => "#{model_file}/#{model_file}", :object => object
  7. end

This assumes that your controllers are named after the singular version of the model file. If they are the plural version, you probably want to do something like this..

  1. # Use if controllers are plural form of model names
  2. def to_partial(object)
  3.   model_file = object.class.to_s.underscore
  4.   controller_file = model_file.pluralize
  5.   render :partial => "#{controller_file}/#{model_file}", :object=>object
  6. end

This code does not let you pass in any other options, or let you render a collection of objects.
For that you can use this…

  1. def to_collection(collection)
  2.   collection.map {|object| to_partial(object)}
  3. end