Today I wanted to talk about #tap, an addition to Ruby in versions >= 1.9, and how I’ve been using and seeing it used lately.

What is #tap?

The feature is coded like so:

VALUE rb_obj_tap(VALUE obj)
{
  rb_yield(obj);
  return obj;
}

So, in Ruby:

class Object
  def tap
    yield self
    self
  end
end

What is #tap for?

So that looks pretty simple, it just allows you do do something with an object inside of a block, and always have that block return the object itself. #tap was created for tapping into method chains, so code like this:

def something
  result = operation
  do_something_with result
  result # return
end

can be turned into (without modifying the contract of do_something_with):

def something
  operation.tap do |op|
    do_something_with op
  end
end

Where has it gone wrong?

Some early blog posts talking about tap focused on its use for inserting calls to putsinto code without modifying behavior:

arr.reverse # BEFORE

arr.tap { |a| puts a }.reverse # AFTER

This isn’t a great use, not only because it involves debugging your code solely by means of output (instead of a debugger), but also because #tap is so much cooler an idea than just a mechanism for inserting temporary code.

Why I like it:

In additional to the tapping behavior described in the first section, here are some other uses I’m seeing / using:

Assigning a property to an object

Especially useful when assigning a single attribute

# TRADITIONAL
object = SomeClass.new
object.key = 'value'
object

# TAPPED
object = SomeClass.new.tap do |obj|
  obj.key = 'value'
end

# CONDENSED
obj = SomeClass.new.tap { |obj| obj.key = 'value' }

Ignoring method return

Useful when wanting to call a single method on an object and keep working with the object afterwards:

# TRADITIONAL
object = Model.new
object.save!
object

# TAPPED
object = Model.new.tap do |model|
  model.save!
end

# CONDENSED
object = Model.new.tap(&:save!)

Using in-place operations chained

A lot of times, we expand logic in order to use in-place methods like reverse!, but look:

# TRADITIONAL
arr = [1, 2, 3]
arr.reverse!
arr

# TAPPED CONDENSED
[1, 2, 3].tap(&:reverse!)

Conclusion:

Just like anything else, don’t overuse #tap just for kicks. Its tempting to tap everything, but it definitely can make code less readable if used inappropriately. That being said, its a great addition to a Rubyist’s sugar toolkit. Give it a shot!