Pros and Cons of Ruby's .tap Method
Tags: ruby, rails, healthydebate, tutorial
Published: 2019-11-25 02:26:29

---
title: "Pros and Cons of Ruby's .tap Method"
published: true
description: exploring the situational pros and cons of ruby's tap method
tags: ruby, rails, healthydebate, tutorial
---

![anime gif of water running from a faucet](https://data.whicdn.com/images/105099972/original.gif)

During a recent technical challenge, I got the chance to learn about and use Ruby's `.tap` method. This method allows you to "tap" into the result of a method call, usually to **perform additional operations on that result**, or to **debug that result (i.e. by printing to the console)**.

A quick Google search for `ruby tap method` will return a lot of articles and StackOverflow discussions about when to use `.tap`. There's a pretty clear [divide between folks](https://stackoverflow.com/a/17493351) [who think it's useful for communicating intent](https://stackoverflow.com/a/30221768) [and thus improving readability](https://medium.com/aviabird/ruby-tap-that-method-90c8a801fd6a), and others [who think it is overly-used](https://redningja.com/dev/rubys-object-tap-a-better-use) / [rarely-useful syntactic sugar](https://www.ruby-forum.com/t/tap-method-good-or-bad-practice/228410) [that harms readability](https://stackoverflow.com/a/17494191).

In this post, I'll explore some of the pros and cons of the `.tap` method.

![anime gif of water pouring from faucet into two cupped hands](https://66.media.tumblr.com/4969e0b880e1d90247561146c6e548e8/tumblr_n8dt1aWi0C1sg6db3o1_500.gifv)

# What is the .tap method?
[From the Ruby documentation for Object](https://docs.ruby-lang.org/en/2.4.0/Object.html#method-i-tap):
>**tap{|x|...} → obj**
>
>Yields self to the block, and then returns self. The primary purpose of this method is to “tap into” a method chain, in order to perform operations on intermediate results within the chain.

So, when we call `.tap` on an Object, we can insert extra operations before any other code executes. This is especially useful with things like ActiveRecord creation/lookups, where we may immediately want to do something to a new/found record.

For example, if we have a library app with Book and Checkout models, we could check for an existing Checkout record with `Checkout.find_by(book: book)`, then use `.tap` to create a block to manipulate the found Checkout (and the Book we used to find it!):
```ruby
Checkout.find_by(book: book).tap do |checkout|
  checkout.update!(check_in_date: date)
  book.update!(checked_out: false)
end
```


## Pros
![gif of cat drinking water from a faucet](https://starecat.com/content/wp-content/uploads/cat-drinking-tap-water-drops.gif)
* **wrap ActiveRecord creation/lookup patterns in a block that communicates intent**
	* [per this StackOverflow example](https://stackoverflow.com/a/17493351), this:
		```ruby
		user = User.new.tap do |u|
		  u.build_profile
		  u.process_credit_card
		  u.ship_out_item
		  u.send_email_confirmation
		  u.blahblahyougetmypoint
		end
		```
		could be easier to understand at a glance than seeing this:
		```ruby
		user = User.new
		user.build_profile
		user.process_credit_card
		user.ship_out_item
		user.send_email_confirmation
		user.blahblahyougetmypoint
		```
* **provide a way to debug code step-by-step (as an alternative to `pry`)**
	* [Again from the Ruby documentation for `.tap`](), this example is designed to write step-by-step outputs to the console to help us debug:
		```ruby
		(1..10)                .tap {|x| puts "original: #{x.inspect}"}
		  .to_a                .tap {|x| puts "array: #{x.inspect}"}
		  .select {|x| x%2==0} .tap {|x| puts "evens: #{x.inspect}"}
		  .map {|x| x*x}       .tap {|x| puts "squares: #{x.inspect}"}
		``` 

## Cons
![gif of polygon-rendered skeleton in a bathtub turning a faucet handle](https://media3.giphy.com/media/l0HluUOXgxNb6dcis/giphy.gif?cid=790b761194fab3ff56439c6e95236611d1426e3c30d78722&rid=giphy.gif)
* **limited use because sometimes ActiveRecord patterns are more recognizable/readable than the .tap syntax**
	* [per this soapbox rant, the "traditional" pattern may be more recognizable](https://redningja.com/dev/rubys-object-tap-a-better-use) to people familiar with Rails, despite (or perhaps because of) the redundant return of `self` at the end:
		```ruby
		obj = SomeClass.new
		obj.blah = true
		obj
		```
* **when debugging, must carefully format to be readable without disrupting the rest of the code (as intended)**
	* [per the same article as the previous con, using `.tap` to debug chains of ActiveRecord methods](https://redningja.com/dev/rubys-object-tap-a-better-use) is very useful, and doesn't get in the way of reading the code it's testing--but writing this code, and *keeping it tidy and readable* while maintaining/changing it, is extra effort:
		```ruby
		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')
		```


# Use case: character-generator API
In this use case, I build a character-generator API where Players can create randomly-generated Characters. Here, we will see two places to (potentially) use `.tap`:
1. When creating a new Character instance, to more clearly communicate our intent behind several variables, and to reduce some redundant code
2. When using a set number of points to assign to a Character's stats, while keeping track of (a) the random "6-sided die roll", (b) the remaining number of points, and (c) the Character's current and remaining stats.

## API overview: models and relationships
Our two main models will be Players and Characters.
* **Players** simply have a `user_name` and a `display_name`:
	```ruby
	# /db/schema.rb
	
	  create_table "players", force: :cascade do |t|
	    t.string "user_name"
	    t.string "display_name"
	    t.datetime "created_at", precision: 6, null: false
	    t.datetime "updated_at", precision: 6, null: false
	  end
	```
* **Characters** have a name, 4 stats, and a Player they belong to:
	```ruby
	# /db/schema.rb

	  create_table "characters", force: :cascade do |t|
	    t.string "name"
	    t.integer "strength"
	    t.integer "dexterity"
	    t.integer "intelligence"
	    t.integer "charisma"
	    t.datetime "created_at", precision: 6, null: false
	    t.datetime "updated_at", precision: 6, null: false
	    t.integer "player_id", null: false
	    t.index ["player_id"], name: "index_characters_on_player_id"
	  end
	```

## RandomCharacterGenerator service object
We will be using `.tap` inside a service object, `RandomCharacterGenerator`, which will provide a `.new_character()` method that takes in a name and player, and automatically creates and saves a Character in the database:
```ruby
# /app/services/random_character_generator.rb

class RandomCharacterGenerator

    def new_character(name, player)
        character = Character.new(name: name, player: player)
        stats_array = ["strength", "dexterity", "intelligence", "charisma"]
        points_pool = 9
        max_roll = 6
        roll_stats(character, stats_array, points_pool, max_roll)
        character.save!
        character
    end


    private

    def roll_stats(character, stats_array, points_pool, max_roll)
        stats_array.each_with_index do |stat, index|
            roll = rand(1..max_roll)
            remaining_stats = (stats_array.length - 1) - index
            if remaining_stats == 0
                character[stat] = points_pool
                points_pool = 0
            elsif points_pool - roll < remaining_stats
                max_points = points_pool - remaining_stats
                character[stat] = max_points
                points_pool -= max_points
            else
                character[stat] = roll
                points_pool -= roll
            end
        end
    end
    
end
```

Now let's look at some places where we can use `.tap` to **(potentially)** tie our code together more clearly!
![gif of popeye the sailor tying two streams of water together in a knot](https://media3.giphy.com/media/lVBtp4SRW6rvDHf1b6/giphy.gif?cid=790b7611435a774e18d01d89f9ca828565ee35307462fd2e&rid=giphy.gif)

# Case 1: communicating intent and context of variables during `Character.new()`
In `.new_character()`, we're performing several operations as soon as we create a new Character instance. Right after `Character.new()`, we:
* define 3 variables (stats_array, points_pool, max_roll)
* call the private method `roll_stats()` with those 3 variables
* call `save!` on our new Character
* return the Character

However, it's a little unclear whether those 3 new variables need to be used anywhere else. Plus, you may be the kind of person who hates that redundant hanging line that just returns `character`! 

So, let's use `.tap` to wrap those variables, the `roll_stats()` call that uses them, and the `save!` call in a block--and eliminate that final `character` line while we're at it:
```ruby
# /app/services/random_character_generator.rb

    def new_character(name, player)
        Character.new.tap do |character|
            stats_array = ["strength", "dexterity", "intelligence", "charisma"]
            points_pool = 9
            max_roll = 6
            roll_stats(character, stats_array, points_pool, max_roll)
            save!
        end
    end
```
Now, we have several potential advantages:
* Our intent with `stats_array`, `points_pool`, and `max_roll` is clearer, because the variables only exist in the `.tap` block's scope, and are immediately passed into `roll_stats()`. **We can be more confident that those variables won't be needed anywhere else in our code.**
* We know that `.tap` always returns the object it's called on, so we don't have to worry about forgetting to explicitly return `character` at the end of `new_character()`. **We can be more confident that our new Character will be returned as expected.**

However...
* We saved...**0 lines of code.**

### Pro or con: do you think the *ORIGINAL* or *NEW* version is more readable? (Feel free to comment below!)



# Case 2: debugging values step-by-step during iteration in `roll_stats()`
In the private method `roll_stats()`, we have several values that need to be tracked as each Character stat is randomly assigned a number 1 through 6:
* how many points are left to allocate
* how many stats are left to assign points to (so no stat ends up with 0)
* what number 1 through 6 is randomly "rolled"
* is the stat being correctly assigned the "rolled" value
* is the number of points left to spend decreasing by the "rolled" value

Faced with the option to insert a `puts "value: #{value}"` or `pry` line in-between each step, I realized that using `.tap` instead would let me add debugging messages to the console **without breaking up the code I'm testing**.

So, after each step (and in a few extra places), I tabbed way over to the side and added a `.tap` call with a `puts "value: #{value}"` message to check the values step-by-step: _**(scroll to the right)**_
```ruby
# /app/services/random_character_generator.rb

    def roll_stats(character, stats_array, points_pool, max_roll)
        stats_array.each_with_index do |stat, index|
            roll = rand(1..max_roll)                                    .tap {|r| puts "roll: #{r}"}
            remaining_stats = ((stats_array.length - 1) - index)        .tap {|r| puts "remaining_stats: #{r}"}
                                                                        .tap {|r| puts "points_pool (before): #{points_pool}"}
            if remaining_stats == 0
                character[stat] = points_pool
                points_pool = 0
            elsif points_pool - roll < remaining_stats
                max_points = points_pool - remaining_stats
                character[stat] = max_points
                points_pool -= max_points
            else
                character[stat] = roll
                points_pool -= roll
            end                                                         .tap {|r| puts "character[#{stat}]: #{character[stat]}"}
                                                                        .tap {|r| puts "points_pool (after): #{points_pool}\n\n"}
        end
    end
```

Now, when I'm running tests with RSpec, I can get this console output to help me debug:
```
[18:36:52] (master) tap-example-character-creator-api
// ♥ bundle exec rspec
roll: 3
remaining_stats: 3
points_pool (before): 9
character[strength]: 3
points_pool (after): 6

roll: 2
remaining_stats: 2
points_pool (before): 6
character[dexterity]: 2
points_pool (after): 4

roll: 1
remaining_stats: 1
points_pool (before): 4
character[intelligence]: 1
points_pool (after): 3

roll: 2
remaining_stats: 0
points_pool (before): 3
character[charisma]: 3
points_pool (after): 0

....

Finished in 0.02678 seconds (files took 3.61 seconds to load)
4 examples, 0 failures
```


The main advantage here is that **the readability of the original code is not changed**--in fact, it's easy to ignore that those `.tap` calls are even there!

However, it took time to format those `.tap` calls to be out of the way but still formatted in a readable way--**and any changes to those lines of code will inevitably change the indentation that keeps them lined up.** Maintaining that readability, or wanting to comment out/delete those `.tap` calls, costs extra time and effort.

### Pro or con: do you think it takes *MORE* or *LESS* time to write and maintain this syntax for debugging? (Feel free to comment below!)


# Conclusion
Honestly, I can't sum up the `.tap` method any better than [Kartik Jagdale does in this article](https://medium.com/aviabird/ruby-tap-that-method-90c8a801fd6a), so I'll simply quote him:
> Just like any other ruby syntactic sugar , I would say `tap` is a pretty cool ruby method which can not just be used for readability but also to debug chained methods. Give it a shot.

![anime gif of two golden faucets powerfully streaming water](https://data.whicdn.com/images/302979371/original.gif)


### Special thanks to [Anthony Hernandez](https://www.antfeedr.com/) for introducing me to the .tap method! :)

## Further reading/links/discussions/debates about Ruby's .tap method:
* [https://docs.ruby-lang.org/en/2.4.0/Object.html#method-i-tap](https://docs.ruby-lang.org/en/2.4.0/Object.html#method-i-tap)
* [https://stackoverflow.com/questions/17493080/advantage-of-tap-method-in-ruby](https://stackoverflow.com/questions/17493080/advantage-of-tap-method-in-ruby)
* [https://redningja.com/dev/rubys-object-tap-a-better-use](https://redningja.com/dev/rubys-object-tap-a-better-use)
* [https://medium.com/aviabird/ruby-tap-that-method-90c8a801fd6a](https://medium.com/aviabird/ruby-tap-that-method-90c8a801fd6a)
* [https://www.engineyard.com/blog/five-ruby-methods-you-should-be-using](https://www.engineyard.com/blog/five-ruby-methods-you-should-be-using)
* [https://www.ruby-forum.com/t/tap-method-good-or-bad-practice/228410](https://www.ruby-forum.com/t/tap-method-good-or-bad-practice/228410)
* [https://www.rubyguides.com/2017/10/7-powerful-ruby-methods/](https://www.rubyguides.com/2017/10/7-powerful-ruby-methods/)
* **GitHub repo for the character-generator API use case:** [https://github.com/isalevine/rails-tap-example-character-api](https://github.com/isalevine/rails-tap-example-character-api)

### Got any tips, tricks, or instances for using Ruby's .tap method? Please feel free to comment and share below! <3