# Rails desde cero (II): Introducción a ActiveRecord

*Segunda entrega de la serie **[Rails desde cero](/search?tag=rails-desde-cero)**. Tiempo de lectura estimado: 10 minutos.*

ActiveRecord es el corazón de cualquier aplicación Rails. Es el ORM (*Object-Relational Mapper*) que traduce las filas de tu base de datos en objetos Ruby, y tus objetos Ruby en SQL. Gracias a él puedes trabajar con los datos de forma expresiva y natural sin escribir casi una sola línea de SQL.

En este artículo veremos los fundamentos: cómo se definen los modelos, cómo funcionan las asociaciones, cómo se validan los datos y cómo se hacen consultas. En el siguiente artículo profundizaremos en los aspectos avanzados que marcan la diferencia en proyectos reales.

## El modelo y la tabla

![Rails desde cero (II): Introducción a ActiveRecord](fig-01.webp)

La convención básica de ActiveRecord es que cada modelo se corresponde con una tabla en la base de datos. El nombre del modelo es singular y en CamelCase; el de la tabla es plural y en snake_case. Rails hace la conversión automáticamente:

| Modelo | Tabla |
|---|---|
| `Article` | `articles` |
| `User` | `users` |
| `BlogPost` | `blog_posts` |
| `OrderItem` | `order_items` |

Todo modelo de Rails hereda de `ApplicationRecord`:

```ruby
class Article < ApplicationRecord
end
```

Con solo eso, Rails ya sabe cómo mapear el modelo a la tabla `articles`, qué columnas tiene (las lee del esquema) y cómo hacer operaciones CRUD sobre él. No hace falta declarar atributos ni tipos: ActiveRecord los infiere directamente de la base de datos.

## CRUD: las operaciones básicas

ActiveRecord te da métodos para las cuatro operaciones fundamentales sobre cualquier registro.

### Crear

```ruby
# new + save (dos pasos)
article = Article.new(title: "Mi primer artículo", body: "Hola mundo")
article.save  # => true si pasa las validaciones, false si no

# create (un solo paso, devuelve el objeto)
article = Article.create(title: "Mi primer artículo", body: "Hola mundo")

# create! (lanza excepción si falla)
article = Article.create!(title: "Mi primer artículo", body: "Hola mundo")
```

La diferencia entre `save` y `save!` (o entre `create` y `create!`) es importante: los métodos con `!` lanzan una excepción `ActiveRecord::RecordInvalid` si el registro no pasa las validaciones, mientras que los que no la llevan devuelven `false`. En aplicaciones reales, `create!` y `save!` son más seguros porque te obligan a manejar el error explícitamente.

### Leer

```ruby
# Por id (lanza ActiveRecord::RecordNotFound si no existe)
Article.find(1)

# Por condiciones (devuelve nil si no existe)
Article.find_by(title: "Mi primer artículo")

# Todos los registros
Article.all

# Con condiciones
Article.where(published: true)

# El primero y el último
Article.first
Article.last
```

### Actualizar

```ruby
article = Article.find(1)

# update_attributes
article.update(title: "Título actualizado")

# Asignación directa + save
article.title = "Título actualizado"
article.save
```

### Eliminar

```ruby
article = Article.find(1)
article.destroy  # ejecuta callbacks y validaciones
article.delete   # elimina directamente sin callbacks
```

La distinción entre `destroy` y `delete` es relevante: `destroy` ejecuta todos los callbacks definidos en el modelo (lo veremos más adelante), mientras que `delete` va directo a la base de datos. En general, usa `destroy` salvo que tengas una razón de rendimiento para lo contrario.

## Validaciones

![Rails desde cero (II): Introducción a ActiveRecord](fig-02.webp)

Las validaciones son reglas que un registro debe cumplir para poder guardarse en la base de datos. Se definen en el modelo y se ejecutan automáticamente antes de cualquier `save` o `create`.

```ruby
class Article < ApplicationRecord
  validates :title, presence: true, length: { minimum: 5, maximum: 100 }
  validates :body, presence: true
  validates :slug, uniqueness: true, format: { with: /\A[a-z0-9-]+\z/ }
  validates :status, inclusion: { in: %w[draft published archived] }
end
```

Los validadores más habituales son:

 - `presence`: el campo no puede estar vacío
 - `uniqueness`: el valor debe ser único en la tabla
 - `length`: controla la longitud de strings
 - `numericality`: verifica que el valor sea numérico
 - `format`: valida contra una expresión regular
 - `inclusion` / `exclusion`: el valor debe (o no debe) estar en una lista

Cuando un registro no pasa las validaciones, Rails lo indica en el objeto `errors`:

```ruby
article = Article.new(title: "Hi")
article.valid?  # => false

article.errors.full_messages
# => ["Title is too short (minimum is 5 characters)", "Body can't be blank"]

article.errors[:title]
# => ["is too short (minimum is 5 characters)"]
```

Puedes también escribir validaciones personalizadas:

```ruby
class Article < ApplicationRecord
  validate :title_cannot_contain_forbidden_words

  private

  def title_cannot_contain_forbidden_words
    forbidden = %w[spam clickbait]
    if forbidden.any? { |word| title.to_s.downcase.include?(word) }
      errors.add(:title, "contiene palabras no permitidas")
    end
  end
end
```

## Asociaciones

Las asociaciones definen las relaciones entre modelos. ActiveRecord soporta los tipos más comunes con una sintaxis muy declarativa.

### `belongs_to` y `has_many`

La asociación más frecuente: un artículo pertenece a un autor, y un autor tiene muchos artículos.

```ruby
class User < ApplicationRecord
  has_many :articles, dependent: :destroy
end

class Article < ApplicationRecord
  belongs_to :user
end
```

La tabla `articles` tendrá una columna `user_id` que es la clave foránea. Rails lo infiere del nombre de la asociación.

Con esto disponemos de métodos muy expresivos:

```ruby
user = User.find(1)
user.articles          # todos los artículos del usuario
user.articles.count    # cuántos tiene
user.articles.create!(title: "Nuevo", body: "Contenido")  # crear asociado

article = Article.find(1)
article.user           # el usuario al que pertenece
article.user.name      # acceder a atributos del usuario
```

### `has_one`

Cuando la relación es de uno a uno:

```ruby
class User < ApplicationRecord
  has_one :profile
end

class Profile < ApplicationRecord
  belongs_to :user
end
```

### `has_many :through`

Para relaciones muchos a muchos con una tabla intermedia:

```ruby
class Article < ApplicationRecord
  has_many :article_tags
  has_many :tags, through: :article_tags
end

class Tag < ApplicationRecord
  has_many :article_tags
  has_many :articles, through: :article_tags
end

class ArticleTag < ApplicationRecord
  belongs_to :article
  belongs_to :tag
end
```

```ruby
article.tags           # todos los tags del artículo
tag.articles           # todos los artículos con ese tag
article.tags << Tag.find_by(name: "rails")  # añadir un tag
```

### `has_and_belongs_to_many`

Una alternativa más sencilla para muchos a muchos sin necesidad de modelo intermedio, cuando la tabla de unión no necesita atributos propios:

```ruby
class Article < ApplicationRecord
  has_and_belongs_to_many :tags
end
```

En la práctica, `has_many :through` es preferible porque te da más control y flexibilidad si la relación evoluciona.

## Consultas básicas

![Rails desde cero (II): Introducción a ActiveRecord](fig-03.webp)

ActiveRecord tiene una API de consultas muy expresiva que genera SQL de forma automática.

```ruby
# Condiciones simples
Article.where(published: true)
Article.where(status: ["draft", "published"])

# Condiciones con string (cuidado con la inyección SQL)
Article.where("created_at > ?", 1.week.ago)
Article.where("title LIKE ?", "%rails%")

# Ordenar
Article.order(created_at: :desc)
Article.order(:title)

# Limitar resultados
Article.limit(10)
Article.offset(20).limit(10)  # paginación manual

# Seleccionar columnas específicas
Article.select(:id, :title, :published_at)

# Contar, sumar, promediar
Article.count
Article.where(published: true).count
Article.average(:reading_time)
```

Una de las grandes ventajas de ActiveRecord es que las consultas son **lazy**: no se ejecutan hasta que realmente necesitas los datos. Esto permite encadenar condiciones de forma natural:

```ruby
query = Article.where(published: true)
query = query.where("created_at > ?", 1.month.ago) if filter_recent
query = query.order(:title) if sort_by_title
query.limit(10)  # aquí se ejecuta el SQL
```

## Scopes: consultas reutilizables

Los scopes te permiten dar nombre a consultas frecuentes y reutilizarlas de forma encadenable:

```ruby
class Article < ApplicationRecord
  scope :published, -> { where(published: true) }
  scope :recent, -> { order(created_at: :desc) }
  scope :by_author, ->(user) { where(user: user) }
end
```

```ruby
Article.published                        # artículos publicados
Article.published.recent                 # publicados, más recientes primero
Article.published.recent.limit(5)        # los 5 más recientes publicados
Article.by_author(current_user).recent   # del usuario actual, ordenados
```

Los scopes son equivalentes a métodos de clase, pero tienen la ventaja de ser siempre encadenables y de devolver siempre un `ActiveRecord::Relation` (nunca `nil`), lo que los hace más seguros en cadenas de consultas.

## Callbacks

Los callbacks son métodos que se ejecutan automáticamente en momentos concretos del ciclo de vida de un registro: antes de guardarse, después de crearse, antes de eliminarse, etc.

```ruby
class Article < ApplicationRecord
  before_save :generate_slug
  after_create :notify_subscribers
  before_destroy :archive_comments

  private

  def generate_slug
    self.slug = title.parameterize
  end

  def notify_subscribers
    NotificationJob.perform_later(id)
  end
end
```

Los hooks disponibles son:

 - `before_validation` / `after_validation`
 - `before_save` / `after_save`
 - `before_create` / `after_create`
 - `before_update` / `after_update`
 - `before_destroy` / `after_destroy`
 - `after_commit` / `after_rollback`

Los callbacks son útiles, pero hay que usarlos con cabeza. Un modelo lleno de callbacks que disparan jobs, envían emails y modifican otros modelos se vuelve muy difícil de testear y razonar. Eso lo veremos en detalle en el artículo avanzado.

## Migraciones y el esquema

Ya vimos las migraciones en el artículo de fundamentos, pero vale la pena recordar la relación directa con los modelos: **el esquema manda**. ActiveRecord lee las columnas de la base de datos en tiempo de ejecución, así que si añades una columna en una migración, el modelo la tendrá disponible automáticamente sin tocar el código Ruby.

```ruby
# Añadir una columna a articles
bin/rails generate migration AddPublishedAtToArticles published_at:datetime

# La migración generada:
class AddPublishedAtToArticles < ActiveRecord::Migration[7.1]
  def change
    add_column :articles, :published_at, :datetime
  end
end

bin/rails db:migrate
```

A partir de ahí, `article.published_at` ya funciona sin más cambios.

## Resumen

Con lo que hemos visto en este artículo ya tienes las herramientas para hacer prácticamente cualquier cosa básica con ActiveRecord:

 - Crear, leer, actualizar y eliminar registros con una API expresiva en Ruby.
 - Definir validaciones que protegen la integridad de tus datos.
 - Modelar relaciones entre entidades con asociaciones.
 - Escribir consultas legibles y encadenables.
 - Reutilizar lógica de consulta con scopes.
 - Reaccionar a eventos del ciclo de vida con callbacks.

En el siguiente artículo subimos el nivel: consultas N+1 y cómo evitarlas, eager loading, consultas complejas, el uso correcto de callbacks, y patrones para mantener los modelos limpios cuando la lógica de negocio crece.

*¿Algo que no haya quedado claro? Déjalo en los comentarios.*
