Skip to content

Instantly share code, notes, and snippets.

Last active April 15, 2017 17:48
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
What would you like to do?

(steps on soap box)

I think Ruby's Object#tap method is abused. I see code like this all the time:

obj = do |o|
  o.blah = true

What good does tap do here? It saves 0 lines of code. It makes it complex for no reason. And if it's a particularly long block, it just makes it harder to see what actually gets returned. To me, it feels like trying to be clever for the sake of being clever.

Compare it to the "traditional" way:

obj =
obj.blah = true

Same number of lines of code. Same return value. Only 1 local variable (as opposed to needing to come up with 2 different names like above with obj and o). No unnecessary complexity. I prefer this simpler version.

(steps off soap box)

Here's a better, more appropriate use of tap:

  .map { |o| }
  .more_transform           .tap { |things| puts "transformed: #{things.inspect}" }
  .even_more_transformations if to "tap into" a method chain (to quote the official Ruby documentation).

This can be useful with debugging a series of ActiveRecord chained scopes, e.g.

  .active                      .tap { |users| puts "Users so far: #{users.size}" }
  .non_admin                   .tap { |users| puts "Users so far: #{users.size}" }
  .at_least_years_old(25)      .tap { |users| puts "Users so far: #{users.size}" }

This makes it super easy to debug at any point in the chain without having to store anything in in a local variable nor requiring much altering of the original code.

And lastly, use it as a quick and unobtrusive way to debug without disrupting normal code execution:

def rockwell_retro_encabulate
  # Will debug `@result` just before returning it.
Copy link

bparanj commented Jan 6, 2016

How is this better that just doing 'puts result' ?

Copy link

why not just

# one line of code { |o| o.blah = true }

# or use cases like this
obj ||= do |o|
  o.blah = true
  o.whatever = true

Copy link

Trivial examples are trivial. Usually I find myself using tap in order to encapsulate an object's simple properties inside of a complicated context, or to encapsulate logic. Like so:

@thing_i_hate = do |thing| = 'Apple'
  thing.type = 'fruit'

@thing_i_love = do |thing| = 'Pear'
  thing.type = 'fruit'
  if today == :tuesday
    thing.share = true
    thing.share = false

...which makes it easy to copy-paste properties between the objects because they are consistent within the tap blocks. I find the wrapping logic to make it easier for me to understand what is happening because the context is very constrained. I find this especially useful when writing RubyMotion and defining UIViewControllers or whatever where I just tap them as view but their they're in the exterior context as some really long variable name.

But yes, I've also seen tap abuse. Though it's often because someone is pre-expecting additional complexity, or because they removed complexity but didn't do a full refactor.

Thanks for sharing!

Copy link

elia commented Jan 15, 2016

Nice post!

Beyond the point of the article, but I think it's worth pointing out that p returns the value passed, so:

return thing


return p(thing)


return thing.tap(&method(:puts))

all return the same value.

Copy link

I think one example of using tap to initialize in a practical way is the following:

some_confirming_variable = true some_confirming_variable).tap do |os|
    os.sooner = "sooner"
    os.later = "later"
    os.you_get_it = "yeah"

Copy link

@ordinaryzelig Nice article. I often use tap for Array#uniq! because the return value isn't consistent. It returns nil when nothing was changed. For example, list.tap &:uniq! will always return the list.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment