Inicio / Ruby / Ruby on Rails 8: Desarrollo Fullstack / Arquitectura MVC en Rails

Arquitectura MVC en Rails

Model-View-Controller, flujo de peticiones HTTP, Zeitwerk y convenciones de nombres.

Principiante

Arquitectura MVC en Rails

El patrón Model-View-Controller (MVC) es el corazón de Rails. Comprender cómo fluye una petición a través de estas tres capas es esencial para desarrollar aplicaciones bien organizadas.


¿Qué es MVC?

MVC separa tu aplicación en tres componentes con responsabilidades claras:

          Petición HTTP
               │
               ▼
         ┌──────────┐
         │  Router   │  ← config/routes.rb
         └────┬─────┘
              │
              ▼
      ┌───────────────┐
      │  Controller   │  ← Coordina la lógica
      └──┬─────────┬──┘
         │         │
         ▼         ▼
   ┌──────────┐  ┌──────────┐
   │  Model   │  │   View   │
   │ (datos)  │  │ (HTML)   │
   └──────────┘  └──────────┘
Componente Responsabilidad
Model Representa datos, reglas de negocio, acceso a BD
View Genera la respuesta HTML (o JSON) para el usuario
Controller Recibe la petición, interactúa con el modelo y renderiza la vista

Flujo de una petición HTTP en Rails

Cuando un usuario visita http://localhost:3000/articles/5, sucede lo siguiente:

1. El servidor recibe la petición

GET /articles/5 HTTP/1.1

2. El Router busca la ruta

# config/routes.rb
Rails.application.routes.draw do
  resources :articles
end

Rails determina que GET /articles/5 debe ir a ArticlesController#show con params[:id] = 5.

3. El Controller procesa la petición

# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def show
    @article = Article.find(params[:id])
  end
end

4. El Model consulta la base de datos

# app/models/article.rb
class Article < ApplicationRecord
  # Active Record genera automáticamente:
  # - Article.find(5) → SELECT * FROM articles WHERE id = 5
  # - Article.all     → SELECT * FROM articles
  # - article.title   → acceso a la columna title
end

5. La View renderiza el HTML

<!-- app/views/articles/show.html.erb -->
<h1><%= @article.title %></h1>
<p><%= @article.body %></p>
<small>Publicado: <%= @article.created_at.strftime("%d/%m/%Y") %></small>

6. La respuesta llega al navegador

HTTP/1.1 200 OK
Content-Type: text/html

<h1>Mi primer artículo</h1>
<p>Contenido del artículo...</p>
<small>Publicado: 23/02/2026</small>

Estructura del directorio app/

El directorio app/ es donde vive casi todo tu código:

app/
├── assets/           # Hojas de estilo, imágenes
├── channels/         # Action Cable (WebSockets)
├── controllers/      # Controladores
│   ├── application_controller.rb   # Controlador base
│   ├── articles_controller.rb
│   └── concerns/     # Módulos compartidos entre controladores
├── helpers/          # Métodos auxiliares para vistas
│   └── articles_helper.rb
├── javascript/       # JavaScript (Import Maps o bundler)
├── jobs/             # Active Job (tareas en segundo plano)
├── mailers/          # Action Mailer (correos electrónicos)
├── models/           # Modelos Active Record
│   ├── application_record.rb  # Modelo base
│   ├── article.rb
│   └── concerns/     # Módulos compartidos entre modelos
└── views/            # Vistas
    ├── layouts/       # Layouts (plantillas base)
    │   └── application.html.erb
    ├── articles/      # Vistas del controlador Articles
    │   ├── index.html.erb
    │   ├── show.html.erb
    │   ├── _form.html.erb   # Partial (empieza con _)
    │   └── new.html.erb
    └── shared/        # Partials compartidos

El archivo config/routes.rb

El router es el punto de entrada de toda petición. Define qué controlador y acción manejan cada URL:

# config/routes.rb
Rails.application.routes.draw do
  # Ruta raíz
  root "pages#home"

  # Rutas RESTful completas
  resources :articles

  # Rutas individuales
  get  "about",   to: "pages#about"
  post "contact", to: "pages#contact"
end

Para ver todas las rutas definidas:

rails routes

# Salida:
#       Prefix  Verb   URI Pattern               Controller#Action
#     articles  GET    /articles(.:format)        articles#index
#               POST   /articles(.:format)        articles#create
#  new_article  GET    /articles/new(.:format)    articles#new
# edit_article  GET    /articles/:id/edit(.:format) articles#edit
#      article  GET    /articles/:id(.:format)    articles#show
#               PATCH  /articles/:id(.:format)    articles#update
#               DELETE /articles/:id(.:format)    articles#destroy

Convenciones de nombres

Rails sigue convenciones estrictas de nomenclatura. Si las respetas, todo funciona automáticamente:

Concepto Convención Ejemplo
Modelo Singular, CamelCase Article
Tabla BD Plural, snake_case articles
Controlador Plural, CamelCase + Controller ArticlesController
Archivo modelo Singular, snake_case app/models/article.rb
Archivo controlador Plural, snake_case app/controllers/articles_controller.rb
Vistas Plural, directorio app/views/articles/
Migración Descriptiva create_articles
Helper Plural ArticlesHelper
# Rails infiere automáticamente:
# Modelo Article → tabla "articles"
# ArticlesController → vistas en app/views/articles/
# resources :articles → rutas hacia ArticlesController

💡 Si necesitas romper una convención (por ejemplo, un nombre de tabla diferente), puedes configurarlo manualmente en el modelo con self.table_name = "mi_tabla".


Auto-carga de clases con Zeitwerk

Rails 8 usa Zeitwerk como cargador de código. Esto significa que nunca necesitas escribir require manualmente para archivos dentro de app/:

# NO necesitas hacer esto:
# require "app/models/article"
# require "app/services/payment_processor"

# Zeitwerk carga automáticamente basándose en la ruta del archivo:
# app/models/article.rb         → Article
# app/services/payment_processor.rb → PaymentProcessor
# app/models/admin/user.rb      → Admin::User

Reglas de Zeitwerk

# El nombre del archivo debe coincidir con el nombre de la clase:
# app/models/blog_post.rb → BlogPost       ✅
# app/models/blogpost.rb  → BlogPost       ❌ (no coincide)

# Los directorios se convierten en módulos (namespaces):
# app/controllers/admin/users_controller.rb → Admin::UsersController

# Puedes agregar directorios personalizados a la auto-carga:
# config/application.rb
config.autoload_paths << Rails.root.join("app/services")
config.autoload_paths << Rails.root.join("app/validators")

Recargar código en desarrollo

En modo desarrollo, Zeitwerk recarga automáticamente los archivos cuando los modificas. No necesitas reiniciar el servidor para ver cambios en modelos, controladores o vistas.

# Si necesitas forzar la recarga en consola:
reload!

El Application Controller

Todos los controladores heredan de ApplicationController, que a su vez hereda de ActionController::Base:

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  # Métodos aquí son compartidos por TODOS los controladores

  before_action :set_locale

  private

  def set_locale
    I18n.locale = params[:locale] || I18n.default_locale
  end
end
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  # Hereda todo lo definido en ApplicationController
  def index
    @articles = Article.all
  end
end

El Application Record

De forma similar, todos los modelos heredan de ApplicationRecord:

# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  # Métodos compartidos por todos los modelos
end
# app/models/article.rb
class Article < ApplicationRecord
  # Hereda de ApplicationRecord → ActiveRecord::Base
  validates :title, presence: true
end

Resumen

En esta lección aprendiste:

  • Cómo funciona el patrón MVC y el rol de cada componente
  • El flujo completo de una petición HTTP en Rails (Router → Controller → Model → View)
  • La estructura del directorio app/ y sus subdirectorios
  • Cómo funciona el archivo config/routes.rb
  • Las convenciones de nombres de Rails y por qué importan
  • Cómo Zeitwerk auto-carga clases sin necesidad de require

En la siguiente lección profundizaremos en el sistema de rutas de Rails y cómo definir rutas RESTful, anidadas y con namespaces.

Ejercicio de práctica

Simulador de flujo MVC

Simula el flujo MVC de Rails con clases Ruby puras.

  1. MiniModel — clase con attr_accessor :attributes (hash). Método de clase create(attrs) que retorna instancia con atributos. Método valid? que retorna true si :name está presente.
  2. MiniControllerinitialize recibe la clase del modelo. Método index retorna todos los registros (array de clase). Método create(params) crea y retorna instancia si valid?, o nil.
  3. MiniView — Método render(data) que retorna "<ul>#{items}</ul>" donde items son <li> con el :name de cada registro.