Sep 2015

11

Succinct Ruby

I have heard of developers writing blogs sometimes so that it is a good reference point for themselves in the future. Today's blog is one of those cases where i wanted to document some of the amazing snippets of Ruby Code that i have come across over the years. Here are a few of them.

Enumerator.new

Here is the classic fibnoacci series using Enumerator.new. I am amazed at how clearly the logic comes to the fore in this example.


fibonacci = Enumerator.new do |f|
  i = j = 1
  loop do
    f.yield i
    i,j=j,i+j
  end
end

fibonacci.take(10) # => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
        

Another example to simulate a dice roll with n faces and x times, calculating the total and displaying it.


def dice(s)
  Enumerator.new do |v|
    loop do
      v.yield Random.rand(s) + 1
    end
  end
end

def turn(faces, times)
  dice(faces).take(5).inject(:+)
end

turn(6,5) # => 17
        

Lambda

An excellent use of lambda to keep it DRY! Since both vat and sales_tax are doing the same operation why not reuse it using a lambda.


def calculate_tax(tax)
  return lambda{ |sub_total| sub_total * tax  }
end

sales_tax = calculate_tax(0.8)
vat       = calculate_tax(0.0195)

sales_tax.call(29.99) # => 23.992
vat.call(9.99) # => 0.194805
        

Yield

The following is excellent example of using yield to create a simple benchmark utility


def time_it(operation)
  start_time = Time.now
  yield if block_given?
  end_time = Time.now
  difference = round_time(end_time - start_time)
  puts "#{operation} took #{difference} seconds."
end

def round_time(seconds)
  seconds.round(2)
end

# And then use it like this
puts time_it('Import CSV') { MyApp::CSVImporter.import } # Import CSV took 5.02 seconds.
        

Reduce

The classic MapReduce popularized by Google. In this example we see how easily we can transform an array to a hash using inject (an alias to reduce in Ruby).


# words = %w[Mary had a little lamp a little lamp]
frequency_map = words.inject({}) do |hash, word|
  word_count = hash.has_key?(word) ? hash[word] += 1 : 1
  hash.merge(word => word_count)
end
# frequency_map = {"Mary"=>1, "had"=>1, "a"=>2, "little"=>2, "lamp"=>2}
        

Opening classes and upto

With great power comes great responsibility, so they say ... But isn't it amazing that we can open up any class and extend it. The upto method is another alternative to for loops in Ruby. It takes a block and does until the condition is met - again very unique...


class Integer
  def prime?
    2.upto Math.sqrt(self)  do |i|
      return false if self % i == 0
    end
    true # If you here then it's prime
  end
end

2.prime? # => true
3.prime? # => true
4.prime? # => false
5.prime? # => true
39.prime? # => false
        

Metaprogramming

Here is a version of creating an Enum class in Ruby using metaprogramming. A big thanks to Dave Thomas for the following two examples, got these from one of his early screencast on Ruby metaprogramming.


class Enum
  def self.new
    Class.new do
      def initialize(name)
        @name = name
      end

      def self.const_missing(name)
        const_set(name, new(name))
      end
    end
  end
end

ThreatLevel = Enum.new
Color       = Enum.new

ThreatLevel::Orange == Color::Orange # => false
        

Runtime validations

One of my favorites to create runtime validations in user defined objects. Again i would like to reiterate that using metaprogramming is very debated in the Ruby community but i am highlighting on what is possible here.


module CheckAttributes
  def self.included(klass)
    klass.extend(ClassMethods)
  end

  module ClassMethods
    def attr_checked(attribute, &validation)
      class_eval do
        define_method "#{attribute}=" do |value|
          raise 'Invalid Attribute' unless validation.call(value)
          instance_variable_set("@#{attribute}", value)
        end

        define_method "#{attribute}" do
          instance_variable_get("@#{attribute}")
        end
      end
    end
  end
end

class Person
  include CheckAttributes

  attr_checked :age do |v|
    v >= 18
  end
end

bob = Person.new
bob.age = 17 # => Invalid Attribute (RuntimeError)
bob.age = 19 # => 19
 



Tags: Ruby