Really silly percentage calculation in Ruby

Here's a silly way to calculate percentages of numbers in a Ruby project or Rails app.

The end result of this is having created a few methods for Ruby numeric classes that allow you to calculate percentages:

>> 23.456.five_percent
=> 1.1728
>> 100.46.seven_percent
=> 7.0322
>> 95.fifty_percent
=> 47.5

To accomplish this, we'll use use meta programming to add the methods to the Fixnum and Float classes.

First, create a way to translate numbers in English to numeric values. This was taken from a thread at ruby-form.

Then, we'll make use of Ruby's method_missing to find out if this is a "*_percent" method and act accordingly. The percent_method? method returns true if it is, false if it ain't.

module NumberPercentageExtension
  module InstanceMethods
    ENGLISH_VALUE = {}
      %w| zero one two three four five six seven eight nine ten eleven
          twelve thirteen fourteen fifteen sixteen seventeen eighteen
          nineteen |.each_with_index{ |word,i| ENGLISH_VALUE[word] = i }
      %w| zero ten twenty thirty forty fifty sixty seventy eighty
          ninety|.each_with_index{ |word,i| ENGLISH_VALUE[word] = i*10 }
    ENGLISH_VALUE['hundred'] = 100
    def percent_method?(method)
      tokens = method.to_s.split('_')
      return false if tokens.size < 2
      is_percent = tokens[-1] == 'percent'
      tokens[0..-2].collect { |word| is_percent &&= ENGLISH_VALUE.has_key?(word) }
      return is_percent
    end
    def method_missing(method, *args)
      if percent_method?(method)
        method.to_s.split('_')[0..-2].map { |word| ENGLISH_VALUE[word] }.sum * self / 100.to_f
      else
        super
      end
    end
  end
end

We also want respond_to? to be aware of the new *_percent methods, so we include that as well.

def respond_to?(method)
  return true if percent_method?(method)
  super(method)
end

Finally, include the module in both Fixnum and Float:

Fixnum.class_eval do
  include InstanceMethods
end
Float.class_eval do
  include InstanceMethods
end

Till next one.

Tagged as: howto, ruby
Fork me on GitHub