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

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

Javier Valencia · · 4 min de lectura · 2076 visitas · Desarrollo
ruby rails tutorial rails-desde-cero activerecord

Segunda entrega de la serie 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

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:

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

# 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

# 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

article = Article.find(1)

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

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

Eliminar

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

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.

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:

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:

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.

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:

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:

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:

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
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:

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

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

# 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:

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:

class Article < ApplicationRecord
  scope :published, -> { where(published: true) }
  scope :recent, -> { order(created_at: :desc) }
  scope :by_author, ->(user) { where(user: user) }
end
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.

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.

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