(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
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:
...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 asview
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!