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.
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.
Combinatory method like tap, but able to return a different value?
Define Object#as
:
class Object
def as
yield self
end
end
And now you can write:
def not_sure_this_is_nice_enough_method1
something_complex(a, b, c).as do |obj|
a_predicate_check? ? obj.one_more_method_call : obj
end
end
Ruby Tempfile#tap: What class defines this method and what is it for?
#tap
is defined on Object
https://ruby-doc.org/core-2.3.1/Object.html#method-i-tap
It was introduced in Ruby 1.9. It yields self to the block and then returns self. I think an illustrative example is when it's used to return an object from a method.
You could do this.
def foo
a = []
a.push(3)
a
end
def foo
[].tap do |a|
a.push(3)
end
end
In the first example the array a is returned explicitly and in the second tap is being used to yield the block to self and then return self.
Difference between Kernel#yield_self, yield(self), Kernel#then and Object#tap in Ruby?
The difference between tap
and yield_self
is in what is returned by each of the two methods.
Object#tap
yields self to the block and then returns self. Kernel#yield_self
yields self to the block and then returns the result of the block.
Here are some examples of where each can be useful:
tap
Replacing the need for a result
line at the end of a method:
def my_method
result = get_some_result
call_some_method_with result
result
end
can be written as:
def my_method
get_some_result.tap do |result|
call_some_method_with result
end
end
Another example is initialisation of some object that takes several steps:
some_object = SomeClass.new.tap do |obj|
obj.field1 = some_value
obj.field2 = other_value
end
yield_self and yield(self)
If used inside one of your own methods yield_self
would have the same effect as yield(self)
. However, by having it as a method in its own right this promotes method chaining as an alternative to nested function calls.
This blog post by Michał Łomnicki has some helpful examples. e.g. this code:
CSV.parse(File.read(File.expand_path("data.csv"), __dir__))
.map { |row| row[1].to_i }
.sum
can be rewritten as:
"data.csv"
.yield_self { |name| File.expand_path(name, __dir__) }
.yield_self { |path| File.read(path) }
.yield_self { |body| CSV.parse(body) }
.map { |row| row[1].to_i }
.sum
This can aid with clarity where nested calls are being used for a series of transformations on some data. Similar features exist in other programming languages. The pipe operator in Elixir is a good one to take a look at.
then
#yield_self
might feel a bit technical and wordy. That's why Ruby 2.6 introduced an alias for #yield_self
, #then
.
"something"
.then {|str| str.chars.map {|x| x.ord + 1 }}
.then {|ords| ords.map {|x| x.chr }}
.then {|chars| chars.join }
.then {|str| str + "!" }
.then {|str| str + "!" }
# tpnfuijoh!!
Are there any advantages in using block initialization?
The only reason here is to have more concise code(you don't have to add the instance name before the method call). By using blocks in general you can write very neat, concise and readable code. Some times you can save your code consumers lots of typing and even code logic.
Here is a traditional case!
file = File.open("some_file.txt","w")
file << "more code"
file.close
Compare it to this nice block alternative:
File.open("some_file.txt","w") { |file| file << "adding new stuff" }
It saved the user from the hassle of having to open and close(personally i keep forgetting this one) the file himself. Instead it made him focus on what he wants.
Try to invest blocks in such situations + when you want to write nice DSLs.
The advantage of enumerators over collections
Using an Enumerator makes some things possible that Arrays don't allow.
An Enumerator can represent a sequence that is lazily evaluated, meaning the code to generate an element doesn't get run until the element is needed.
That means that an Enumerator can represent an infinite sequence. For example:
e = Enumerator.new { |y| s = 'a'; loop { y << s = s.succ } }
e.first(5) # => ["b", "c", "d", "e", "f"]
This example shows an infinite sequence, which implies it has to be lazily evaluated because your computer is finite. But even if you aren't working with infinite sequences, the lazy evaluation can still be very helpful for saving CPU time.
Also, an Enumerator can be used an external iterator, which provides all sorts of flexibility in how you use it. (See 7stud's answer for an example.)
Related Topics
M Hartl's Ruby on Rails Tutorial Chapter 5 Custom Title on Home Page
Ruby: How to Load a File into Interactive Ruby Console (Irb)
How to Get the Number of Days in a Given Month in Ruby, Accounting for Year
"Uninitialized Constant" Error When Including a Module
Rails: How to Make Date Strftime Aware of the Default Locale
Only Show Decimal Point If Floating Point Component Is Not .00 Sprintf/Printf
Activemodel::Missingattributeerror Occurs After Deploying and Then Goes Away After a While
How to Get All Class Names in a Namespace in Ruby
How to Use Ruby's Readlines.Grep for Utf-16 Files
File.Open, Open and Io.Foreach in Ruby, What Is the Difference
Ruby Metaprogramming Online Tutorial
Should I Define a Main Method in My Ruby Scripts
How to Drop to the Irb Prompt from a Running Script
Why Is It Best to Store a Telephone Number as a String VS. Integer