I haven’t posted here in a very long time, but I recently got a full-time job using Ruby and Rails (hurray) so Ruby is more on my mind lately. In fact I’ve gotten a better understanding of what life is like for the average Rails developer by seeing how my co-worker Alex writes his Ruby code. Now Alex is a smart guy, he has been doing web-sites for years, is proficient in ColdFusion, PHP , Flash, HTML and CSS, yet his Ruby code is not always as elegant as he or I would like. Of course I’ve been using Ruby for almost 6 years so know it quite well (and that is a big reason why I was hired.)

Still even I find the occasional new nugget and figured this blog would be a good forum to expose some of my new insights. This way other Rails developers like Alex who aren’t as proficient in Ruby as they would like can benefit from my experience.

Recently I was perusing the documentation for the Enumerable module and took a closer look at the grep method. This method is surprisingly more powerful than it might seem at first glance. To learn more, please continue reading this entry…

What the Documentation Says

First, let’s describe the basic workings of the grep method, straight from the Ruby documentation:

enumObj.grep( pattern ) -> anArray
enumObj.grep( pattern ) {| obj | block } -> anArray

Returns an array of every element in enumObj for which Pattern === element. If the
optional block is supplied, each matching element is passed to it, and the
block's result is stored in the output array.

The most obvious use of grep is with arrays of Strings and a RegExp as the argument:

irb(main):001:0> names = ["Joe", "Bill", "Jill", "Susan", "Sam"]
=> ["Joe", "Bill", "Jill", "Susan", "Sam"]
irb(main):002:0> names.grep(/^J/)
=> ["Joe", "Jill"]
irb(main):003:0> names.grep(/^S/) {|name| name.upcase}
=> ["SUSAN", "SAM"]

Getting Deeper

But the key thing to remember is that grep actually uses the === operator when comparing the argument passed to each element in the Array. So any class that intelligently implements that operator can be used:

irb(main):004:0> numbers = [1, 2, 3, 4, 5, 6, 8, 9]
=> [1, 2, 3, 4, 5, 6, 8, 9]
irb(main):005:0> numbers.grep(3..6)
=> [3, 4, 5, 6]
irb(main):006:0> dates = [Date.new(2000, 1, 1), Date.new(2002, 2, 2),
    Date.new(2004, 3, 3), Date.new(2006, 4, 4)]
=> [#<Date: 4903089/2,0,2299161>, #<Date: 4904615/2,0,2299161>,
    #<Date: 4906135/2,0,2299161>, #<Date: 4907659/2,0,2299161>]
irb(main):007:0> dates.grep(Date.new(2001, 1, 1)..Date.new(2005, 1, 1)) {|date| date.to_s }
=> ["2002-02-02", "2004-03-03"]
irb(main):008:0> class Base;end
=> nil
irb(main):009:0> class Child1 < Base;end
=> nil
irb(main):010:0> class Child2 < Base;end
=> nil
irb(main):011:0> class NotAChild;end
=> nil
irb(main):012:0> objects = [Child1.new, NotAChild.new, Child2.new]
=> [#<Child1:0x2df6ce8>, #<NotAChild:0x2df6cd4>, #<Child2:0x2df6cc0>]
irb(main):013:0> objects.grep(Base)
=> [#<Child1:0x2df6ce8>, #<Child2:0x2df6cc0>]

In the above examples the Range#=== and Class#=== operators to grep through numbers, dates and instances of classes.

The Magical Transformation Block

Something that I haven’t yet talked about, but which I’ve used in the examples, is the block passed to grep. This acts much like the block in Enumerable#map, taking a member of the array and returning it transformed in some way. Above I’ve made Strings uppercase and turned dates into more readable Strings, but this block can be as complex as you might need.

Making Your Own Classes “Grep-Friendly”

If you have classes which you might want to use with grep, all you need to do is implement an intelligent === method for whatever you will be passing to grep. In fact, as an example I decided to implement a Magic class which takes a block for use in the === method:

class Magic
  def initialize(&block)
    @block = block
  end

  def ===(other)
    @block.call(other)
  end
end

class Animal < Struct.new(:name, :sound, :class)
  def to_s
    "#{name}'s go '#{sound}'"
  end
end

animals = [
  Animal.new("Cow", "Moo!", "Mammal"),
  Animal.new("Snake", "Hiss!", "Reptile"),
  Animal.new("Dog", "Bark!", "Mammal"),
  Animal.new("Eagle", "Go America!", "Bird"),
  Animal.new("Cat", "Meow!", "Mammal"),
  Animal.new("Shark", "Da Dum, Da Dum, Da Dum!", "Fish")
]
puts animals.grep(Magic.new {|a| a.class == "Mammal"})
# Results in:
# Cow's go 'Moo!'
# Dog's go 'Bark!'
# Cat's go 'Meow!'

Conclusion

I hope this relatively brief look into the Ruby Core was informative and will help any readers produce more elegant and maintainable Ruby code in their applications. As I find other interesting methods and uses I’ll post about them.