Two Powerful Iterator Methods
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
- How to Run Capybara Tests for Ruby Applications in Remote Browsers
- How to Add PostgreSQL Full-Text Search to Ruby Apps and Optimize Its Performance
- Let’s Test It Well: Simply and Smartly
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.
Sergey Avseyev and Joaquín Vicente.