Módulos en Ruby: qué son, para qué sirven y mixins
Un módulo es como una caja de herramientas: no es un objeto que haga nada por sí mismo, pero cualquier clase puede cogerla y usar lo que trae dentro.
¿Qué son los módulos en Ruby?
Los módulos se parecen a las clases porque contienen métodos, constantes y otros módulos. La diferencia es que no se pueden instanciar ni heredar de ellos:
module Numeros
end
Numeros.new # NoMethodError: undefined method 'new' for module Numeros
Ruby no tiene interfaces ni herencia múltiple. En su lugar, usa módulos.
¿Buscabas el operador módulo (%), el resto de una división? Está en operador módulo (%).
Para qué sirven los módulos
- Como librería o espacio de nombres (namespace). Agrupan métodos y constantes bajo un nombre para que no choquen con otros que se llamen igual.
- Para ampliar las clases (mixin). Si una clase incluye un módulo, sus objetos ganan los métodos del módulo.
Ejemplo de módulo como librería:
module Geometria
PI_APROX = 3.14
def self.area_circulo(radio) = PI_APROX * radio * radio
end
puts Geometria.area_circulo(2) # 12.56
Módulos dentro de módulos: el operador ::
Un módulo puede contener clases y otros módulos. Es como el apellido de una familia: puede haber dos Producto en el mismo programa si uno es Tienda::Producto y otro Almacen::Producto. Para llegar a lo que hay dentro se usa :::
module Tienda
IVA = 0.21
class Producto
attr_reader :nombre, :precio
def initialize(nombre, precio)
@nombre = nombre
@precio = precio
end
def precio_con_iva = (precio * (1 + IVA)).round(2)
end
module Formato
def self.euros(cantidad) = format("%.2f €", cantidad)
end
end
libro = Tienda::Producto.new("Libro", 20)
puts Tienda::Formato.euros(libro.precio_con_iva) # 24.20 €
puts Tienda::IVA # 0.21
Dentro de Producto basta con escribir IVA: Ruby busca la constante en las clases y módulos que la rodean, de dentro afuera (primero Tienda::Producto, luego Tienda; es la lista que devuelve Module.nesting). Desde fuera hay que nombrarla entera, Tienda::IVA. Así organizan sus clases las gemas y Rails, y suele coincidir con la estructura de carpetas: Tienda::Producto vive en tienda/producto.rb. Cómo cargar esos ficheros se explica en incluir otros archivos en Ruby.
module_function: un método que sirve de las dos formas
A veces quieres llamar a un método como Conversor.km_a_millas(10) y, además, poder incluirlo en una clase. module_function crea las dos versiones a la vez:
module Conversor
module_function
def km_a_millas(km) = (km * 0.621371).round(2)
end
puts Conversor.km_a_millas(10) # 6.21
class Viaje
include Conversor
def resumen(km) = "#{km} km son #{km_a_millas(km)} millas"
end
puts Viaje.new.resumen(5) # 5 km son 3.11 millas
Viaje.new.km_a_millas(5)
private method 'km_a_millas' called for an instance of Viaje (NoMethodError)
La copia que llega a la clase es privada: Viaje la usa por dentro, pero no la ofrece hacia fuera. Así es como funciona Math: puedes escribir Math.sqrt(16) o incluir Math y escribir sqrt(16).
¿Qué es un mixin?
Un mixin es un módulo que se «mezcla» en una clase para darle métodos que no ha heredado. La clase sigue teniendo un único padre, pero puede incluir tantos módulos como quiera.
Hay dos formas de hacerlo: include y extend.
include: todos los objetos de la clase ganan los métodos
module Numeros
def to_s
case @value
when 1 then "uno"
when 2 then "dos"
when 3 then "tres"
else "mucho"
end
end
end
class Contador
include Numeros
attr_accessor :value
end
contador = Contador.new
contador.value = 3
puts contador.to_s # tres
puts contador.is_a?(Numeros) # true
p Contador.ancestors.first(3) # [Contador, Numeros, Object]
Fíjate en la última línea: el módulo se coloca en la cadena de antepasados de la clase, justo encima de ella. Por eso is_a?(Numeros) devuelve true, aunque no sea una subclase.
El módulo Comparable
Ruby trae módulos listos para usar. Comparable te da <, >, ==, between?, clamp… con solo definir el método <=>:
class Tramo
include Comparable
attr_reader :nombre, :distancia
def initialize(nombre, distancia)
@nombre = nombre
@distancia = distancia
end
def <=>(otro_tramo)
distancia <=> otro_tramo.distancia
end
end
tramo1 = Tramo.new("Tramo 1", 1000)
tramo2 = Tramo.new("Tramo 2", 2000)
puts tramo1 <=> tramo2 # -1
puts tramo1 < tramo2 # true
puts tramo1 == tramo2 # false
puts tramo1 > tramo2 # false
puts [tramo2, tramo1].min.nombre # Tramo 1
extend: solo un objeto (o la clase) gana los métodos
Con extend el módulo se añade a un objeto concreto. Los demás objetos de la misma clase no cambian:
class Marcador
attr_accessor :value
def to_s = @value.to_s
end
a = Marcador.new
a.value = 3
b = Marcador.new
b.value = 2
puts a.to_s # 3
a.extend(Numeros)
puts a.to_s # tres
puts b.to_s # 2 (b no ha cambiado)
Si haces extend dentro de una clase, el objeto que se amplía es la propia clase, así que los métodos del módulo pasan a ser métodos de clase:
module Saludo
def saluda = "Hola desde #{name}"
end
class Persona
extend Saludo
end
puts Persona.saluda # Hola desde Persona
include |
extend |
|
|---|---|---|
| ¿Quién gana los métodos? | Todos los objetos de la clase | Un objeto concreto, o la clase si se usa dentro de ella |
| Los métodos son… | De instancia | Del objeto (o de clase) |
Diferencias entre módulos y clases
| Módulos | Clases | |
|---|---|---|
| Instancias | No pueden crear objetos. | Crean objetos, y cada uno tiene su estado (sus variables de instancia). |
| Mezcla | Se pueden mezclar en clases y en otros módulos. Sus métodos y constantes se añaden a quien los incluye. | No se pueden mezclar en nada. |
| Herencia | Un módulo no hereda ni se puede heredar de él. | Una clase hereda de otra, nunca de un módulo. |
La gran ventaja frente a la herencia es que puedes incluir varios módulos a la vez, mientras que una clase solo tiene un padre. Así cada módulo aporta una característica, sin tener que montar jerarquías de clases.
Rails usa mucho esta técnica, y también la de reabrir clases y módulos ya definidos para añadirles métodos. Un ejemplo son los concerns de Rails, que son módulos con algo de ayuda extra.
¿Cuándo heredar de una clase y cuándo incluir un módulo?
Hazte esta pregunta: ¿la clase hija «es un» tipo de la clase padre?
- Sí: usa herencia.
- No: usa un módulo.
Por ejemplo: ¿un autobús es un vehículo? Sí, así que Autobus < Vehiculo. ¿Una radio es un autobús? No: la radio es algo que el autobús tiene o sabe hacer, así que encaja mejor en un módulo.
Cómo se escribe la herencia con < y super se explica en Clases en Ruby.
Herencia para lo que algo es; módulos para lo que algo sabe hacer.
Errores frecuentes
1. Llamar al método de un módulo sin haberlo definido con self. Un def normal dentro de un módulo crea un método para quien lo incluya, no para el propio módulo:
module Geometria
def area_circulo(radio) = 3.14 * radio * radio
end
puts Geometria.area_circulo(2)
undefined method 'area_circulo' for module Geometria (NoMethodError)
Escribe def self.area_circulo si lo vas a llamar como Geometria.area_circulo, o usa module_function.
2. Usar include cuando querías extend. Con include, los métodos son para los objetos, no para la clase:
module Saludo
def saluda = "Hola desde #{name}"
end
class Persona
include Saludo
end
puts Persona.saluda
undefined method 'saluda' for class Persona (NoMethodError)
El mensaje da la pista: dice for class Persona, no for an instance of Persona. Estás llamando a la clase, así que necesitas extend Saludo.
3. Incluir una clase. Solo se pueden mezclar módulos:
class Contador
include String
end
wrong argument type Class (expected Module) (TypeError)
4. Incluir Comparable sin definir <=>. El módulo trae <, > y compañía, pero todos dependen de <=>. Si no lo defines, Ruby usa el de Object, que solo sabe decir si dos objetos son el mismo:
class Tramo
include Comparable
attr_reader :distancia
def initialize(distancia)
@distancia = distancia
end
end
puts Tramo.new(1000) < Tramo.new(2000)
comparison of Tramo with Tramo failed (ArgumentError)
5. Escribir el nombre en minúscula. El nombre de un módulo es una constante, así que empieza por mayúscula:
module tienda
end
tienda.rb: --> tienda.rb
unexpected constant path after `module`; class/module name must be CONSTANT
> 1 module tienda
> 2 end
tienda.rb:1: syntax error found (SyntaxError)
> 1 | module tienda
| ^~~~~~ unexpected constant path after `module`; class/module name must be CONSTANT
2 | end
Compruébalo con un test
Un módulo pensado para mezclarse se prueba con una clase mínima hecha solo para el test. Así compruebas lo que el módulo promete sin depender de ninguna clase real. Este test usa Minitest, que viene con Ruby, y Struct, que crea en una línea una clase con los atributos que le digas:
require "minitest/autorun"
module Medible
include Comparable
def <=>(otro) = distancia <=> otro.distancia
end
class MedibleTest < Minitest::Test
# Una clase mínima, solo para el test: lo único que Medible necesita es #distancia
Tramo = Struct.new(:distancia) { include Medible }
def test_compara_por_distancia
corto = Tramo.new(1000)
largo = Tramo.new(2000)
assert corto < largo
assert_equal corto, [largo, corto].min
assert Tramo.new(1500).between?(corto, largo)
end
end
ruby medible_test.rb
1 runs, 3 assertions, 0 failures, 0 errors, 0 skips
Si alguien da la vuelta a la comparación (otro.distancia <=> distancia), el código sigue pareciendo razonable, pero el test lo detecta:
MedibleTest#test_compara_por_distancia [medible_test.rb:17]:
Expected false to be truthy.
El test también deja escrito el contrato del módulo: quien lo incluya tiene que responder a distancia. Para empezar con tests en Ruby, lee la introducción al testing en Ruby.
Cuándo no usar un módulo
Un módulo es la herramienta correcta para una capacidad que comparten varias clases sin estar emparentadas, como Comparable. Hay tres señales de que no encaja:
- El módulo depende de variables que no son suyas. El
Numerosdel principio lee@value, pero no lo crea: confía en que la clase lo tenga. Si una clase lo llama de otra forma, nada falla; el módulo simplemente da un resultado equivocado:
# Numeros es el módulo de la sección «include», que lee @value
class Marcador
include Numeros
attr_accessor :valor # se llama valor, no value
end
m = Marcador.new
m.valor = 1
puts m.to_s # mucho
Sin error, sin aviso: @value vale nil y el case cae en el else. Es más seguro que el módulo pida un método con nombre (como Medible pide distancia) que leer variables de instancia ajenas: si el método falta, al menos salta un error (NameError o NoMethodError) que dice qué falta.
- El módulo es un cajón de sastre. Un
Utilscon veinte métodos sin relación entre sí no agrupa nada; solo esconde el desorden. Si te cuesta ponerle un nombre que diga qué hace, probablemente son varios módulos, o métodos que pertenecen a una clase concreta. - Solo lo usa una clase. Sacar código a un módulo para que la clase «parezca» más corta no reduce la complejidad: la reparte en dos ficheros. Si además necesita guardar estado propio, suele ser mejor un objeto aparte que la clase recibe y usa (composición), que se puede probar por separado.
¿Se pueden hacer subclases de módulos?
No. Lo que sí puedes es incluir un módulo en una clase o en otro módulo. No crea una subclase, pero, como has visto, is_a? devuelve true para el módulo incluido. Es la forma que tiene Ruby de conseguir algo parecido a la herencia múltiple.
Ejercicio de cinco minutos
- Copia el test de
Medibley añade un caso conclamp: un tramo de 3000 limitado entrecortoylargodebe darlargo. ¿Tienes que escribir algo en el módulo para que pase? - Crea un módulo
Tiendacon una claseCarritodentro y llámala desde fuera conTienda::Carrito.new. - Reescribe
Numerospara que llame a un métodovaloren vez de leer@value, e inclúyelo en una clase que no lo tenga. Fíjate en qué error sale ahora y compáralo con elmuchosilencioso de antes.
¿Quieres practicar más? Tienes un ejercicio práctico de módulos en Ruby y más ejemplos de mixin en Ruby, con prepend incluido.
Fuentes
- Documentación oficial de Ruby 3.4: Module (
include,extend,Module.nestingymodule_function, cuyas copias de instancia son privadas), Comparable y Struct. - Todos los ejemplos se han ejecutado en Docker con la imagen oficial
ruby:3.4(Ruby 3.4.12): los del artículo original el 8 de noviembre de 2026; los espacios de nombres,module_function, los errores frecuentes y el test (Minitest 5.25.4), el 11 de noviembre de 2026. Estos últimos se han repetido conruby:4.0(Ruby 4.0.9, Minitest 6.0.0) con la misma salida. Las salidas de error están recortadas a las líneas que importan. - Vídeo: Curso de Ruby - 26 - Módulos, include y extend, de FuryCode.