Si ya tienes alguna experiencia de trabajo con Ruby, probablemente has tenido que lidiar con las colecciones.
Las colecciones pueden ser tediosas. Pero GAR (Gracias a Ruby) disponemos de un amplio arsenal para tratar con ellas!
En este post voy a mostrarte dos métodos iteradores de Ruby que realmente me agradan, sobre todo por su sencillez y belleza.

Mi caprichoso ejemplo

Hace poco, tuve que hacer un pequeño parser JSON para el feed público de Flickr, así que voy a usar algunos métodos de ese módulo como ejemplo para nuestro artículo.
Básicamente, el módulo tenía que ir a buscar un objeto JSON devuelto por la API de Flickr, y parsearlo a un objeto JSON de Ruby, chequeanbdo algunas validaciones.
Este tipo de funcionalidad no toma más de una docena de líneas de Ruby, pero es un buen ejemplo para lograr nuestro objetivo.

Nos centraremos en dos métodos para el módulo:

  • json_items: devuelve un array de hashes. Cada hash representa un elemento del feed de la API de Flickr.
  • check(item): comprueba si el hash dado satisface ciertas validaciones.

También nos saltaremos toda la codificación de la URL y algunas cosas de la validación para centrarnos en los métodos iterativos.
En primer lugar, echemos un vistazo al método de verificación. El objetivo de este método es crear un bucle en torno a un ítem (un hash) y actualizarlo sobre la marcha.
Un enfoque válido podría tener este aspecto:

def check(item)
  new_item = {}
  item.each do |key, value|
    #if any value is empty it puts a placeholder
    new_item[key] = value.empty? ? "Sin datos" : value 
  end
  new_item
end

Este código funciona sin duda, pero también tiene algunos problemas de diseño como temporal (e innecesario) hash.

Como desarrolladores de Ruby debemos cuidar de la API de Ruby en estos casos.

El código de abajo puede ser bueno como punto de partida, pero no cuesta mucho darnos cuenta de que tiene “algo raro”

Así que si nos fijamos en la documentación del Hash, podemos encontrar una solución alternativa (y más bonita) con el método update:

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

Aquí estamos utilizando el método update de Hash, que se utiliza cuando se desea actualizar el contenido de un hash basado en otro. En este caso sólo tenemos un hash, así que aplicaremos el método update a sí mismo.

Así, utilizando el método update evitamos crear un hash innecesario y tenemos nuestro código más legible y claro.

Es hora del método json_items. Como ya hemos dicho, este método debe devolver un array de hashes, que debe ser controlada con nuestro método de verificación.

Un código posible, podría ser este:

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

Realmente odio este fragmento de código repetitivo, es muy común cuando se quiere crear un array basado en otro:

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

GAR tenemos un método increíble para objetos Array cuando tenemos que hacer frente a este tipo de fragmentos: inject.

Este maravilloso método nos permite hacer cosas “mágicas”… :D Por ejemplo:

Si queremos conocer el promedio de longitud de las palabras de un documento, o de una cadena, podemos hacer:

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

Esto se puede hacer de forma más concisa con inject:

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

Como su nombre sugiere inject “inyecta” un objeto inicial (0,0 en el ejemplo anterior) y lo utiliza como valor inicial de “memo” (resultado en el código), luego itera el bloque dado según las características de cada método.

Inject es muy flexible, si no se especifica explícitamente un valor inicial de la inyección, el primer elemento de la colección se utiliza como valor inicial.

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

También puede utilizar inject con hashes. Cuando se ejecuta inject en un hash, el hash se convierte primero en un array antes de ser pasado.

Aplicándolo a nuestro método json_items obtenemos:

def json_items
  json = get_some_json_data
  items = json.inject([]) do |items, item|
    items << check(item)
  end
end

Así que con inject nos hemos ahorrado de crear instancias de una variable temporal, lo que nos permitió construir un nueva array sobre la marcha y tener un código más conciso y legible.

Inject inherentemente proyecta un conjunto de valores de una colección a un valor único. En otras palabras es como una función de muchos-a-uno. Por ello inject tiene un conocido alias: reduce. En matemáticas y otras lenguajes de programación también tiene otros nombres como fold, accumulate, aggregate, compress. Este tipo de funciones analiza una estructura de datos recursiva y recombina, a través del uso de una operación de combinación dada, los resultados del procesamiento recursivo de sus partes constituyentes, con ello devuelve un valor de retorno.

Por lo tanto el ejemplo json_items no es el mejor ejemplo para utilizar inyect, porque estamos tratando de lograr una conversión de uno-a-uno. En estos casos hay que utilizar otros métodos como map o collect que encajan mejor con lo que estamos tratando de hacer.

def json_items
  json = get_some_json_data
  json.map{ |item| check(item) }
end

Ruby nos proporciona métodos impresionantes, debemos usarlos con prudencia y siguiendo la filosofía de Ruby. Así que los animo a utilizar estos métodos para hacer más claras y hermosas aplicaciones con Ruby!

Emiliano Coppo

Ruby on Rails Software Engineer