DevTools

Cheatsheet Ruby

Referência completa da linguagem Ruby — elegante, expressiva e orientada a objetos

Volver a los lenguajes
Ruby
64 tarjetas encontradas
Categorías:
Versiones:

Básico


8 cards
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") # true

La 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/arrays

Los 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


8 cards
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
end

Los 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 0

begin/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


8 cards
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 }  # 5

sort 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


8 cards
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 * 3

Retorno 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
end

Los 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 mismo

Los 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)
end

Proc 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, DSLs

method_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


8 cards
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_h

Struct 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
end

Ruby 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
end

define_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.new

Modules 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
end

Refinements 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 clase

public (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


8 cards
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.saldo

Rescatar 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 item

Ruby 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.chomp

File.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


8 cards
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.rb

puts 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 resultado

IRB 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 tareas

Rake 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


8 cards
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
end

Rails 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
end

Sinatra 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 profiling

Benchmark 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 > 5

RSpec 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 Redis

Solargraph 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.