Cheatsheet Ruby
Referência completa da linguagem Ruby — elegante, expressiva e orientada a objetos
Ruby
Básico
Variables y scope
nombre = "Ruby" # local (minúscula) $global = "todas partes" # global @instancia = "objeto" # instancia @@clase = "clase" # clase CONSTANTE = "fija" # constante (convención) # Asignación múltiple: x, y, z = 1, 2, 3 a, b = b, a # swap elegante # Asignación condicional: @cache ||= calcular() # solo si nil/false # Equivale: @cache = @cache || calcular() # Operador de navegación segura: nombre&.upcase # nil si nombre es nil # Verificar definida: defined?(nombre) # "local-variable"
El prefijo define el scope: sin prefijo = local, $ = global, @ = instancia, @@ = clase, MAYÚSCULA = constante. ||= para lazy init. &. (safe navigation) evita errores en nil. El swap sin variable temporal es idiomático.
Operadores
# Aritméticos: + - * / % ** 2 ** 10 # 1024 10 / 3 # 3 (división entera) 10.0 / 3 # 3.333... # Comparación: 1 == 1.0 # true (valor) 1.eql?(1.0) # false (valor + tipo) 1.equal?(1) # true (mismo objeto) # Lógicos: && || ! # Safe navigation: nombre&.upcase # nil si nombre es nil # Ternario: status = edad >= 18 ? "adulto" : "menor" # Ranges: (1..10) # inclusivo (1 a 10) (1...10) # exclusivo (1 a 9) (1..).include?(999) # true (endless)
== compara valor, .eql? valor+tipo, .equal? identidad. &. (safe navigation) evita NoMethodError en nil. .. inclusivo, ... exclusivo. La división entera trunca — usar Float para decimales.
Tipos de datos
42.class # Integer
3.14.class # Float
"texto".class # String
true.class # TrueClass
nil.class # NilClass
:simbolo.class # Symbol
[1, 2].class # Array
{ a: 1 }.class # Hash
(1..10).class # Range
/regex/.class # Regexp
# Todo es objeto:
42.even? # true
42.times { } # método en número
nil.nil? # true
nil.is_a?(Object) # true
# Tipado dinámico pero fuerte:
# "42" + 1 # ¡TypeError! (sin conversión implícita)Ruby tiene tipado dinámico pero fuerte — sin conversiones implícitas. .class revela el tipo. Todo es objeto (números, nil, true). Los Symbols (:nombre) son identificadores inmutables. is_a? verifica la jerarquía de tipos.
Conversiones
"42".to_i # 42
"3.14".to_f # 3.14
42.to_s # "42"
42.to_f # 42.0
"abc".to_i # 0 (¡no lanza error!)
# Versiones estrictas:
Integer("42") # 42
Integer("abc") # ¡ArgumentError!
Float("3.14") # 3.14
# Parsing seguro:
begin
n = Integer(input)
rescue ArgumentError
n = 0
end
# Otras:
"1,2,3".split(",").map(&:to_i) # [1, 2, 3]
[1, 2, 3].join("-") # "1-2-3"
:nombre.to_s # "nombre"to_i/to_f nunca lanzan error — devuelven 0 en strings inválidas (¡peligroso!). Integer()/Float() son estrictos y lanzan ArgumentError. Para parsing seguro, usar Integer() con rescue.
Strings
nombre = "Mundo"
saludo = "¡Hola, #{nombre}!" # interpolación
# Heredoc (multilínea):
texto = <<~HEREDOC
Línea 1
Línea 2
HEREDOC
# Métodos útiles:
"ruby".upcase # "RUBY"
"RUBY".downcase # "ruby"
"hola mundo".capitalize # "Hola mundo"
"a,b,c".split(",") # ["a", "b", "c"]
" abc ".strip # "abc"
"texto".reverse # "otxet"
"hola".length # 4
"banana".gsub("a", "o") # "bonono"
"prueba".include?("ue") # trueLa interpolación #{} es la forma preferida. <<~HEREDOC crea strings multilínea (quita el indent). gsub sustituye todas las ocurrencias. split divide en array. strip elimina whitespace. Las strings son mutables (usar .freeze para inmutable).
Ranges y case
# Ranges:
(1..10).to_a # [1, 2, ..., 10]
(1...10).to_a # [1, 2, ..., 9]
("a".."f").to_a # ["a", "b", "c", "d", "e", "f"]
# Verificar pertenencia:
(1..100).include?(50) # true
(1..100).cover?(50) # true (más rápido)
# Endless/Beginless (Ruby 2.6+):
(18..) # 18 al infinito
(..100) # infinito hasta 100
# case con ranges:
case edad
when 0..12 then "niño"
when 13..17 then "adolescente"
when 18.. then "adulto"
end
# Iterar:
(1..5).each { |i| print i } # 12345.. inclusivo, ... exclusivo. cover? es O(1) (no itera). Ranges endless (18..) y beginless (..100). case/when con ranges es idiomático. Funciona con números, letras y fechas.
Symbols y truthiness
# Symbols: identificadores inmutables y únicos
:nombre.object_id == :nombre.object_id # true
"nombre".object_id == "nombre".object_id # false
# Usados como hash keys y enums:
persona = { nombre: "Ana", edad: 25 }
persona[:nombre] # "Ana"
# Truthiness en Ruby:
# ¡Solo nil y false son falsy!
!!nil # false
!!false # false
!!0 # true (¡0 es truthy!)
!!"" # true (¡string vacía es truthy!)
!![] # true (¡array vacío es truthy!)
# Verificar nil:
valor.nil? # true/false
valor.nil? || valor.empty? # para strings/arraysLos Symbols son únicos en memoria — ideales para keys y enums. En Ruby, solo nil y false son falsy — 0, "" y [] son truthy (diferente de JS/Python). .nil? verifica nil explícitamente.
Comentarios y documentación
# Comentario de línea =begin Comentario multilínea (poco usado) =end # Documentación (RDoc/YARD): # Calcula el doble de un número. # # @param n [Integer] el número # @return [Integer] el doble def doble(n) n * 2 end # Anotaciones: # TODO: implementar caching # FIXME: bug con valores negativos # NOTE: requiere Ruby 3.0+ # Generar docs: # yard doc # rdoc lib/
# para comentarios de línea. =begin/=end para multilínea (raro). YARD es el estándar de documentación (@param, @return). Anotaciones: TODO, FIXME, NOTE. yard doc genera HTML.
Controlo de Fluxo
If / Unless
if edad >= 18 puts "Adulto" elsif edad >= 12 puts "Adolescente" else puts "Niño" end # unless (if negativo): unless lista.empty? procesar(lista) end # Modifier form (una línea): puts "Adulto" if edad >= 18 puts "Vacío" unless lista.any? # if como expresión: resultado = if x > 0 "positivo" else "negativo" end
unless es lo opuesto de if — más legible para condiciones negativas. La forma modifier (código if condición) es idiomática para una línea. if es expresión (devuelve valor). Sin paréntesis en la condición. Llaves sustituidas por end.
Break, Next y Redo
[1, 2, 3, 4, 5].each do |n| next if n.even? # salta pares (continue) break if n > 4 # detiene el bucle puts n # 1, 3 end # redo: repite la iteración actual attempts = 0 3.times do |i| attempts += 1 redo if i == 1 && attempts < 5 end # break con valor: resultado = [1, 2, 3].map do |n| break "paró" if n > 2 n * 2 end # resultado = "paró" # retry: reinicia el begin entero
next salta a la siguiente iteración (continue). break termina el bucle. redo repite la iteración actual sin avanzar. break puede devolver valor. retry reinicia el bloque entero. Controles flexibles sobre la iteración.
Case / When
case dia when 1..5 puts "Día laborable" when 6, 7 puts "Fin de semana" else puts "Inválido" end # Con clases (operador ===): case objeto when String then "Es texto" when Integer then "Es número" when Array then "Es lista" end # Como expresión: status = case código when 200 then :ok when 404 then :not_found when 500 then :error end # Con regex: case input when /\d+/ then "número" when /\w+/ then "palabra" end
case/when usa === para comparar — funciona con ranges, clases, regexps. Es expresión que devuelve valor. then permite una línea. Más potente que el switch de C. Sin break (no hace fallthrough).
Ternario y ||=
# Ternario: status = edad >= 18 ? "adulto" : "menor" # || para defaults: nombre = input || "Anónimo" # ||= (asignación condicional): @cache ||= calcular_datos() # Solo calcula si @cache es nil/false # &&= (asignación si truthy): valor &&= valor.strip # Safe navigation (&.): ciudad = usuario&.direccion&.ciudad # nil si cualquier nivel es nil # Combinado: total = pedido&.items&.sum(&:precio) || 0
||= es ubicuo para lazy initialization y caching. &. encadena llamadas sin NoMethodError en nil. Ternario para condiciones simples. &&= solo asigna si es truthy. Estos operadores eliminan verificaciones nil verbosas.
Bucles
# while: n = 10 while n > 0 n -= 1 end # until (while negativo): until cola.empty? procesar(cola.pop) end # bucle infinito: loop do break if condicion end # Modifier: n -= 1 while n > 0 procesar until cola.empty? # begin..end while (ejecuta 1x): begin input = gets end while input.nil?
until ejecuta mientras la condición sea falsa. loop do crea un bucle infinito (se detiene con break). La forma modifier es idiomática para bucles cortos. Ruby prefiere iteradores (each, times) sobre bucles manuales.
Pattern Matching (Ruby 3+)
# case/in (Ruby 3.0+):
case datos
in { nombre: String => n, edad: Integer => i }
puts "#{n} tiene #{i} años"
in [1, 2, *resto]
puts "Lista con resto: #{resto}"
in { tipo: "admin" }
puts "Administrador"
else
puts "No corresponde"
end
# Destructuring:
case punto
in [x, y] if x == y
puts "Diagonal"
end
# One-line pattern (Ruby 3.0+):
{ nombre: "Ana" } => { nombre: }
puts nombre # "Ana"Pattern matching (Ruby 3.0+) descompone estructuras complejas. in verifica patrones con binding de variables. Soporta guards (if), splats (*) y pin (^). One-line con =>. Potente para parsear datos y APIs.
Iteradores
# times:
5.times { |i| puts i } # 0 1 2 3 4
# each:
[1, 2, 3].each { |n| puts n }
# upto / downto:
1.upto(5) { |i| print i } # 12345
10.downto(1) { |i| print i } # 10987654321
# step:
(0..20).step(5) { |i| print i } # 0 5 10 15 20
# each_with_index:
["a", "b", "c"].each_with_index do |v, i|
puts "#{i}: #{v}"
end
# each_with_object:
hash = [1, 2, 3].each_with_object({}) do |n, h|
h[n] = n ** 2
endLos iteradores son la forma idiomática — sustituyen los for loops. times, upto, downto, step para conteos. each_with_index da el índice. each_with_object acumula en una colección. Bloques {} para una línea, do..end para múltiples.
Begin / Rescue
begin
resultado = operacion_arriesgada
rescue ArgumentError => e
puts "Argumento inválido: #{e.message}"
rescue StandardError => e
puts "Error: #{e.class} - #{e.message}"
else
puts "Éxito: #{resultado}"
ensure
puts "Siempre ejecuta (cleanup)"
end
# Raise:
raise ArgumentError, "Edad inválida" if edad < 0
# Retry:
begin
conectar
rescue TimeoutError
retry # intenta de nuevo
end
# Modifier:
valor = Integer(input) rescue 0begin/rescue es el try/catch de Ruby. else ejecuta solo sin excepción. ensure siempre ejecuta (finally). raise lanza. retry reinicia. Jerarquía: Exception > StandardError > RuntimeError. Modifier rescue para una línea.
Coleções
Arrays
nums = [1, 2, 3, 4, 5] nums << 6 # append (push) nums.pop # elimina el último nums.shift # elimina el primero nums.unshift(0) # añade al inicio nums[0] # primero nums[-1] # último nums[1..3] # slice [2, 3, 4] nums.length # tamaño nums.include?(3) # true # Literales especiales: %w[uno dos tres] # ["uno", "dos", "tres"] %i[uno dos tres] # [:uno, :dos, :tres] # Heterogéneo: mixto = [1, "dos", :tres, [4], nil]
Los arrays son dinámicos y heterogéneos. << es el operador idiomático de append. Los índices negativos cuentan desde el final. %w[] crea arrays de strings sin comillas. %i[] crea arrays de symbols. Slice con ranges. Mutables por default.
Ordenación y búsqueda
nums = [5, 2, 8, 1, 9]
nums.sort # [1, 2, 5, 8, 9]
nums.sort.reverse # [9, 8, 5, 2, 1]
nums.sort_by { |n| -n } # descendente
# Búsqueda:
nums.find { |n| n > 4 } # 5 (primero)
nums.any? { |n| n > 8 } # true
nums.all? { |n| n > 0 } # true
nums.none? { |n| n > 10 } # true
nums.count { |n| n.even? } # 2
# min/max:
nums.min # 1
nums.max # 9
nums.minmax # [1, 9]
# bsearch (array ordenado):
[1, 3, 5, 7, 9].bsearch { |n| n >= 5 } # 5sort devuelve un nuevo array ordenado. sort_by ordena por clave custom. find devuelve el primer match. any?/all?/none? verifican condiciones. bsearch es O(log n) en arrays ordenados.
Hashes
persona = { nombre: "Ana", edad: 25, ciudad: "Lisbon" }
persona[:nombre] # "Ana"
persona.fetch(:email, "N/A") # "N/A" (default)
persona[:email] = "ana@mail.com"
persona.delete(:ciudad)
persona.keys # [:nombre, :edad, :email]
persona.values # ["Ana", 25, "ana@mail.com"]
persona.each { |k, v| puts "#{k}: #{v}" }
# Hash con default:
contador = Hash.new(0)
contador[:a] += 1 # 1 (sin KeyError)
# Hash con bloque default:
grupos = Hash.new { |h, k| h[k] = [] }
grupos[:frutas] << "manzana"Los hashes mapean claves a valores. fetch con default evita nil silencioso. Hash.new(0) para contadores. Hash.new { } para agrupamientos. Las keys son típicamente symbols. dig para navegación anidada segura.
Group, Zip y Tally
personas = [
{ nombre: "Ana", edad: 25 },
{ nombre: "Ray", edad: 30 },
{ nombre: "Mia", edad: 25 }
]
# group_by:
por_edad = personas.group_by { |p| p[:edad] }
# {25=>[Ana,Mia], 30=>[Ray]}
# zip: combina arrays
[1, 2, 3].zip(["a", "b", "c"])
# [[1,"a"], [2,"b"], [3,"c"]]
# partition: divide en dos
pares, impares = (1..6).partition(&:even?)
# tally (Ruby 2.7+): conteo
"abracadabra".chars.tally
# {"a"=>5, "b"=>2, "r"=>2, "c"=>1, "d"=>1}
# chunk: agrupa consecutivos
[1, 1, 2, 3, 3].chunk_while { |a, b| a == b }.to_a
# [[1,1], [2], [3,3]]group_by agrupa por clave en un Hash de Arrays. zip combina posición a posición. partition divide en dos. tally cuenta ocurrencias. chunk_while agrupa elementos consecutivos. Todos devuelven objetos nuevos.
Map y Select
nums = [1, 2, 3, 4, 5, 6]
# map: transforma cada elemento
dobles = nums.map { |n| n * 2 } # [2,4,6,8,10,12]
# select/reject: filtra
pares = nums.select { |n| n.even? } # [2, 4, 6]
impares = nums.reject { |n| n.even? } # [1, 3, 5]
# Shorthand con &:
strings = nums.map(&:to_s) # ["1", "2", ...]
# Encadenamiento:
resultado = nums
.select { |n| n > 2 }
.map { |n| n * 10 }
# [30, 40, 50, 60]
# filter_map (Ruby 2.7+):
positivos = [-1, 2, -3, 4].filter_map do |n|
n * 2 if n > 0
end # [4, 8]map transforma, select filtra (mantiene truthy), reject filtra (mantiene falsy). &:método es shorthand idiomático. filter_map combina select+map en un paso. El encadenamiento crea pipelines declarativos.
Lazy e infinito
# Lazy: procesa solo lo necesario
resultado = (1..Float::INFINITY).lazy
.select { |n| n.even? }
.map { |n| n ** 2 }
.first(5)
# [4, 16, 36, 64, 100] — ¡sin bucle infinito!
# Sin lazy: intentaría procesar infinito
# Con lazy: evalúa elemento a elemento
# Benchmark: 10M elementos
(1..10_000_000).lazy
.select(&:even?)
.map { |n| n * 2 }
.first(10)
# ¡Instantáneo! (solo procesa 20 elementos)
# Enumerator infinito:
fib = Enumerator.new do |y|
a, b = 0, 1
loop { y << a; a, b = b, a + b }
end
fib.first(10) # [0,1,1,2,3,5,8,13,21,34]lazy aplaza el procesamiento — esencial para secuencias infinitas o grandes. Evalúa elemento a elemento (no materializa el array). Enumerator.new crea generadores infinitos. Ideal para pipelines con first(n).
Reduce y accumulate
nums = [1, 2, 3, 4, 5]
# reduce/inject: acumula en un valor
suma = nums.reduce(0) { |acc, n| acc + n } # 15
suma = nums.reduce(:+) # 15 (shorthand)
producto = nums.reduce(1, :*) # 120
# each_with_object: acumula en colección
hash = nums.each_with_object({}) do |n, h|
h[n] = n ** 2
end
# {1=>1, 2=>4, 3=>9, 4=>16, 5=>25}
# Agrupar con reduce:
palabras = %w[ana rui ana mia]
freq = palabras.reduce(Hash.new(0)) do |h, p|
h[p] += 1
h
end # {"ana"=>2, "rui"=>1, "mia"=>1}reduce (alias inject) acumula elementos en un único valor. El primer arg es el valor inicial. &:símbolo para operaciones binarias. each_with_object evita variable externa. Base para suma, producto, conteo, agrupamiento.
Set y operaciones
require "set"
a = Set[1, 2, 3, 4]
b = Set[3, 4, 5, 6]
a | b # unión: {1,2,3,4,5,6}
a & b # intersección: {3,4}
a - b # diferencia: {1,2}
a ^ b # simétrica: {1,2,5,6}
a.include?(3) # true (¡O(1)!)
a.add(5)
a.delete(1)
a.subset?(Set[1, 2, 3, 4, 5])
# Los arrays también tienen operaciones:
[1, 2, 3] | [3, 4, 5] # [1,2,3,4,5]
[1, 2, 3] & [3, 4, 5] # [3]
[1, 2, 3] - [3] # [1, 2]
# uniq:
[1, 2, 2, 3, 3, 3].uniq # [1, 2, 3]Set tiene include? O(1) (vs O(n) de Array). Operaciones: | unión, & intersección, - diferencia, ^ simétrica. Los arrays también soportan |, &, -. uniq elimina duplicados.
Métodos e Blocos
Métodos
def saludar(nombre, prefijo = "Hola")
"#{prefijo}, #{nombre}!"
end
saludar("Ana") # "Hola, Ana!"
saludar("Ana", "Buenos días") # "Buenos días, Ana!"
# Keyword arguments:
def conectar(host:, puerto: 3306, ssl: false)
# ...
end
conectar(host: "localhost", ssl: true)
# Retorno implícito (última expresión):
def doble(n)
n * 2 # ¡sin return!
end
# Endless method (Ruby 3.0+):
def triple(n) = n * 3Retorno implícito — la última expresión es devuelta. Los keyword args (nombre:) hacen las llamadas autodocumentadas. Endless methods (def x = expr) para una línea. Los parámetros default eliminan sobrecargas. Llamar sin paréntesis es idiomático.
Method chaining y tap
# Method chaining:
resultado = "Hola Mundo"
.downcase
.gsub("mundo", "ruby")
.capitalize
# "Hola ruby"
# tap: efecto lateral sin romper el chain
[3, 1, 2]
.tap { |a| puts "Original: #{a}" }
.sort
.tap { |a| puts "Ordenado: #{a}" }
.first
# then (Ruby 2.6+):
5.then { |n| n * 2 }.then { |n| n + 1 }
# 11
# dig (navegación segura):
datos = { user: { address: { city: "Lisbon" } } }
datos.dig(:user, :address, :city) # "Lisbon"
datos.dig(:user, :phone, :number) # nil (¡sin error!)Method chaining crea pipelines legibles. tap permite inspección/logging sin interrumpir. then transforma el valor en el pipeline (como el |> de Elixir). dig navega hashes anidados sin NoMethodError. Todo devuelve self o un objeto nuevo.
Bloques y yield
# yield: ejecuta el bloque pasado
def medir_tiempo
inicio = Time.now
resultado = yield
puts "Tardó: #{Time.now - inicio}s"
resultado
end
medir_tiempo { operacion_lenta() }
# Bloque con parámetros:
def repetir(n)
n.times { |i| yield(i) }
end
repetir(3) { |i| puts "Iteración #{i}" }
# block_given?:
def quizas
yield if block_given?
end
# Bloques {} vs do..end:
[1, 2].each { |n| puts n } # una línea
[1, 2].each do |n| # múltiples
puts n
puts n * 2
endLos bloques son la feature más idiomática de Ruby — funciones anónimas pasadas implícitamente. yield ejecuta el bloque. block_given? verifica. {} para una línea, do..end para múltiples. Usados en each, map, open, etc.
Métodos de clase
class Matemática
# Método de clase (self.):
def self.pi
3.14159
end
# Alternativa con class << self:
class << self
def raiz(n)
Math.sqrt(n)
end
end
end
Matemática.pi # 3.14159
Matemática.raiz(16) # 4.0
# Singleton method en un objeto:
obj = Object.new
def obj.gritar
"SOY ESPECIAL"
end
obj.gritar # "SOY ESPECIAL"
# Singleton pattern:
require "singleton"
class DB
include Singleton
end
DB.instance # siempre el mismoLos métodos de clase (self.nombre) se llaman en la clase. class << self abre la singleton class. Los singleton methods existen solo en un objeto. include Singleton de la stdlib garantiza instancia única. Útil para factories y utilidades.
Procs y Lambdas
# Proc: bloque como objeto
doble = Proc.new { |n| n * 2 }
doble.call(5) # 10
doble.(5) # 10 (shorthand)
# Lambda: proc estricto
triple = ->(n) { n * 3 }
triple.call(5) # 15
# Diferencias:
# 1. Lambda verifica aridad (nº args)
# 2. return en lambda sale de la lambda
# return en proc sale del método
# & para convertir bloque<->proc:
[1, 2, 3].map(&:to_s) # ["1", "2", "3"]
# Equivale: map { |n| n.to_s }
# Proc como parámetro:
def aplicar(proc, valor)
proc.call(valor)
endProc y lambda son bloques como objetos. Lambda es más estricto (verifica args, return local). &:método convierte un símbolo en proc — shorthand idiomático. ->(args) { } es la sintaxis moderna de lambda. First-class citizens.
Currying y composición
# Currying:
suma = ->(a, b, c) { a + b + c }
curried = suma.curry
curried[1][2][3] # 6
add5 = curried[5]
add5[3][2] # 10
# Composición (Ruby 2.6+):
doble = ->(n) { n * 2 }
incremento = ->(n) { n + 1 }
compuesto = doble << incremento # doble(incremento(n))
compuesto.(5) # 12
compuesto2 = doble >> incremento # incremento(doble(n))
compuesto2.(5) # 11
# Aplicación práctica:
pipeline = ->(s) { s.strip } >> ->(s) { s.downcase }
pipeline.(" HOLA ") # "hola"curry transforma una función de N args en cadena de funciones de 1 arg. << y >> componen funciones (right-to-left / left-to-right). Técnicas funcionales para código declarativo. Las lambdas son first-class y componibles.
Splats y Destructuring
# Splat (*): args variables
def suma(*nums)
nums.reduce(0, :+)
end
suma(1, 2, 3, 4, 5) # 15
# Double splat (**): kwargs variables
def config(**opts)
opts.each { |k, v| puts "#{k}=#{v}" }
end
config(host: "localhost", port: 80)
# Block arg (&):
def con_bloque(&bloque)
bloque.call
end
# Destructuring:
def procesar((nombre, edad))
"#{nombre} tiene #{edad}"
end
procesar(["Ana", 25])
# Splat en asignación:
primero, *resto = [1, 2, 3, 4, 5]
# primero = 1, resto = [2, 3, 4, 5]* colecta args extra en un Array. ** colecta kwargs en un Hash. & colecta el bloque como Proc. Destructuring extrae elementos de arrays. Splat en asignación divide arrays. Juntos crean APIs flexibles y expresivas.
Method missing y respond_to
class Flexible
def method_missing(nombre, *args, &bloque)
"Llamó #{nombre} con #{args.inspect}"
end
def respond_to_missing?(nombre, priv = false)
true
end
end
f = Flexible.new
f.cualquier_cosa(1, 2) # "Llamó cualquier_cosa con [1, 2]"
f.respond_to?(:abc) # true
# send: llama método por nombre
obj.send(:nombre) # equivale a obj.nombre
obj.send("nombre=", "nuevo") # setter dinámico
# public_send: solo métodos públicos
obj.public_send(:nombre)
# Usado en: ActiveRecord, Delegator, DSLsmethod_missing intercepta métodos inexistentes — base de ActiveRecord y DSLs. Siempre implementar respond_to_missing? junto. send llama por nombre/símbolo. public_send respeta la visibilidad. Usar con moderación.
Classes e Módulos
Clases
class Persona
attr_accessor :nombre, :edad # getters + setters
attr_reader :email # solo getter
def initialize(nombre, edad)
@nombre = nombre
@edad = edad
@email = "#{nombre}@mail.com"
end
def saludo
"Hola, soy #{@nombre}"
end
end
p = Persona.new("Ana", 25)
p.nombre = "Maria" # setter vía attr_accessor
p.saludo # "Hola, soy Maria"
p.email # "ana@mail.com" (read-only)attr_accessor/attr_reader/attr_writer generan getters/setters automáticamente. initialize es el constructor (llamado por .new). @ define variables de instancia. Ruby es puramente OO — todo es objeto.
Structs y Data
# Struct: clase rápida con atributos
Punto = Struct.new(:x, :y) do
def distancia
Math.sqrt(x**2 + y**2)
end
end
p = Punto.new(3, 4)
p.x # 3
p.distancia # 5.0
# Keyword init:
Config = Struct.new(:host, :port, keyword_init: true)
c = Config.new(host: "localhost", port: 80)
# Data (Ruby 3.2+): inmutable
Punto2 = Data.define(:x, :y)
p2 = Punto2.new(x: 3, y: 4)
# p2.x = 5 # ¡NoMethodError! (inmutable)
# Ambos generan: initialize, getters, ==, to_s, to_hStruct crea clases simples sin boilerplate. Data (Ruby 3.2+) es inmutable — ideal para value objects. Ambos generan initialize, getters, ==, to_s y to_h automáticamente. keyword_init: true para args nombrados.
Herencia
class Animal
def initialize(nombre)
@nombre = nombre
end
def sonido
"..."
end
end
class Perro < Animal
def sonido
"#{@nombre}: ¡Guau!"
end
end
class Gato < Animal
def sonido
"#{@nombre}: ¡Miau!"
end
end
Perro.new("Rex").sonido # "Rex: ¡Guau!"
# super llama al método padre:
class Cachorro < Perro
def sonido
super + " (bajito)"
end
endRuby soporta herencia simple (<). super llama al método de la clase padre. Métodos sobrescritos sin keyword. No existe herencia múltiple de clases — usar modules (mixins) para composición de comportamiento.
Metaprogramación
# define_method: crea métodos dinámicamente
class Modelo
[:nombre, :email, :edad].each do |attr|
define_method(attr) do
instance_variable_get("@#{attr}")
end
define_method("#{attr}=") do |v|
instance_variable_set("@#{attr}", v)
end
end
end
# instance_eval: cambia self
config.instance_eval do
@host = "localhost"
@port = 3000
end
# class_eval: evalúa en la clase
String.class_eval do
def shout
"#{upcase}!"
end
enddefine_method crea métodos en runtime. instance_eval ejecuta un bloque con self cambiado — base de DSLs. class_eval evalúa código en el contexto de la clase. instance_variable_get/set accede variables dinámicamente. Potente pero usar con moderación.
Modules y Mixins
module Nadador
def nadar
"#{@nombre} está nadando"
end
end
module Volador
def volar
"#{@nombre} está volando"
end
end
class Pato < Animal
include Nadador # métodos de instancia
include Volador
end
class Config
extend Nadador # métodos de clase
end
# Module como namespace:
module API
class Client; end
VERSION = "1.0"
end
API::Client.newModules son la alternativa a la herencia múltiple. include añade métodos de instancia. extend añade métodos de clase. Un módulo puede incluirse en múltiples clases. También sirven como namespaces (API::Client).
Refinements
# Refinements: monkey patch con scope
module StringExtensions
refine String do
def shout
"#{upcase}!"
end
end
end
# Solo activo en este archivo:
using StringExtensions
"hola".shout # "HOLA!"
# Monkey patching (global — evitar):
class String
def titleize
split.map(&:capitalize).join(" ")
end
end
# open class (extensión segura):
class Array
def segundo
self[1]
end
endRefinements permiten modificar clases con scope limitado (using) — evita conflictos globales. Monkey patching modifica globalmente — potente pero peligroso. Rails lo usa extensivamente. Preferir refinements o módulos para extensiones.
Access control
class Cuenta
def initialize(saldo)
@saldo = saldo
end
def saldo
@saldo
end
private # los métodos abajo son privados
def validar(valor)
valor > 0
end
protected # accesible entre instancias
def comparar(otra)
@saldo <=> otra.saldo
end
end
# public (default): accesible desde fuera
# private: sin receptor explícito (self)
# protected: entre instancias de la misma clasepublic (default): accesible desde cualquier lugar. private: solo llamable sin receptor explícito. protected: permite llamadas entre instancias de la misma clase. En Ruby, la privacidad es por convención — no es estricta como Java.
Comparable y Enumerable
# Comparable: implementa <=>
class Temperatura
include Comparable
attr_reader :celsius
def initialize(celsius)
@celsius = celsius
end
def <=>(otra)
celsius <=> otra.celsius
end
end
t1 = Temperatura.new(25)
t2 = Temperatura.new(30)
t1 < t2 # true
t1.between?(Temperatura.new(20), Temperatura.new(30))
# Enumerable: implementa each
class Lista
include Enumerable
def each(&bloque)
# iterar elementos
end
end
# Gana: map, select, reduce, sort, etc.Comparable + <=> da todos los operadores de comparación. Enumerable + each da map, select, reduce, sort, etc. Mixins de la stdlib que añaden comportamiento rico con mínimo código.
Avançado
Error handling avanzado
# Jerarquía de excepciones:
# Exception
# ├── StandardError (rescue default)
# │ ├── ArgumentError
# │ ├── RuntimeError
# │ ├── TypeError
# │ ├── NoMethodError
# │ └── IOError
# └── NoMemoryError, SignalException...
# Excepción custom:
class SaldoInsuficiente < StandardError
attr_reader :saldo, :valor
def initialize(saldo, valor)
@saldo = saldo
@valor = valor
super("Saldo #{saldo} insuficiente para #{valor}")
end
end
raise SaldoInsuficiente.new(100, 200)
# Rescatar específica:
rescue SaldoInsuficiente => e
puts e.saldoRescatar solo StandardError (no Exception). Crear excepciones custom heredando de StandardError. super(message) define el mensaje. Atributos extra para contexto. La jerarquía permite rescue por categoría.
Regex
# Matching:
"hola mundo" =~ /mundo/ # 5 (índice)
"hola mundo".match?(/\d/) # false
# Captura:
m = "2024-01-15".match(/(\d{4})-(\d{2})-(\d{2})/)
m[1] # "2024"
m[2] # "01"
m[3] # "15"
# Named captures:
m = "Ana:25".match(/(?<nombre>\w+):(?<edad>\d+)/)
m[:nombre] # "Ana"
m[:edad] # "25"
# Sustitución:
"abc123".gsub(/\d+/, "X") # "abcX"
"a1b2c3".scan(/\d/) # ["1", "2", "3"]
# Modificadores:
/regex/i # case-insensitive
/regex/m # multiline
/regex/x # verbose (comentarios)=~ devuelve el índice del match. match? devuelve boolean (más rápido). Captura con () y named con (?<nombre>). gsub sustituye, scan extrae todos. %r{} como alternativa a //.
Threads y Mutex
# Threads:
threads = 5.times.map do |i|
Thread.new do
sleep(rand)
puts "Thread #{i} terminó"
end
end
threads.each(&:join) # espera todas
# Mutex (thread-safety):
mutex = Mutex.new
contador = 0
10.times.map do
Thread.new do
1000.times { mutex.synchronize { contador += 1 } }
end
end.each(&:join)
contador # 10000 (sin race condition)
# Queue (thread-safe):
queue = Queue.new
Thread.new { queue.push("trabajo") }
item = queue.pop # bloquea hasta tener itemRuby tiene GIL — paralelismo limitado para CPU-bound. Mutex protege el estado compartido. Queue es thread-safe para comunicación. join espera a que la thread termine. Para paralelismo real, usar Ractor (Ruby 3.0+).
IO y archivos
# Leer archivo entero:
contenido = File.read("datos.txt")
# Leer línea a línea:
File.foreach("datos.txt") do |linea|
puts linea.chomp
end
# Escribir:
File.write("salida.txt", "¡Hola!\n")
# Append:
File.open("log.txt", "a") do |f|
f.puts "Nueva entrada"
end
# Verificar:
File.exist?("datos.txt") # true/false
File.size("datos.txt") # bytes
File.extname("foto.png") # ".png"
# Dir:
Dir.glob("*.rb") # lista archivos
Dir.mkdir("nueva_carpeta")
Dir.pwd # directorio actual
# STDIN:
input = gets.chompFile.read lee entero. File.foreach itera líneas (memory-friendly). File.write escribe. Un bloque con File.open cierra automáticamente. Dir.glob para patrones. gets lee stdin. chomp elimina el newline.
Ractors (Ruby 3+)
# Ractor: paralelismo real (sin GIL) r1 = Ractor.new do resultado = calculo_pesado resultado end r2 = Ractor.new do otro_calculo_pesado end # Resultados: v1 = r1.take # espera y obtiene v2 = r2.take # Comunicación por mensajes: r = Ractor.new do msg = Ractor.receive msg * 2 end r.send(21) r.take # 42 # Restricciones: # - Sin estado compartido mutable # - Los objetos se copian o se congelan # - Ideal para CPU-bound aislado
Ractors (Ruby 3.0+) permiten paralelismo real — memoria aislada. Comunicación por mensajes (send/receive/take). Sin objetos mutables compartidos. Ideal para CPU-bound. Alternativa: Fibers para concurrencia cooperativa.
JSON y HTTP
require "json"
require "net/http"
require "uri"
# JSON:
hash = { nombre: "Ana", edad: 25 }
json_str = JSON.generate(hash)
# '{"nombre":"Ana","edad":25}'
datos = JSON.parse('{"nombre":"Ana"}')
datos["nombre"] # "Ana"
# Archivo JSON:
File.write("data.json", JSON.pretty_generate(hash))
datos = JSON.parse(File.read("data.json"))
# HTTP GET:
uri = URI("https://api.example.com/datos")
respuesta = Net::HTTP.get_response(uri)
if respuesta.is_a?(Net::HTTPSuccess)
datos = JSON.parse(respuesta.body)
end
# HTTP POST:
Net::HTTP.post(uri, json_str, "Content-Type" => "application/json")JSON.generate serializa, JSON.parse deserializa. JSON.pretty_generate para output legible. Net::HTTP de la stdlib para requests. Gems: httparty o faraday para APIs más complejas. URI hace parsing de URLs.
DSLs en Ruby
# DSL con instance_eval:
class HTMLBuilder
def initialize
@html = []
end
def method_missing(nombre, *args, &bloque)
@html << "<#{nombre}>#{args.first}</#{nombre}>"
end
def to_s
@html.join("\n")
end
end
def html(&bloque)
b = HTMLBuilder.new
b.instance_eval(&bloque)
b.to_s
end
html do
h1 "Título"
p "Contenido"
end
# <h1>Título</h1>
# <p>Contenido</p>Las DSLs son ubicuas en Ruby — Rails, RSpec, Rake, Gemfile. instance_eval + method_missing crean APIs declarativas. El bloque ejecuta con self cambiado. Sintaxis natural sin receptor. Patrón fundamental del ecosistema Ruby.
Testing (Minitest)
require "minitest/autorun"
class CalculadoraTest < Minitest::Test
def setup
@calc = Calculadora.new
end
def test_suma
assert_equal 5, @calc.suma(2, 3)
end
def test_division_por_cero
assert_raises(ZeroDivisionError) do
@calc.dividir(1, 0)
end
end
def test_negativo
refute @calc.positivo?(-1)
end
end
# Ejecutar:
# ruby test/calculadora_test.rb
# Assertions comunes:
# assert_equal, assert_nil, assert_includes
# refute, refute_nil, assert_raises
# assert_match(/regex/, string)Minitest viene en la stdlib — cero dependencias. assert_equal compara valores. assert_raises verifica excepciones. refute niega. setup ejecuta antes de cada test. Rails usa Minitest por default. Alternativa: RSpec.
Instalação e Setup
Instalar Ruby (rbenv)
# macOS (Homebrew): brew install rbenv ruby-build rbenv install 3.3.0 rbenv global 3.3.0 # Linux (Ubuntu): sudo apt install rbenv ruby-build rbenv install 3.3.0 rbenv global 3.3.0 # Configurar shell (.bashrc/.zshrc): eval "$(rbenv init -)" # Verificar: ruby --version # ruby 3.3.0 (2024-01-01) [x86_64-linux]
rbenv es el gestor de versiones recomendado. Instala múltiples versiones lado a lado. rbenv global define la versión por defecto. rbenv local por proyecto (archivo .ruby-version). Preferir sobre rvm (más ligero y simple).
Gems y Bundler
# Instalar gem: gem install httparty gem install rails -v 7.1.0 # Listar gems: gem list gem list --local # Bundler (gestión de dependencias): bundle init # crea el Gemfile # Gemfile: source "https://rubygems.org" gem "rails", "~> 7.1" gem "puma", ">= 5.0" group :test do gem "rspec" end # Comandos: bundle install # instala del Gemfile bundle update # actualiza gems bundle exec rspec # ejecuta con las gems
gem install instala paquetes. Bundler gestiona dependencias por proyecto. Gemfile declara gems, Gemfile.lock fija versiones. ~> permite patches. bundle exec garantiza el entorno correcto. Esencial para proyectos reales.
Windows y Docker
# Windows: # 1. Descargar en rubyinstaller.org # 2. Instalar con MSYS2 (checkbox) # 3. Verificar en la terminal: ruby --version gem --version # Docker: docker run -it ruby:3.3 ruby -e "puts 'Hola'" # Dockerfile: # FROM ruby:3.3-slim # WORKDIR /app # COPY Gemfile* ./ # RUN bundle install # COPY . . # CMD ["ruby", "app.rb"] # WSL2 (recomendado para Windows): wsl --install # Después instalar rbenv dentro de WSL
Windows: RubyInstaller con MSYS2. Docker tiene imágenes oficiales (ruby:3.3-slim). WSL2 es recomendado para desarrollo serio en Windows. Ruby 3.x requiere compilador C para gems nativas.
Estructura de proyecto
mi_proyecto/ ├── Gemfile # dependencias ├── Gemfile.lock # versiones exactas ├── Rakefile # tareas (rake) ├── .ruby-version # versión de Ruby ├── lib/ │ ├── mi_proyecto.rb │ └── mi_proyecto/ │ ├── version.rb │ └── core.rb ├── spec/ # tests (RSpec) │ └── core_spec.rb ├── bin/ │ └── console # IRB con el proyecto └── README.md # Rakefile: task :default => :spec task :spec do sh "bundle exec rspec" end
lib/ contiene el código. spec/ o test/ para tests. Gemfile + Gemfile.lock para dependencias. .ruby-version fija la versión (rbenv). Rakefile define tareas. bin/console abre IRB con el proyecto cargado.
Hello World y scripts
# hola.rb
puts "¡Hola, Mundo!"
# Sin main() — ejecuta de arriba a abajo
# puts = print con newline
# print = sin newline
# Ejecutar:
ruby hola.rb
# Script con argumentos:
# args.rb
puts "Args: #{ARGV.inspect}"
# ruby args.rb uno dos tres
# Args: ["uno", "dos", "tres"]
# Shebang (Linux/Mac):
#!/usr/bin/env ruby
puts "Script ejecutable"
# chmod +x script.rb
# ./script.rbputs imprime con newline. print sin newline. Los scripts ejecutan top-level sin main(). ARGV es el array de argumentos. El shebang (#!/usr/bin/env ruby) hace los scripts ejecutables. Todo es objeto en Ruby.
Require y Load
# require: carga una vez (stdlib/gems) require "json" require "net/http" require "date" # require_relative: ruta relativa require_relative "lib/mi_clase" require_relative "../utils/helper" # load: recarga siempre (dev) load "config.rb" # autoload: lazy loading autoload :Parser, "lib/parser" # ¡Parser solo se carga cuando se usa! # En el Gemfile (Bundler): require "bundler/setup" Bundler.require(:default) # Carga todas las gems del Gemfile
require carga archivo/gem una vez. require_relative usa ruta relativa al archivo. load recarga siempre (útil en dev). autoload hace lazy loading. Bundler.require carga todas las gems del Gemfile automáticamente.
IRB (REPL interactivo)
# Iniciar:
$ irb
irb> 2 + 3
=> 5
irb> "ruby".upcase
=> "RUBY"
irb> [1,2,3].map { |n| n * 2 }
=> [2, 4, 6]
irb> exit
# Pry (REPL mejorado):
gem install pry
$ pry
pry> ls String # lista métodos
pry> show-source Array#map
pry> cd [1,2,3] # navega al objeto
# Consejos:
# Tab = autocompletar
# _ = último resultadoIRB es el REPL nativo de Ruby. => muestra el resultado. Pry es una alternativa con más features (ls, show-source, cd). Ideal para experimentar y hacer debug. Tab autocompleta métodos.
Rake (tareas)
# Rakefile
desc "Dice hola"
task :hola do
puts "¡Hola desde Rake!"
end
desc "Tarea con argumentos"
task :saludar, [:nombre] do |t, args|
puts "¡Hola, #{args.nombre}!"
end
# Dependencias entre tareas:
task :build => [:clean, :compile]
task :clean do
puts "Limpiar..."
end
task :compile do
puts "Compilar..."
end
# Ejecutar:
rake hola
rake "saludar[Ana]"
rake build # ejecuta clean + compile
rake -T # lista tareasRake es el make de Ruby. task :nombre define tareas. desc añade descripción. Dependencias con =>. Argumentos con [:arg]. rake -T lista tareas. Rails usa Rake extensivamente (rake db:migrate).
Ecossistema
Rails (básico)
# Instalar:
gem install rails
rails new mi_app
cd mi_app
rails server # http://localhost:3000
# Estructura MVC:
# app/models/ → Model (ActiveRecord)
# app/controllers/ → Controller
# app/views/ → View (ERB)
# Generar scaffold:
rails generate scaffold Post title:string body:text
rails db:migrate
# Model:
class Post < ApplicationRecord
validates :title, presence: true
has_many :comments
end
# Controller:
class PostsController < ApplicationController
def index
@posts = Post.all
end
endRails es el framework web más popular de Ruby. Convención sobre configuración. ActiveRecord es el ORM. scaffold genera CRUD completo. rails db:migrate gestiona el schema. MVC: Models, Controllers, Views. Ecosistema enorme de gems.
Debugging
# binding.irb (breakpoint): def método_con_bug(x) resultado = x * 2 binding.irb # ¡para aquí! resultado + 1 end # binding.pry (mejor): # gem "pry" require "pry" def mi_método binding.pry end # Comandos en Pry: # ls → variables locales # cd objeto → navegar # show-source → ver código # whereami → ubicación # step/next → debug paso a paso # debug gem (Ruby 3.1+): # gem "debug" require "debug" binding.b # breakpoint moderno # Quick debug: p variable # imprime con inspect puts variable.inspect
binding.irb / binding.pry crean breakpoints interactivos. Pry tiene más features (ls, cd, show-source). Ruby 3.1+ tiene la gem debug nativa. p imprime con .inspect para debug rápido.
Sinatra (micro-framework)
# Gemfile:
# gem "sinatra"
require "sinatra"
get "/" do
"¡Hola, Mundo!"
end
get "/hola/:nombre" do
"¡Hola, #{params[:nombre]}!"
end
post "/datos" do
body = request.body.read
"Recibí: #{body}"
end
# Ejecutar:
# ruby app.rb
# http://localhost:4567
# Con views (ERB):
get "/pagina" do
@título = "Mi Página"
erb :pagina # views/pagina.erb
endSinatra es un micro-framework — ideal para APIs pequeñas y prototipos. Rutas con verbos HTTP (get, post). params accede a los parámetros. erb renderiza templates. Sin ORM — añadir ActiveRecord o Sequel manualmente.
Performance
# Benchmark:
require "benchmark"
Benchmark.bm do |x|
x.report("map:") { 1_000_000.times.map { |i| i * 2 } }
x.report("each:") { arr = []; 1_000_000.times.each { |i| arr << i * 2 } }
end
# benchmark-ips (gem):
# gem "benchmark-ips"
Benchmark.ips do |x|
x.report("gsub") { "hello".gsub("l", "r") }
x.report("tr") { "hello".tr("l", "r") }
x.compare!
end
# Consejos de performance:
# - freeze strings: # frozen_string_literal: true
# - lazy para colecciones grandes
# - Set#include? vs Array#include?
# - Evitar asignaciones en loops
# - ObjectSpace para memory profilingBenchmark de la stdlib para mediciones. benchmark-ips para comparaciones estadísticas. # frozen_string_literal: true evita asignación de strings. lazy para colecciones grandes. Set para lookups O(1). Evitar asignaciones en hot paths.
RSpec
# Gemfile: gem "rspec"
# bundle exec rspec --init
# spec/calculadora_spec.rb
RSpec.describe Calculadora do
describe "#suma" do
it "suma dos números" do
calc = Calculadora.new
expect(calc.suma(2, 3)).to eq(5)
end
it "suma negativos" do
expect(subject.suma(-1, -2)).to eq(-3)
end
end
context "con división" do
it "lanza error en cero" do
expect { subject.dividir(1, 0) }
.to raise_error(ZeroDivisionError)
end
end
end
# Matchers: eq, be, include, match
# be_truthy, be_nil, be > 5RSpec es el framework de tests más popular. Sintaxis descriptiva (describe, context, it). expect().to para aserciones. subject es el objeto bajo test. Matchers ricos: eq, include, raise_error. TDD/BDD friendly.
Deploy y producción
# Dockerfile para app Ruby: # FROM ruby:3.3-slim # RUN apt-get update && apt-get install -y build-essential # WORKDIR /app # COPY Gemfile* ./ # RUN bundle install --without development test # COPY . . # CMD ["ruby", "app.rb"] # Puma (web server): # gem "puma" # bundle exec puma -p 3000 # Variables de entorno: # gem "dotenv" require "dotenv/load" DB_URL = ENV["DATABASE_URL"] # Producción: # - RACK_ENV=production # - bundle install --without development test # - Usar Puma/Unicorn como server # - Logs: STDOUT (12-factor) # - Health check endpoint
Docker con imagen ruby:3.3-slim. Puma es el web server estándar. dotenv para variables de entorno. RACK_ENV=production activa optimizaciones. Logs a STDOUT (12-factor). Plataformas: Heroku, Fly.io, Render, Railway.
RuboCop (linting)
# Instalar: gem install rubocop # Ejecutar: rubocop # verifica todo rubocop app/ # solo la carpeta app/ rubocop -a # auto-corrige # .rubocop.yml (config): AllCops: TargetRubyVersion: 3.3 NewCops: enable Style/StringLiterals: EnforcedStyle: double_quotes Metrics/MethodLength: Max: 20 Layout/LineLength: Max: 120 # Categorías: # Style/ → estilo de código # Layout/ → formato # Lint/ → bugs potenciales # Metrics/ → complejidad
RuboCop es el linter/formatter estándar de Ruby. Verifica estilo, layout, bugs y complejidad. -a auto-corrige. .rubocop.yml configura reglas. Integrar en CI y editor. Categorías: Style, Layout, Lint, Metrics.
Herramientas útiles
# Solargraph (LSP para editores):
gem install solargraph
# Configurar en VS Code / Neovim
# yard (documentación):
gem install yard
yard doc lib/
yard server # http://localhost:8808
# gem "awesome_print":
require "ap"
ap({ nombre: "Ana", items: [1, 2, 3] })
# Output coloreado y formateado
# gem "httparty" (HTTP simple):
require "httparty"
respuesta = HTTParty.get("https://api.ex.com/datos")
respuesta.parsed_response
# gem "sequel" (SQL builder):
require "sequel"
DB = Sequel.connect("sqlite://db.sqlite3")
DB[:users].where(age: 25).all
# gem "sidekiq" (background jobs):
# Procesamiento asíncrono con RedisSolargraph da autocomplete/LSP. YARD genera documentación. awesome_print para debug coloreado. HTTParty simplifica HTTP. Sequel es un SQL builder ligero. Sidekiq para background jobs. Ecosistema rico de gems para todo.