Understanding Tap in Ruby

Understanding tap in Ruby

.tap is here to "perform operations on intermediate results within a chain of methods" (quoting ruby-doc).

In other words, object.tap allows you to manipulate object and to return it after the block:

{}.tap{ |hash| hash[:video] = 'Batmaaaaan' }
# => return the hash itself with the key/value video equal to 'Batmaaaaan'

So you can do stuff like this with .tap:

{}.tap{ |h| h[:video] = 'Batmaaan' }[:video]
# => returns "Batmaaan"

Which is equivalent to:

h = {}
h[:video] = 'Batmaaan'
return h[:video]

An even better example:

user = User.new.tap{ |u| u.generate_dependent_stuff }
# user is equal to the User's instance, not equal to the result of `u.generate_dependent_stuff`

Your code:

def self.properties_container_to_object(properties_container)
{}.tap do |obj|
obj['vid'] = properties_container['vid'] if properties_container['vid']
obj['canonical-vid'] = properties_container['canonical-vid'] if properties_container['canonical-vid']
properties_container['properties'].each_pair do |name, property_hash|
obj[name] = property_hash['value']
end
end
end

Is returning a Hash beeing filled in the .tap block

The long-version of your code would be:

def self.properties_container_to_object(properties_container)
hash = {}

hash['vid'] = properties_container['vid'] if properties_container['vid']
hash['canonical-vid'] = properties_container['canonical-vid'] if properties_container['canonical-vid']
properties_container['properties'].each_pair do |name, property_hash|
hash[name] = property_hash['value']
end

hash
end

what ruby tap method does on a {}

#tap method simply passes an object it was called on to a block. At the end of the block it returns the same object again. This way you can chain operations or restrict variable scope.

{}.tap { |h| h[:a] = 1 }.size # => 1

You were able to chain a next method to this block. And also avoided creating a h variable in your scope.

advantage of tap method in ruby

When readers encounter:

user = User.new
user.username = "foobar"
user.save!

they would have to follow all the three lines and then recognize that it is just creating an instance named user.

If it were:

user = User.new.tap do |u|
u.username = "foobar"
u.save!
end

then that would be immediately clear. A reader would not have to read what is inside the block to know that an instance user is created.

tap method in Rails 3 - Have I understood the API Docs correctly?

The tap method has been in Ruby since 1.8.7:

tap{|x|...} => obj

Yields x to the block, and then returns x. The primary purpose of this method is to “tap into” a method chain, in order to perform operations on intermediate results within the chain.

Note that 1.8.6 did not have Object#tap. Presumably, tap was in older versions of Rails (as a monkey patch on Object) but was added to Ruby itself in 1.8.7. Since 1.8.6 is rather ancient now, the Rails version was deprecated and, in more recent Rails releases, removed entirely.

Object#tap is still around so tap itself has not been deprecated, just the Rails monkey patched version has been removed.

Ruby: tap writes on a read?

Object#tap couldn't be simpler:

VALUE
rb_obj_tap(VALUE obj)
{
rb_yield(obj);
return obj;
}

(from the documentation). It just yields and then returns the receiver. A quick check in IRB shows that yield yields the object itself rather than a new object.

def foo
x = {}
yield x
x
end

foo { |y| y['key'] = :new_value }
# => {"key" => :new_value }

So the behavior of tap is consistent with yield, as we would hope.

Building a Rails scope using `tap`

tap will not work for this.

  • all is an ActiveRecord::Relation, a query waiting to happen.
  • all.where(...) returns a new ActiveRecord::Relation the new query.
  • However checking the documentation for tap, you see that it returns the object that it was called on (in this case all) as opposed to the return value of the block.

    i.e. it is defined like this:

    def tap
    yield self # return from block **discarded**
    self
    end

    When what you wanted was just:

    def apply
    yield self # return from block **returned**
    end

    Or something similar to that.

This is why you keep getting all the objects returned, as opposed to the objects resulting from the query. My suggestion is that you build up the hash you send to where as opposed to chaining where calls. Like so:

query = {}
query[:first_name] = options[:query] if options[:query]
query[:graduated] = options[:graduated] if options[:graduated]
# ... etc.

all.where(query)

Or a possibly nicer implementation:

all.where({
first_name: options[:query],
graduated: options[:graduated],
}.delete_if { |_, v| v.empty? })

(If intermediate variables are not to your taste.)

What does brew tap mean?

The tap command allows Homebrew to tap into another repository of formulae. Once you've done this you've expanded your options of installable software.

These additional Git repos (inside /usr/local/Homebrew/Library/Taps) describe sets of package formulae that are available for installation.

E.g.

brew tap                     # list tapped repositories
brew tap <tapname> # add tap
brew untap <tapname> # remove a tap


Related Topics



Leave a reply



Submit