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