¿Qué es Ruby on Rails?
Rails es un framework web full-stack para Ruby. Incluye todo lo que necesita una aplicación web respaldada por una base de datos: un ORM, un router, una capa de vistas, jobs en segundo plano, mailers, caching, WebSockets y un entorno de pruebas. En lugar de elegir y conectar cada una de estas piezas por tu cuenta, heredas un stack coherente diseñado para trabajar en conjunto.
Su idea fundamental es la convención sobre la configuración. El framework asume una estructura lógica — Post vive en app/models/post.rb, se mapea a una tabla de posts y es servido por PostsController — y solo escribes configuración cuando te desvías de ella. El equipo de Rails llama a esta filosofía omakase, la elección del chef: confía en los valores predeterminados y avanzarás más rápido.
Ese intercambio es la razón por la cual Rails ha impulsado productos desde Basecamp y GitHub hasta Shopify y HEY durante veinte años, y por la cual un solo desarrollador aún puede lanzar una aplicación seria en un fin de semana.
Convención sobre configuración
Todos los proyectos de Rails tienen el mismo esqueleto, por lo que nunca tienes que preguntarte dónde va cada cosa.
app/
├── controllers/ # request handling
├── models/ # domain and persistence
├── views/ # templates
├── jobs/ # background work
├── mailers/ # email
└── javascript/ # Stimulus controllers
config/
├── routes.rb # the URL map
└── database.yml # connection settings
db/
└── migrate/ # schema history
La recompensa es la predictibilidad. Un desarrollador nuevo abre el repositorio y ya sabe que el modelo User está en app/models/user.rb y que su tabla es users. El nombrado no es un debate de estilo; es una interfaz de la que depende el framework.
El ciclo de peticiones MVC
Rails es un framework Model-View-Controller, y cada petición HTTP sigue el mismo camino:
- El Routing asocia la URL con una acción del controlador.
- El controller carga o modifica datos a través de los modelos.
- Los Models aplican las reglas de dominio y se comunican con la base de datos.
- Una view renderiza la respuesta, generalmente como HTML.
# config/routes.rb
Rails.application.routes.draw do
resources :posts
root "posts#index"
end
Una petición a GET /posts llega a PostsController#index, que prepara @posts, tras lo cual se renderiza app/views/posts/index.html.erb. Mantener cada capa enfocada en su responsabilidad es lo que permite que una aplicación de Rails sea mantenible a medida que crece.
La línea de comandos es el framework
Casi no interactúas con Rails a través de archivos de configuración. La mayor parte del trabajo ocurre en la terminal.
rails new shop --database=postgresql
cd shop
bin/rails generate scaffold Post title:string body:text published:boolean
bin/rails generate model Comment post:references body:text
bin/rails generate controller Pages home about
bin/rails db:create db:migrate
bin/rails server
generate escribe por ti el modelo, la migración, el controlador, las rutas, las vistas y los tests, y la salida del generador te indica qué ha cambiado. Lee cada archivo generado antes de conservarlo; la forma más rápida de aprender Rails es estudiar lo que escribe y luego borrar lo que no necesites.
Active Record: la capa de modelo
Active Record es el ORM. Una clase de modelo envuelve una tabla de la base de datos, y una instancia envuelve una fila.
Migraciones
Las migraciones son cambios de esquema versionados y reversibles almacenados en db/migrate. Son la única forma soportada de modificar la base de datos.
class CreatePosts < ActiveRecord::Migration[8.0]
def change
create_table :posts do |t|
t.string :title, null: false
t.text :body, null: false
t.boolean :published, default: false, null: false
t.references :author, null: false, foreign_key: { to_table: :users }
t.timestamps
end
add_index :posts, %i[published created_at]
end
end
Ejecuta bin/rails db:migrate para aplicarla y bin/rails db:rollback para deshacerla. Debido a que change es reversible, Rails sabe cómo revertir las operaciones sin que tengas que escribir un método down.
Asociaciones
Las asociaciones describen relaciones y generan métodos convenientes.
class User < ApplicationRecord
has_many :posts, dependent: :destroy
has_many :comments, through: :posts
end
class Post < ApplicationRecord
belongs_to :author, class_name: "User", inverse_of: :posts
has_many :comments, dependent: :destroy
has_one_attached :cover
end
Con esto configurado, user.posts, post.author y post.comments.create(body: "...") funcionan, y dependent: :destroy evita que queden filas huérfanas en la base de datos.
Validaciones y scopes
Las validaciones protegen el dominio en la capa de modelo, por donde pasa cada escritura.
class Post < ApplicationRecord
validates :title, presence: true, length: { maximum: 120 }
validates :body, presence: true
validates :slug, uniqueness: true
scope :published, -> { where(published: true) }
scope :recent, -> { order(created_at: :desc) }
before_validation :generate_slug, on: :create
private
def generate_slug
self.slug ||= title.to_s.parameterize
end
end
Los scopes devuelven relaciones, por lo que se pueden componer: Post.published.recent.limit(20) se lee como una frase y se ejecuta como una sola consulta. Cuando un scope supere una o dos líneas, conviértelo en un método de clase para que siga siendo legible.
Evitando consultas N+1
El problema de rendimiento más común en Rails es la consulta N+1: una consulta para obtener una lista y luego una consulta más por cada asociación de cada fila.
# Two queries total, no matter how many posts.
posts = Post.includes(:author, :comments).recent
posts.each do |post|
puts "#{post.title} by #{post.author.name}"
end
Usa includes cuando vayas a acceder a la asociación, preload cuando quieras una consulta separada, y eager_load cuando también filtres por la asociación. Añade la gema bullet al entorno de development y marcará cada N+1 antes de que llegue a producción.
Controladores y rutas RESTful
resources declara las rutas CRUD convencionales y las mapea a acciones del controlador con nombres coincidentes.
# config/routes.rb
Rails.application.routes.draw do
resources :posts do
resources :comments, only: %i[index create destroy]
end
namespace :api do
namespace :v1 do
resources :posts, only: %i[index show create update destroy]
end
end
end
Los controladores deben permanecer ligeros. Se encargan de cargar datos, llamar al dominio y elegir una respuesta. La lógica de negocio pertenece a los modelos o a los objetos de servicio, no a las acciones.
class PostsController < ApplicationController
before_action :set_post, only: %i[show update destroy]
def index
@posts = Post.published.includes(:author).recent
end
def create
@post = Post.new(post_params)
if @post.save
redirect_to @post, notice: "Post created."
else
render :new, status: :unprocessable_entity
end
end
private
def set_post
@post = Post.find(params[:id])
end
def post_params
params.require(:post).permit(:title, :body, :published, tag_ids: [])
end
end
Los strong parameters no son opcionales. params.require(:post).permit(...) es la barrera que evita que un cliente modifique columnas que nunca tuviste la intención de exponer, como admin o role.
Vistas, ERB y layouts
Las vistas son plantillas ERB: HTML con Ruby embebido. Rails escapa la salida por defecto, lo que elimina toda una categoría de errores de cross-site scripting.
<%# app/views/posts/index.html.erb %>
<h1>Posts</h1>
<%= link_to "New post", new_post_path %>
<ul>
<% @posts.each do |post| %>
<li>
<%= link_to post.title, post %>
<span><%= pluralize(post.comments.size, "comment") %></span>
</li>
<% end %>
</ul>
Usa <%= %> para imprimir contenido y <% %> para ejecutar código sin imprimir. Un layout en app/views/layouts/application.html.erb envuelve cada página, y los partials —archivos que comienzan con un guion bajo— permiten reutilizar el marcado mediante render "post", post: post o simplemente render @posts.
Hotwire: Turbo y Stimulus
El Rails moderno añade interactividad sin necesidad de una single-page app. Turbo intercepta los enlaces y formularios para intercambiar fragmentos de página a través de la red; Stimulus añade pequeños comportamientos de JavaScript donde realmente son necesarios.
<%# Turbo Frames scope an update to one part of the page %>
<%= turbo_frame_tag "comments" do %>
<%= render @post.comments %>
<% end %>
// app/javascript/controllers/dropdown_controller.js
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["menu"];
toggle() {
this.menuTarget.classList.toggle("hidden");
}
}
Esto es mejora progresiva en la práctica: el HTML renderizado en el servidor es la base, y JavaScript lo mejora. La mayoría de las apps de Rails nunca necesitan un router en el lado del cliente.
Assets y import maps
Rails 7 reemplazó el antiguo pipeline de Sprockets por import maps de forma predeterminada y ofrece Propshaft como un asset pipeline más sencillo. Puedes anclar una librería sin necesidad de un paso de compilación con Node.
bin/importmap pin local-time
bin/rails assets:precompile
Si prefieres un bundler, bin/rails javascript:install:esbuild o :vite cambian la configuración. El principio es el mismo: servir lo que el navegador entiende y mantener la toolchain fuera del camino hasta que sea necesaria.
Tareas en segundo plano (Background jobs)
Cualquier proceso lento —enviar un correo electrónico, llamar a una API, generar un reporte— debe ir en un job, no en la solicitud. Active Job es la interfaz común; el adaptador decide dónde se ejecutan los jobs.
class PublishPostJob < ApplicationJob
queue_as :default
retry_on Net::ReadTimeout, wait: :polynomially_longer, attempts: 5
def perform(post_id)
Post.find(post_id).publish!
end
end
PublishPostJob.perform_later(post.id)
# config/application.rb
config.active_job.queue_adapter = :solid_queue # or :sidekiq
Rails 8 incluye Solid Queue, que mantiene los jobs en la base de datos y no requiere Redis, por lo que una aplicación de un solo servidor tiene una pieza móvil menos. Sidekiq sigue siendo una opción sólida para volúmenes altos de datos.
Pruebas
Rails genera pruebas para todo lo que crea. Minitest viene incluido por defecto y es rápido; RSpec es la alternativa popular con una sintaxis más expresiva.
# test/models/post_test.rb
require "test_helper"
class PostTest < ActiveSupport::TestCase
test "requires a title" do
post = Post.new(body: "Hello")
assert_not post.valid?
assert_includes post.errors[:title], "can't be blank"
end
end
# spec/models/post_spec.rb
RSpec.describe Post, type: :model do
it "is invalid without a title" do
expect(Post.new(body: "Hello")).not_to be_valid
end
end
Las pruebas de controlador realizan solicitudes reales en el mismo proceso, lo que permite detectar errores de enrutamiento y de parámetros que las pruebas unitarias pasan por alto.
# test/controllers/posts_controller_test.rb
class PostsControllerTest < ActionDispatch::IntegrationTest
test "lists published posts" do
Post.create!(title: "Hi", body: "There", published: true)
get posts_url
assert_response :success
end
end
Ejecuta todo con bin/rails test o bundle exec rspec. Añade bin/rails test:system para pruebas de sistema basadas en el navegador cuando un flujo sea lo suficientemente importante como para probarlo de extremo a extremo.
La consola de Rails
bin/rails console inicia tu aplicación con todos los modelos y helpers disponibles. Es la forma más rápida de explorar datos, probar una consulta y reproducir un error.
bin/rails console
bin/rails console --sandbox # rolls back all changes on exit
Post.published.recent.limit(5)
Post.where(published: false).update_all(published: true)
Post.includes(:author).find_each { |post| puts post.title }
--sandbox es una red de seguridad mientras experimentas: cualquier escritura se revierte al salir.
Mejores prácticas
- Mantén los controladores ligeros; mueve la lógica de dominio a los modelos o a objetos de servicio.
- Carga las asociaciones de forma anticipada con
includesy supervisa los logs para detectar consultas N+1. - Modifica la base de datos únicamente a través de migraciones y haz commit de las mismas junto con el código.
- Utiliza strong parameters para cada escritura y valida en la capa del modelo.
- Añade índices de base de datos en las columnas que utilices para filtrar y ordenar.
- Delega las tareas lentas a jobs en segundo plano en lugar de ejecutarlas en el ciclo de la solicitud.
- Escribe un test por cada bug que corrijas y mantén la suite de pruebas rápida.
Errores comunes
- Renderizar una lista sin
includesy desplegar un problema de N+1 a producción. - Colocar la lógica de negocio en los controladores hasta que las acciones tengan cientos de líneas.
- Usar
params[:post]directamente en lugar de strong parameters. - Editar
db/schema.rba mano en lugar de escribir una migración. - Olvidar las opciones de
dependent:y dejar registros huérfanos. - Recurrir a un service object o a una gem antes de que el modelo lo justifique.
- Ejecutar consultas pesadas en las vistas en lugar de cargar los datos en el controlador.
Próximos pasos
Rails enseña una forma de pensar robusta: recursos, convenciones y un lugar único para cada responsabilidad. Si te gusta el modelo “batteries-included”, Laravel aplica la misma filosofía en PHP, mientras que Django lo hace en Python. Debido a que el enrutamiento de Rails se basa en recursos, la guía de REST APIs explica los conceptos fundamentales. Y dado que la mayoría de las aplicaciones de Rails funcionan sobre una base de datos relacional, el siguiente paso ideal es profundizar tus conocimientos de PostgreSQL.