There is some methods that are not very known among ruby developpers.
One of them is Enumerable#grep method.
I would like to share one of the use case I have to face with, and how I implement grep.
Purpose, I have a Hash representing a simple network
I’d like to select all the people related to :patrick with network label as key.
# Say I have this hash h = { :work => { :patrick => [:joana, :elvis] }, :football => { :patrick => [:john, :fred, :marcus] , :john => [:paul, :patrick]}, :friends => { :paul => [ :joana, :elisabeth]} } # I want this result : { :work => [:joana,:elvis], :football => [:john, :fred, :marcus] }
A solution is to iterate throug each values then select :patrick
h.inject({}) do |memo,(k,v)| if v.has_key?(:patrick) memo.merge(k => v[:patrick]) else memo end end
Though it works, I think this piece code is not “rubyesque”.
Well, this is a basic case but imagine you have to select X number of variables or more ( :patrick,:paul … etc ) or worse select with a more complex criteria.
After digging into the rdoc documentation, it seems that Enumerable#grep matches our requirements :
- It takes a parameter to compare with each elements of the Enumerable
- It can take a block that returns the value of matching elements
Well, how can we implement that ?
This code below demonstrate why I love Ruby !
class NetworkPattern def initialize(*args) @matches = args end def ===(pair) key, value = pair value.values_at(*@matches).any? end def result(hash) hash.values_at(*self).compact.flatten end def exec(hash) Hash[ hash.to_a.grep(self){|k,v| [k,self.result(v)] } ] end def to_a @matches end end pattern = NetworkPattern.new(:patrick) Hash[ h.to_a.grep(pattern){|k,v| [k,pattern.result(v)] } ] => { :work => [:joana, :elvis], :football=>[:john, :fred, :marcus]} # With somer refactoring, we implement NetworPattern#exec and use it pattern.exec(h) => { :work => [:joana, :elvis], :football=>[:john, :fred, :marcus]}
Well it may seem more complicated but if you want to select both :patrick and :paul relationships, the code does not change howmany arguments you pass in NetworkPattern.new
pattern = NetworkPattern.new(:patrick, :paul) pattern.exec(h) => { :work => [:joana, :elvis], :football =>[:john, :fred, :marcus], :friends =>[:joana, :elisabeth]}
All the logic belongs to the class NetworkPattern and it uses Enumerable#grep with block return.
In your project you can use something like that since it can be easily tested and more readable.

