Object#tap_if and Object#tap_unless

I originally brought this up in: https://github.com/rails/rails/issues/9067

Rails paved the way for Object#tap and Object#try…I’d like to propose Object#tap_if and its counterpart,Object#tap_unless.

I’ve been following 37signals conventions of tapping variables in the views:

  <% account.owner.tap do |user| %>
...
<% end %>

But, I find myself having to do this a lot…

  <% account.owner.tap do |user| %>
<% if user %>
...
<% end %>
<% end %>

It would be great if we could do…

  <% account.owner.tap_if(:present?) do |user| %>
...
<% end %>
<% account.users.tap_if(:any?) do |user| %>
...
<% end %>

The block would only yield if the method evals to true.

Carlos mentioned that you can add an “if account.owner.present?” at the end…

But there are times when the account.owner (or something else) call is expensive and you don’t want to call it twice.

Any feedback would be much appreciated. Thanks!

What’s wrong with…

  <% if user = account.owner %>
...
<% end %>

or

  <% if users = account.users.presence %>
...
<% end %>

?

Object#try takes a block. It’ll probably take care of most simple cases for you:

  <% account.owner.try do |user| %>
<!-- This is shown only if account.owner is not nil? -->
<% end %>

If running with warnings enabled ruby will raise a warning on these.

Best.

Mike

Thank you Godfrey! This does solve 99% of my cases!

@Andrew Using if, the variable “users” is now available everywhere…I only want it available in my block.

Actually, on second thought, this would do exactly what you wanted, and It’s Just Ruby ™:

<% account.users.any? && account.users.tap do |users| %>

  # ...
<% end %>

The problem (I think) with that is that “#users” could be an expensive call you only wish to make once. And I don’t want to get into memoizing…

<% account.expensive_call_for_users.any? && account.expensive_call_for_users.tap … %>

<% (users = account.users).any? && users.tap do |users| %>

Less pretty, still works.

Why not just

break unless user

inside the block?

Not sure if it should be break, next, or return in that context but you get the idea.

I always prefer earlier returns for such case.