Two Powerful Iterator Methods

by Emiliano CoppoJuly 13, 2013
Learn how check(item) and check(item) methods help to optimize Ruby code, increase its readability, and simplify support.

Simplifying development routines

Everybody who deals with Ruby knows that collections can be tedious. However, thanks to Ruby, we have a large arsenal to deal with them! This post overviews two methods that can greatly simplify your development routines. You will get real-life examples that demonstrate how to optimize the code, as well as some tips to increase code readability and enhance system support in the future.

Recently, I’ve written a small JSON parser of the Flickr public feed, so I’ll use some methods of that module as examples. Basically, the module had to fetch a JSON object returned by the Flickr API and parse it to a Ruby JSON object checking some validations. This kind of functionality doesn’t take more than a dozen of Ruby lines, but it’s a good example to achieve our goal.

We’ll focus on two methods of the module:

  • json_items returns an array of hashes. Each hash represents a feed item from the Flickr API.
  • check(item) checks if the given item hash satisfies some validations.

We’ll also skip all the URL encoding and some validation stuff to focus on the iterative methods.

 

The check(item) method

First, lets take a look at the check method. The goal of this method is to loop inside a item (a hash) and update it on the fly. A valid approach could look as shown below.

def check(item)
  new_item = {}
  item.each do |key, value|
    new_item[key] = value.empty? ? "No data" : value
  end
  new_item
end

This code certainly works, but it also has some design issues like a temp (and unnecessary) hash.

As Ruby developers, we should take care of the Ruby API in such cases. The code bellow can be good as a start point, but it does not take long to realize that it “smells.” So if we look at the hash doc we can find an alternative and a prettier solution with the update method:

def check(item)
  item.update(item) do |key, value|
    value.empty? ? "No data" : value
  end
end

Here, we’re using the update method of hash that is employed when you want to update the content of a hash based on some other one. In this case, we only have a single hash, so we apply the update method to itself. By using the update method, we prevented the creation of an unnecessary hash, and we got our code more readable and clearer.

 

The json_items method

Now it’s time to explore the json_items method. As previously stated, this method should return an array of hashes that must be validated with our check method. A possible code could be as illustrated below.

def json_items
  new_items = []
  json = get_some_json_data
  json.each do |item|
    new_items << check(item)
  end
  new_items
end

I really hate this repetitive code snippet, it’s very common when you want to create an array based on some other one.

def method_name
  temp_array = []
  original_array.each do |item|
    temp_array << some_stuff_with(item)
  end
  temp_array
end

Thanks to Ruby, we have an awesome method for array objects when we have to deal with this kind of snippets: inject. This awesome method allows us to make magical things. For instance, if we want to know the average word length of a document or a string, we can do as follows:

def average_word_length
  total = 0.0
  words.each{ |word| total += word.size }
  total / word_count
end

This can be done more concisely with inject.

def average_word_length
  total = words.inject(0.0){ |result, word| word.size + result}
  total / word_count
end

As its name suggests, inject “injects” an initial object (0.0 in the example above) and uses it as the initial value of the “memo” (result in the code), then iterates the given block like each of the methods does. Inject is very flexible, if you do not explicitly specify an initial value in the inject, then the first element of collection is used as the initial value.

def sum
  (1..10).inject{ |sum, n| sum + n }  #returns 55 (= 1+2+...+10)
end

You can also use inject with hashes. When running inject on a hash, the latter is first converted to an array before being passed through. By applying it to the json_items method, we get:

def json_items
  json = JSON.parse(get_json)["items"]
  items = json.inject([]) do |items, item|
    items << check(item)
  end
end

So, with inject, we no longer need to instantiate a temp var, which allows us to build a new array on the fly and have a more concise and readable code.

Inject inherently projects a set of collection values to an unique value. In other words, it resembles a many-to-one function. For this reason, inject has a well-known alias—reduce. In maths and other programming languages, it also has other names like fold, accumulate, aggregate, and compress. This kind of functions analyzes a recursive data structure and recombines through use of a given combining operation the results of recursively processing its constituent parts, building up a return value.

Therefore, the json_items example is not the best example to use inject, because we’re trying to achieve a one-to-one conversion. In such cases, we should use other methods, such as map or collect, that fit better with what we’re trying to do.

def json_items
  json = JSON.parse(get_json)["items"]
  json.map{ |item| check(item) }
end

For more details on hash update, please check out this documentation and this overview. More isights into enumerable inject are available in these docs, as well.

Ruby provides us awesome methods, we should use them wisely and follow the Ruby philosophy. So, I encourage you to employ these methods to make clearer and beautiful Ruby apps!

 

Further reading

 

About the author

Emiliano Coppo is interested in developing Ruby and Ruby On Rails Applications. He is proficient in JavaScript, HTML, CSS, jQuery, jQuery UI and Mobile, PostgreSQL, MySQL. Emiliano describes himself as a passionate coder, always trying to learn new technologies and looking for challenging projects. Find him on GitHub.


This blog post was written by Emiliano Coppo with suggestions and tips from
Sergey Avseyev and Joaquín Vicente.