Ruby Detect Enumerable (AKA Ruby Find enumerable)

Send to friend

I love Ruby’s enumerable methods.

Enumerable#Detect is probably one of the least well known, and thus most people never use it.

It’s the exact same thing as Enumerable#Find. It’s actually stupid simple.

Of course, it’s not as glamorous as superstars like map or select, but as I explain below, it’s a nice way to remove an additional method call.

How to use it

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# Theory
[1,2,3].detect{|i| i == 1} #returns the first element for which the block returns true

#Practice
class Person; attr_accessor :special; end

p1 = Person.new; p2 = Person.new; p3 = Person.new
p1.special = p2.special = false 
p3.special = true

[p1,p2,p3].detect{|p| p.special} === p3
#=> true

# Ruby doc

(1..10).detect  {|i| i % 5 == 0 and i % 7 == 0 }   #=> nil
(1..100).detect {|i| i % 5 == 0 and i % 7 == 0 }   #=> 35

# Replacement for...
>> [p1,p2,p3].select{|p| p.special}.first
is the same as
>> [p1,p2,p3].detect{|p| p.special}

Real life use:

AttachmentFu

1
      thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ }

ActiveRecord

1
          finder_class = class_hierarchy.detect { |klass| !klass.abstract_class? }

Implementation

Rubinius detect code here

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

  ##
  # :call-seq:
  #   enum.detect(ifnone = nil) { | obj | block }  => obj or nil
  #   enum.find(ifnone = nil)   { | obj | block }  => obj or nil
  #
  # Passes each entry in +enum+ to +block+>. Returns the first for which
  # +block+ is not false.  If no object matches, calls +ifnone+ and returns
  # its result when it is specified, or returns nil
  #
  #   (1..10).detect  { |i| i % 5 == 0 and i % 7 == 0 }   #=> nil
  #   (1..100).detect { |i| i % 5 == 0 and i % 7 == 0 }   #=> 35

  def find(ifnone = nil)
    each { |o| return o if yield(o) }
    ifnone.call if ifnone
  end

  alias_method :detect, :find

Dead simple.

Pro Tip

You may notice from the Rubinius source that detect will call any method that you pass in as an argument if the block doesn’t return true for any of the elements.

This lets us do fun stuff like:

1
2
3
>> [7,1,4].detect(lambda{puts 'Oops'}){|number| number == 42}
Oops
=> nil

And of course, we could even return something.

1
2
>> [7,1,4].detect(lambda{return 'Oops'}){|number| number == 42}
=> "Oops"

Interesting. That’s all for now though.