Pasando parámetros:
En ruby al igual que muchos otros lenguajes las funciones pueden reciben muchos parámetros
y el orden en que se pasan determina como se asignan los valores.
Ejemplo:
def suma(a,b)
end
suma(2,3) #a toma el valor de 2, y b toma el valor de 3
Cuando una función recibe sólo uno o dos parámetros esto rara vez es un problema, pero si una función recibe tres o más es muy posible que terminemos llamándola equivocadamente, en otro orden o que después de hacer un cambio en la función, por ejemplo agregando un nuevo parámetro rompamos el como funciona.
Hash como parámetro
Para evitar este tipo de errores existe un patrón de diseño que consiste simplemente en pasar los argumentos dentro de un hash, en el caso de la suma puede ser medio ridículo, pero si se trata de un archivo de configuración donde se pasan 10 parámetros y 5 de estos son optativos y ademas algunos de ellos tienen valores por defecto, ahí la necesidad hace que pasar un hash tenga mucho más sentido.
def suma(args)
args[:a] + args[:b]
end
suma({:a => 2, :b => 3})
Valores por defecto:
En ruby es perfectamente posible asumir que si un parámetro es omitido, la función le asigna un valor por defecto.
def incrementar(a, b = 1)
a + b
end
incrementar(2) # 3
incrementar(2,4) # 6
¿Entonces como manejamos esta situación con un hash?
Ocupando el operador ||, este operador primero evalúa la expresión de la izquierda y si es falsa o nula entonces procede a evaluar la de la derecha
def incrementar(args)
base = args[:base]
incremento = args[:incremento] || 1
base + incremento
end
incrementar({:base => 4})
incrementar({:base => 2, :incremento => 1 })
Es cierto que esta forma de escribir código es un poco más redundante que la anterior, e insisto que cuando se trata de funciones (u objetos) que reciben uno o dos parámetros rara vez tiene sentido, pero cuando se tienen funciones más complejas, esta forma mejora la documentación del código y además permite agregar cambios como parámetros nuevos generando menos ruptura al código y eso se traduce en mejor diseño y mayor velocidad de cambio.
El problema de los booleano
El problema con utilizar el operador || es que pasaría si por defecto nosotros quisieramos que uno de los parámetros fuera el bool true, en ese caso tendríamos un:
flag_x = args[:flag_x] || true
En esa expresión independiente del valor que fuera pasado, flag_x siempre será true.
Solución con Fetch
La solución a este problema consiste en ocupar el método fetch de los hashs, el cual permite rescatar el valor y en caso de que no esté asignar un valor por defecto (pero sólo si no está presente, en lugar de si es false o nil)
def incrementar(args)
base = args.fetch(:base)
incremento = args.fetch(:incremento, 1)
base + incremento
end
incrementar({:base => 4})
incrementar({:base => 2, :incremento => 1 })
Solución con otro hash
Un truco un poco más pro que el anterior es definir otro hash dentro de la función con los valores por defecto y luego usar merge.
def incrementar(args)
default = {:incremento => 1}
args = default.merge(args)
base = args(:base)
incremento = args(:incremento)
base + incremento
end
incrementar({:base => 4})
incrementar({:base => 2, :incremento => 1 })
al hacer el merge en el caso anterior los valores de default son sobreescritos por los del hash args que es justamente lo que esperamos lograr y de esta forma ya no tenemos que preocuparnos de hacer fetch de cada elemento y podemos rápidamente asignar valores por defecto de forma segura en nuestras funciones y métodos.