Erik Trautman logo crest shield seal

Erik Trautman

“Everything you can imagine is real.”
-- Pablo Picasso

Ruby Explained: Iteration

This post will get into Ruby loops and flow control

You can assemble code, tell the program which parts of it to execute, and wrap it all up in a method. There’s still something missing… what if you want to make something happen a whole bunch of times? You certainly don’t just run the method again and again manually. Luckily we’ve got several standard ways of iterating through a piece of code until we tell the program to stop.

You should understand the basic iterators for and while and understand how to use #each and #times. We’ll talk more about blocks and the other Ruby iterators like #map and #select in the next posts, so it should be more obvious how #each and #times work after reading that.

A loop is really just code that will run a number of times until some condition is met. A variable is typically used to keep track of which iteration you are on or to otherwise increment until the condition is reached. This is called the index variable.

loop is the most basic way to loop in Ruby and it’s not used all that much because the other ways to loop are much sexier. loop takes a block of code, denoted by either { ... } or do ... end (if it’s over multiple lines). It will keep looping until you tell it to stop using a break statement:

> loop { puts "this will not stop until you press CTRL+c" }
this will not stop until you press CTRL+c
this will not stop until you press CTRL+c
... and so on

> i=0                   # Our index variable
> loop do
>   i+=1
>   print "#{i} "
>   break if i==10
> end
1 2 3 4 5 6 7 8 9 10 => nil

while performs a similar function but in a much more compact way, by allowing you to specify the condition that must be true to keep looping, and you’ll find yourself using it much more in your own code. It doesn’t actually take a formal block of code, just runs everything until it reaches its end.

Just remember to declare the variable(s) you’ll be using (or they’ll get reset with each iteration) and to increment at some point (or you’ll get stuck in an infinite loop… use ctrl+c to break in Terminal):

> i=1
> while i < 5
>   print "#{i} "
>   i+=1
> end
1 2 3 4 => nil

until is almost identical to while but, instead of running as long as the specified condition is true, it runs as long as the condition is false

for is a looping mechanism present in lots of other languages but it gets de-emphasized in Ruby and you don’t see it used much. A common use is to loop over every number in a range. You name the variable that holds the current number at the top and can then access it from inside the loop:

> for a_number in (1..3)
>   print "#{a_number} "
> end
1 2 3 => 1..3

Things get more interesting when you realize that most of your loops will probably involve looping over each element in either an array or a hash of items. Ruby knows this and made it super easy for you by specifying the #each method that you call directly on that array or hash. #each will automatically pass whichever item it is currently on into your code block. That item will be named whatever name you specify inside the pipes | name_goes_here |:

> guys = ["Bob", "Billy", "Joe"]
> guys.each do |current_name|    # better to call it just "name" in your code
>   print "#{current_name}! "
> end
Bob! Billy! Joe! => ["Bob", "Billy", "Joe"]  # returns original array

Many other loops are just you trying to do something a certain number of times (which was the case in our for loop example). In that case, Ruby has the simplest possible method for you: #times. If you pipe in an argument, it will take the value of the current iteration (starting from zero):

> 5.times do |jump_num|
>     print "Jump #{jump_num}!"
> end
Jump 1!Jump 2!Jump 3!Jump 4!Jump 5!=> 5

A couple other methods with similar purposes to #times that you see less frequently:

  • #upto, as in 1.upto(4) { |current_number| ...some code... }, just like #times but you choose the starting and ending point instead of always starting at zero.
  • #downto, similar to #upto but… down… to….

Your best friends early on will be while for anything that needs to run until a certain condition is reached (like winning the game), #each for any time you want to do stuff with every item in an array or hash, and #times for the simple cases when you just want to do something a fixed number of times.

Because you may want some additional control over your loops, use these statements to jump in and out of them for certain abnormal conditions:

  • break will stop the current loop. Often used with an if to specify under what conditions to do that.
  • next will jump to the next iteration. Also usually used with an if statement.
  • redo will let you restart the loop (without evaluating the condition on the first go-through), again usually with some condition
  • retry works on most loops (not while or until) similarly to redo but it will re-evaluate the condition before running through the loop again (hence try instead of do).
  • NOTE: Do NOT use return to exit a loop, since that will exit the whole method that contains it as well!

Nesting loops occurs when one goes inside another, so you execute the entire inner loop for each iteration of the outer loop. You’ll see those for "two-dimensional" problems, like searching through arrays within arrays, but if you find yourself nesting too often or too deeply, you probably need to reexamine how you’ve structured your solution overall.

Here’s an example that goes through the comments in a hypothetical blog and captures a preview from each of them. It uses some Rails methods to get the posts and comments then loops to tease out the previews:

def comment_previews
  comment_previews = []
  posts = Post.all              # array of all blog posts
  posts.each do |post|
    comments = post.comments    # array of that post's comments
    comments.each do |comment|
      comment_previews << comment[0..80] 

*The “Ruby Explained” posts are designed to be a sort of “In-Plain-English” version of key Ruby concepts which are usually covered in other introductory texts but rarely for free and often incompletely. When I’m learning a new thing, I usually want someone to explain it to me like I’m a five year old because that’s the best way to make sure nothing gets missed. This is my attempt to pass that same sentiment on to you. Let me know if there’s anything I can improve.*

If you’re just getting interested in this stuff, check out The Odin Project for a free curriculum to learn web development.