Skip to content

Instantly share code, notes, and snippets.

@ordinaryzelig
Last active April 15, 2017 17:48

(steps on soap box)

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

obj = SomeClass.new.tap do |o|
  o.blah = true
end

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 = SomeClass.new
obj.blah = true
obj

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:

list
  .map { |o| o.foo.bar }
  .reverse
  .more_transform           .tap { |things| puts "transformed: #{things.inspect}" }
  .even_more_transformations

...as 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.

User
  .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}" }
  .residing_in('USA')

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
  provide_inverse_reactive_current
  synchronize_cardinal_graham_meters
  @result.tap(&method(:puts))
  # Will debug `@result` just before returning it.
end
@elia
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

and

return p(thing)

and

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

all return the same value.

@davidimoore
Copy link

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

some_confirming_variable = true

OpenStruct.new(confirmation: some_confirming_variable).tap do |os|
if(some_confirming_variable)
    os.sooner = "sooner"
    os.later = "later"
    os.you_get_it = "yeah"
  end
end

@austinthecoder
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