DEV Community

Cover image for Ruby blocks from the Outside In w\ yield
Dedé Menezes
Dedé Menezes

Posted on

Ruby blocks from the Outside In w\ yield

Ruby programming language if full of blocks. Most of the time we will use blocks but almost never define methods that uses them. So when I sit down to study with a friend I took some time to explain how does it work in the internals.

What is a block?

It can also be called an anonymous method. A method without a name.
Think about this 🤔

def do_your_thing(variable)
  variable.upcase
end
Enter fullscreen mode Exit fullscreen mode
do |variable|
  variable.upcase
end
Enter fullscreen mode Exit fullscreen mode

Okay, okay. It's too different. Instead of def there is a do and instead of () we have ||'s 😅

So, what is the goal with this?

The power on this relies on the fact that block can be passed to methods, including those you define. It is a piece of code, a block of code, bounded to a method. It can also be called in different ways but we won't see any of it in here.

The main point is that we can now pass some extra behaviors to some methods! Cool right?

This is what allows us to have one method called #select and only specify how we want to filter when we CALL the method.

students = ['Bia', 'Samantha', 'Jackie', 'Billie', 'Johnny']
students_with_j = students.select do |student|
  student.start_with?('J')
end
p students_with_j #=> ['Jackie', 'Johnny']
Enter fullscreen mode Exit fullscreen mode

Clever hum? Ruby <3

So, how does it work from Outside In?

Well, let's take a look into a fake #timer example. Here is what we wnat to do ⤵️

# We want to create a method 
# to calculate how long 
# took to perform a task

def timer
  # Obviously we will simulate a very long task ;)
  puts 'Starting task now!'
  # let's get the start time
  start_time = Time.now

  # PERFORMING HUGE TASK!
  puts 'Doing dishes...'
  sleep(3) # proccess sleeps for three seconds
  puts 'Done w\ dishes! zo/'

  # Let's get the end time and calculate the elapsed time
  time_end = Time.now
  elapsed_time = time_end - start_time

  puts "It took us: #{elapsed_time}s"
end
Enter fullscreen mode Exit fullscreen mode

Now, we want to use this method to calculate how long will take to perform another task. In this case, we would need to change the method definition because this new task would take only 0.5 seconds. If we keep changing the method for different tasks then it's better to rename the method hueheuue! this method shoud be called one_point_two_task s😅 Obviously it's a joke

So, how can we make this method CUSTOMIZABLE?!

You got it right! Using a BLOCK!

Yield without parameters
timer do
  # Obviously we will simulate a very long task ;)
  puts 'Starting task now!'
  # let's get the start time
  start_time = Time.now

  # Here I will define what should we do in the task!
  # in our case that is where our sleep(2), sleep(1.2) should be!
  # To run the code inside the block we use the keyword ⤵️
  yield

  # Let's get the end time and calculate the elapsed time
  time_end = Time.now
  elapsed_time = time_end - start_time

  puts "It took us: #{elapsed_time}s"
end
Enter fullscreen mode Exit fullscreen mode

Now, when calling the method, we can specify different tasks to be measured ;)

timer do
  puts 'Cooking lunch...'
  sleep(0.2)
  puts 'Done!'
end
Enter fullscreen mode Exit fullscreen mode
Yield with parameters

Let's create another example, a method to greet someone.

def greet(name)
  "Good morning, #{name}"
end

puts greet('Qin')
puts greet('Pedro')
puts greet('Michael')
Enter fullscreen mode Exit fullscreen mode

What if we want now to be able to greet in different languages? Right now my method can only greet in English. Should we change the method name or make it customizable?

You got it right! We can make it customizable using blocks!

The message now will be defined INSIDE THE BLOCK when calling the method. Like the example below ⤵️

message = greet('Chloe') do |name|
  "Bounjour, #{name.upcase}!"
end
puts message
Enter fullscreen mode Exit fullscreen mode

So to access the name passed as an argument, we need to PASS IT to the yield. Doing that will make it available as the PIPE VARIABLE!

def greet(name)
  yield(name)
end

message = greet('Chloe') do |name|
  "Bounjour, #{name.upcase}!"
end
puts message
Enter fullscreen mode Exit fullscreen mode

Conclusion

Blocks in Ruby are a powerful feature that allow you to add customizable behaviors to your methods. Understanding and utilizing blocks allow you to write more flexible and reusable code. Blocks provide a way to pass additional logic to methods. Keep experimenting with blocks, and you'll discover even more creative ways to enhance your Ruby programs.

That's all folks! I hope you liked it!
Happy coding! :)

Top comments (0)