Definir métodos: la unidad básica de reutilización
Un método encapsula una tarea con un nombre, recibe datos (parámetros) y devuelve un resultado. En Ruby, definir métodos es la forma más directa de evitar duplicación y hacer tu código más legible.
Sintaxis mínima
def saludar(nombre)
"Hola, #{nombre}"
end
saludar("Ana") # => "Hola, Ana"Observa que el método devuelve un valor aunque no uses return. Eso se llama retorno implícito.
Parámetros: posicionales, por defecto y keywords
1) Parámetros posicionales
Se asignan por orden. Úsalos cuando el significado sea obvio y el número de argumentos sea pequeño.
def area_rectangulo(ancho, alto)
ancho * alto
end
area_rectangulo(5, 2) # => 102) Parámetros con valor por defecto
Permiten omitir argumentos comunes.
def formatear_precio(monto, moneda = "EUR")
"#{monto} #{moneda}"
end
formatear_precio(10) # => "10 EUR"
formatear_precio(10, "USD") # => "10 USD"3) Parámetros keyword (con nombre)
Mejoran la claridad cuando hay varios parámetros opcionales o cuando el orden no debería importar.
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
def crear_usuario(nombre:, email:, rol: "user")
{ nombre: nombre, email: email, rol: rol }
end
crear_usuario(nombre: "Ana", email: "ana@mail.com")
# => {:nombre=>"Ana", :email=>"ana@mail.com", :rol=>"user"}Si olvidas un keyword requerido, Ruby lanza un error, lo cual ayuda a detectar fallos temprano.
Mezcla típica: posicional + keyword
def enviar_email(destinatario, asunto:, prioridad: "normal")
"Enviando a #{destinatario} (#{prioridad}): #{asunto}"
end
enviar_email("ana@mail.com", asunto: "Bienvenida")Retorno implícito y explícito
Retorno implícito (recomendado en la mayoría de casos)
Ruby devuelve la última expresión evaluada.
def descuento(monto)
monto * 0.10
endRetorno explícito con return
Útil para salir antes (guard clauses) o para dejar claro un “corte” en el flujo.
def precio_final(monto, cupon: nil)
return monto if cupon.nil?
if cupon == "PROMO10"
monto * 0.90
else
monto
end
endGuía práctica: refactoriza un script en métodos reutilizables
Objetivo: tomar un código que calcula totales y aplicarle estructura. Imagina que tienes una lista de importes (por ejemplo, pedidos) y quieres: sumar, aplicar impuesto y formatear.
Paso 1: define métodos pequeños y con una sola responsabilidad
def subtotal(importes)
importes.sum
end
def aplicar_impuesto(monto, tasa: 0.21)
monto * (1 + tasa)
end
def formatear_moneda(monto, moneda: "EUR")
"#{format('%.2f', monto)} #{moneda}"
endPaso 2: compón los métodos para resolver el caso completo
def total_con_impuesto_formateado(importes, tasa: 0.21, moneda: "EUR")
total = aplicar_impuesto(subtotal(importes), tasa: tasa)
formatear_moneda(total, moneda: moneda)
end
importes = [10, 25.5, 4]
total_con_impuesto_formateado(importes)
# => "47.80 EUR"Este estilo facilita pruebas, cambios y reutilización en otros contextos.
Bloques: personaliza comportamiento sin cambiar el método
Un bloque es un fragmento de código que puedes pasar a un método. Se escribe con { ... } o do ... end. Es ideal para “inyectar” lógica: transformaciones, filtros, callbacks, etc.
Bloques con iteradores: map, select, reduce
Estos métodos trabajan sobre colecciones y reciben un bloque.
map: transforma cada elementoselect: filtra elementos según una condiciónreduce(oinject): acumula un resultado
numeros = [1, 2, 3, 4, 5]
cuadrados = numeros.map { |n| n * n }
# => [1, 4, 9, 16, 25]
pares = numeros.select { |n| n.even? }
# => [2, 4]
suma = numeros.reduce(0) { |acc, n| acc + n }
# => 15yield: ejecutar el bloque dentro de tu método
Cuando defines un método, puedes “llamar” al bloque recibido usando yield. Esto te permite crear métodos reutilizables que delegan una parte variable al bloque.
Ejemplo: medir tiempo (callback simple)
def medir(etiqueta)
inicio = Time.now
resultado = yield
fin = Time.now
{ etiqueta: etiqueta, segundos: (fin - inicio), resultado: resultado }
end
info = medir("cálculo") do
(1..1_000_00).reduce(0) { |acc, n| acc + n }
end
info[:resultado]Comprobar si hay bloque: block_given?
Si tu método puede funcionar con o sin bloque, valida antes de usar yield.
def con_log(mensaje)
puts "[LOG] #{mensaje}"
return yield if block_given?
:sin_bloque
endActividad: método que recibe un bloque para personalizar un cálculo
Necesidad real: calcular un total, pero permitir que el usuario del método defina cómo se calcula cada línea (por ejemplo, aplicar descuento por producto, redondeos, etc.).
Paso a paso
- El método recibe una colección.
- Por defecto, usa el valor tal cual.
- Si hay bloque, el bloque decide el valor a sumar.
def total_personalizable(items)
items.reduce(0) do |acc, item|
valor = block_given? ? yield(item) : item
acc + valor
end
end
precios = [10, 20, 30]
# Sin bloque: suma normal
total_personalizable(precios) # => 60
# Con bloque: descuento del 10%
total_personalizable(precios) { |p| p * 0.9 } # => 54.0Este patrón aparece en librerías y frameworks: el método ofrece una estructura y el bloque personaliza el detalle.
De bloque a objeto: Proc y lambda
Un bloque es “anónimo” y vive en la llamada. A veces necesitas guardar esa lógica en una variable, pasarla entre métodos o reutilizarla. Para eso existen Proc y lambda.
Cuándo usar cada uno (regla práctica)
| Herramienta | Úsala cuando... | Nota clave |
|---|---|---|
| Bloque | La lógica se usa una sola vez en esa llamada | No se guarda |
| Proc | Quieres guardar un callback flexible | Más permisivo con argumentos |
| lambda | Quieres una “función” más estricta | Valida aridad (número de argumentos) |
Ejemplo mínimo: transformación configurable (reutilizable)
normalizar = ->(texto) { texto.strip.downcase }
nombres = [" ANA ", "Bea", " carlos "]
nombres.map(&normalizar)
# => ["ana", "bea", "carlos"]El operador & convierte un Proc/lambda en bloque para métodos como map.
Ejemplo mínimo: callback (por ejemplo, “cuando termine, avisa”)
def procesar_pedido(id, on_success: nil)
resultado = { id: id, estado: "ok" }
on_success&.call(resultado)
resultado
end
notificar = ->(res) { puts "Pedido #{res[:id]} procesado" }
procesar_pedido(123, on_success: notificar)Este estilo es común cuando quieres hacer tu método extensible sin acoplarlo a una acción concreta (enviar email, loguear, actualizar UI, etc.).
Diferencia práctica entre Proc y lambda (aridad)
p1 = Proc.new { |a, b| [a, b] }
l1 = ->(a, b) { [a, b] }
p1.call(1) # => [1, nil]
# l1.call(1) # => ArgumentError (faltan argumentos)Componer transformaciones sobre colecciones
Una forma moderna de escribir Ruby es encadenar transformaciones claras: primero filtras, luego transformas, luego agregas. Esto reduce estados intermedios y hace el flujo de datos evidente.
Ejemplo: pipeline con select + map + reduce
productos = [
{ nombre: "Teclado", precio: 30, stock: 3 },
{ nombre: "Mouse", precio: 15, stock: 0 },
{ nombre: "Monitor", precio: 120, stock: 2 }
]
total_inventario = productos
.select { |p| p[:stock] > 0 }
.map { |p| p[:precio] * p[:stock] }
.reduce(0, :+)
total_inventario # => 30*3 + 120*2 = 330Actividad: crea un “pipeline” configurable con lambdas
Necesidad real: aplicar una serie de transformaciones que pueden cambiar según el contexto (por ejemplo, normalización, validación, formateo).
def aplicar_pipeline(valor, pasos)
pasos.reduce(valor) { |acc, paso| paso.call(acc) }
end
pasos = [
->(t) { t.strip },
->(t) { t.downcase },
->(t) { t.gsub(" ", "-") }
]
aplicar_pipeline(" Hola Mundo ", pasos)
# => "hola-mundo"Actividades propuestas (práctica guiada)
1) Refactorizar scripts previos en métodos reutilizables
- Identifica 2–3 fragmentos repetidos (por ejemplo: calcular subtotal, formatear salida, validar datos).
- Extrae cada fragmento a un método con nombre claro.
- Reemplaza el código duplicado por llamadas a esos métodos.
- Mejora la firma del método usando keywords para opciones (por ejemplo,
moneda:,tasa:).
2) Crear un método que reciba un bloque para personalizar el cálculo
- Implementa
total_personalizable(o uno similar) para tu caso real: notas, precios, puntos, tiempos. - Haz que funcione sin bloque (comportamiento por defecto).
- Agrega un ejemplo con bloque que cambie la regla (descuento, redondeo, penalización, etc.).
3) Componer transformaciones sobre colecciones
- Elige una colección de hashes (por ejemplo, productos, usuarios, tareas).
- Construye un pipeline:
selectpara filtrar,mappara transformar,reducepara agregar. - Extrae al menos una transformación a una
lambdareutilizable y úsala conmap(&mi_lambda)o dentro de un pipeline de pasos.