1 ¿Qué es Active Record?
Active Record es parte de la M en MVC - el modelo - que es la capa del sistema responsable de representar datos y lógica de negocio. Active Record te ayuda a crear y usar objetos Ruby cuyos atributos requieren almacenamiento persistente en una base de datos.
NOTA: ¿Cuál es la diferencia entre Active Record y Active Model? Es posible modelar datos con objetos Ruby que no necesitan estar respaldados por una base de datos. Active Model se usa comúnmente para eso en Rails, haciendo que tanto Active Record como Active Model sean parte de la M en MVC, así como tus propios objetos Ruby simples.
El término "Active Record" también se refiere a un patrón de arquitectura de software. Active Record en Rails es una implementación de ese patrón. También es una descripción de algo llamado un sistema de Mapeo Objeto-Relacional. Las secciones a continuación explican estos términos.
1.1 El Patrón Active Record
El patrón Active Record es descrito por Martin Fowler en el libro Patterns of Enterprise Application Architecture como "un objeto que envuelve una fila en una tabla de base de datos, encapsula el acceso a la base de datos y añade lógica de dominio a esos datos." Los objetos Active Record llevan tanto datos como comportamiento. Las clases Active Record coinciden muy de cerca con la estructura de registros de la base de datos subyacente. De esta manera, los usuarios pueden leer y escribir fácilmente en la base de datos, como verás en los ejemplos a continuación.
1.2 Mapeo Objeto-Relacional
El Mapeo Objeto-Relacional, comúnmente referido como ORM, es una técnica que conecta los objetos ricos de un lenguaje de programación con tablas en un sistema de gestión de bases de datos relacionales (RDBMS). En el caso de una aplicación Rails, estos son objetos Ruby. Usando un ORM, los atributos de los objetos Ruby, así como la relación entre objetos, pueden ser fácilmente almacenados y recuperados de una base de datos sin escribir instrucciones SQL directamente. En general, los ORMs minimizan la cantidad de código de acceso a la base de datos que tienes que escribir.
NOTA: Un conocimiento básico de los sistemas de gestión de bases de datos relacionales (RDBMS) y del lenguaje de consulta estructurado (SQL) es útil para comprender completamente Active Record. Por favor, consulta este tutorial de SQL (o este tutorial de RDBMS) o estúdialos por otros medios si deseas aprender más.
1.3 Active Record como un Marco ORM
Active Record nos da la capacidad de hacer lo siguiente usando objetos Ruby:
- Representar modelos y sus datos.
- Representar asociaciones entre modelos.
- Representar jerarquías de herencia a través de modelos relacionados.
- Validar modelos antes de que se persistan en la base de datos.
- Realizar operaciones de base de datos de manera orientada a objetos.
2 Convención sobre Configuración en Active Record
Al escribir aplicaciones utilizando otros lenguajes de programación o marcos, puede ser necesario escribir mucho código de configuración. Esto es particularmente cierto para los marcos ORM en general. Sin embargo, si sigues las convenciones adoptadas por Rails, escribirás muy poco o nada de código de configuración al crear modelos de Active Record.
Rails adopta la idea de que si configuras tus aplicaciones de la misma manera la mayoría de las veces, entonces esa manera debería ser la predeterminada. La configuración explícita solo debería ser necesaria en aquellos casos donde no puedas seguir la convención.
Para aprovechar la convención sobre la configuración en Active Record, hay algunas convenciones de nomenclatura y esquema que seguir. Y en caso de que lo necesites, es posible anular las convenciones de nomenclatura.
2.1 Convenciones de Nomenclatura
Active Record utiliza esta convención de nomenclatura para mapear entre modelos (representados por objetos Ruby) y tablas de base de datos:
Rails pluralizará los nombres de clase de tu modelo para encontrar la tabla de base de datos respectiva. Por ejemplo, una clase llamada Book
se mapea a una tabla de base de datos llamada books
. Los mecanismos de pluralización de Rails son muy poderosos y capaces de pluralizar (y singularizar) tanto palabras regulares como irregulares en el idioma inglés. Esto usa el método pluralize de Active Support.
Para nombres de clase compuestos por dos o más palabras, el nombre de la clase del modelo seguirá las convenciones de Ruby de usar un nombre UpperCamelCase. El nombre de la tabla de base de datos, en ese caso, será un nombre en snake_case. Por ejemplo:
BookClub
es la clase del modelo, singular con la primera letra de cada palabra en mayúscula.book_clubs
es la tabla de base de datos correspondiente, plural con guiones bajos separando las palabras.
Aquí hay algunos ejemplos más de nombres de clases de modelo y nombres de tablas correspondientes:
Modelo / Clase | Tabla / Esquema |
---|---|
Article |
articles |
LineItem |
line_items |
Product |
products |
Person |
people |
2.2 Convenciones de Esquema
Active Record también utiliza convenciones para los nombres de columna en las tablas de base de datos, dependiendo del propósito de estas columnas.
- Claves primarias - Por defecto, Active Record usará una columna entera llamada
id
como la clave primaria de la tabla (bigint
para PostgreSQL, MySQL y MariaDB,integer
para SQLite). Al usar Migraciones de Active Record para crear tus tablas, esta columna se creará automáticamente. - Claves foráneas - Estos campos deben ser nombrados siguiendo el patrón
singularized_table_name_id
(por ejemplo,order_id
,line_item_id
). Estos son los campos que Active Record buscará cuando crees asociaciones entre tus modelos.
También hay algunos nombres de columna opcionales que añadirán características adicionales a las instancias de Active Record:
created_at
- Se establece automáticamente en la fecha y hora actuales cuando el registro se crea por primera vez.updated_at
- Se establece automáticamente en la fecha y hora actuales cada vez que el registro se crea o se actualiza.lock_version
- Añade bloqueo optimista a un modelo.type
- Especifica que el modelo usa Herencia de Tabla Única.(association_name)_type
- Almacena el tipo para asociaciones polimórficas.(table_name)_count
- Se usa para almacenar en caché el número de objetos pertenecientes en asociaciones. Por ejemplo, si losArticle
s tienen muchosComment
s, una columnacomments_count
en la tablaarticles
almacenará en caché el número de comentarios existentes para cada artículo.
NOTA: Aunque estos nombres de columna son opcionales, están reservados por Active Record. Evita las palabras clave reservadas al nombrar las columnas de tu tabla. Por ejemplo, type
es una palabra clave reservada utilizada para designar una tabla que utiliza Herencia de Tabla Única (STI). Si no estás usando STI, usa una palabra diferente para describir con precisión los datos que estás modelando.
3 Creando Modelos de Active Record
Al generar una aplicación Rails, se creará una clase abstracta ApplicationRecord
en app/models/application_record.rb
. La clase ApplicationRecord
hereda de ActiveRecord::Base
y es lo que convierte una clase Ruby regular en un modelo de Active Record.
ApplicationRecord
es la clase base para todos los modelos de Active Record en tu aplicación. Para crear un nuevo modelo, subclase la clase ApplicationRecord
y listo:
class Book < ApplicationRecord
end
Esto creará un modelo Book
, mapeado a una tabla books
en la base de datos, donde cada columna en la tabla se mapea a atributos de la clase Book
. Una instancia de Book
puede representar una fila en la tabla books
. La tabla books
con las columnas id
, title
y author
, puede ser creada usando una instrucción SQL como esta:
CREATE TABLE books (
id int(11) NOT NULL auto_increment,
title varchar(255),
author varchar(255),
PRIMARY KEY (id)
);
Sin embargo, esa no es la forma en que normalmente lo haces en Rails. Las tablas de bases de datos en Rails se crean típicamente usando Migraciones de Active Record y no SQL en bruto. Una migración para la tabla books
anterior puede generarse así:
$ bin/rails generate migration CreateBooks title:string author:string
y resulta en esto:
# Nota:
# La columna `id`, como clave primaria, se crea automáticamente por convención.
# Las columnas `created_at` y `updated_at` se añaden por `t.timestamps`.
# db/migrate/20240220143807_create_books.rb
class CreateBooks < ActiveRecord::Migration
def change
create_table :books do |t|
t.string :title
t.string :author
t.timestamps
end
end
end
Esa migración crea las columnas id
, title
, author
, created_at
y updated_at
. Cada fila de esta tabla puede ser representada por una instancia de la clase Book
con los mismos atributos: id
, title
, author
, created_at
y updated_at
. Puedes acceder a los atributos de un libro así:
irb> book = Book.new
=> #<Book:0x00007fbdf5e9a038 id: nil, title: nil, author: nil, created_at: nil, updated_at: nil>
irb> book.title = "The Hobbit"
=> "The Hobbit"
irb> book.title
=> "The Hobbit"
NOTA: Puedes generar la clase del modelo de Active Record así como una migración correspondiente con el comando bin/rails generate model Book title:string author:string
. Esto crea los archivos app/models/book.rb
, db/migrate/20240220143807_create_books.rb
, y un par de otros para propósitos de prueba.
3.1 Creando Modelos con Espacios de Nombres
Los modelos de Active Record se colocan bajo el directorio app/models
por defecto. Pero puede que quieras organizar tus modelos colocando modelos similares bajo su propia carpeta y espacio de nombres. Por ejemplo, order.rb
y review.rb
bajo app/models/book
con los nombres de clase Book::Order
y Book::Review
, respectivamente. Puedes crear modelos con espacios de nombres con Active Record.
En el caso de que el módulo Book
no exista ya, el comando generate
creará todo así:
$ bin/rails generate model Book::Order
invoke active_record
create db/migrate/20240306194227_create_book_orders.rb
create app/models/book/order.rb
create app/models/book.rb
invoke test_unit
create test/models/book/order_test.rb
create test/fixtures/book/orders.yml
Si el módulo Book
ya existe, se te pedirá que resuelvas el conflicto:
$ bin/rails generate model Book::Order
invoke active_record
create db/migrate/20240305140356_create_book_orders.rb
create app/models/book/order.rb
conflict app/models/book.rb
Overwrite /Users/bhumi/Code/rails_guides/app/models/book.rb? (enter "h" for help) [Ynaqdhm]
Una vez que la generación del modelo con espacio de nombres es exitosa, las clases Book
y Order
se ven así:
# app/models/book.rb
module Book
def self.table_name_prefix
"book_"
end
end
# app/models/book/order.rb
class Book::Order < ApplicationRecord
end
Establecer el table_name_prefix en Book
permitirá que la tabla de base de datos del modelo Order
se nombre book_orders
, en lugar de simplemente orders
.
La otra posibilidad es que ya tengas un modelo Book
que quieras mantener en app/models
. En ese caso, puedes elegir n
para no sobrescribir book.rb
durante el comando generate
.
Esto aún permitirá un nombre de tabla con espacio de nombres para la clase Book::Order
, sin necesidad de table_name_prefix
:
# app/models/book.rb
class Book < ApplicationRecord
# código existente
end
Book::Order.table_name
# => "book_orders"
4 Anulando las Convenciones de Nomenclatura
¿Qué pasa si necesitas seguir una convención de nomenclatura diferente o necesitas usar tu aplicación Rails con una base de datos heredada? No hay problema, puedes anular fácilmente las convenciones predeterminadas.
Dado que ApplicationRecord
hereda de ActiveRecord::Base
, los modelos de tu aplicación tendrán una serie de métodos útiles disponibles para ellos. Por ejemplo, puedes usar el método ActiveRecord::Base.table_name=
para personalizar el nombre de la tabla que se debe usar:
class Book < ApplicationRecord
self.table_name = "my_books"
end
Si lo haces, tendrás que definir manualmente el nombre de la clase que aloja los fixtures (my_books.yml
) usando el método set_fixture_class
en tu definición de prueba:
# test/models/book_test.rb
class BookTest < ActiveSupport::TestCase
set_fixture_class my_books: Book
fixtures :my_books
# ...
end
También es posible anular la columna que se debe usar como la clave primaria de la tabla usando el método ActiveRecord::Base.primary_key=
:
class Book < ApplicationRecord
self.primary_key = "book_id"
end
NOTA: Active Record no recomienda usar columnas no primarias llamadas id
. Usar una columna llamada id
que no sea una clave primaria de una sola columna complica el acceso al valor de la columna. La aplicación tendrá que usar el alias de atributo id_value
para acceder al valor de la columna id
que no es PK.
NOTA: Si intentas crear una columna llamada id
que no sea la clave primaria, Rails lanzará un error durante las migraciones como: no puedes redefinir la columna de clave primaria 'id' en 'my_books'.
Para definir una clave primaria personalizada, pasa { id: false } a create_table.
5 CRUD: Lectura y Escritura de Datos
CRUD es un acrónimo de los cuatro verbos que usamos para operar sobre datos: Crear, Realizar, Actualizar y Eliminar. Active Record crea automáticamente métodos para permitirte leer y manipular datos almacenados en las tablas de la base de datos de tu aplicación.
Active Record hace que sea sencillo realizar operaciones CRUD utilizando estos métodos de alto nivel que abstraen los detalles de acceso a la base de datos. Ten en cuenta que todos estos métodos convenientes resultan en una(s) instrucción(es) SQL que se ejecutan contra la base de datos subyacente.
Los ejemplos a continuación muestran algunos de los métodos CRUD así como las instrucciones SQL resultantes.
5.1 Crear
Los objetos Active Record pueden crearse a partir de un hash, un bloque, o tener sus atributos configurados manualmente después de la creación. El método new
devolverá un objeto nuevo, no persistido, mientras que create
guardará el objeto en la base de datos y lo devolverá.
Por ejemplo, dado un modelo Book
con atributos de title
y author
, la llamada al método create
creará un objeto y guardará un nuevo registro en la base de datos:
book = Book.create(title: "The Lord of the Rings", author: "J.R.R. Tolkien")
# Ten en cuenta que el `id` se asigna ya que este registro se compromete a la base de datos.
book.inspect
# => "#<Book id: 106, title: \"The Lord of the Rings\", author: \"J.R.R. Tolkien\", created_at: \"2024-03-04 19:15:58.033967000 +0000\", updated_at: \"2024-03-04 19:15:58.033967000 +0000\">"
Mientras que el método new
instanciará un objeto sin guardarlo en la base de datos:
book = Book.new
book.title = "The Hobbit"
book.author = "J.R.R. Tolkien"
# Ten en cuenta que el `id` no está configurado para este objeto.
book.inspect
# => "#<Book id: nil, title: \"The Hobbit\", author: \"J.R.R. Tolkien\", created_at: nil, updated_at: nil>"
# El `book` anterior aún no está guardado en la base de datos.
book.save
book.id # => 107
# Ahora el registro `book` está comprometido a la base de datos y tiene un `id`.
Finalmente, si se proporciona un bloque, tanto create
como new
cederán el nuevo objeto a ese bloque para su inicialización, mientras que solo create
persistirá el objeto resultante en la base de datos:
book = Book.new do |b|
b.title = "Metaprogramming Ruby 2"
b.author = "Paolo Perrotta"
end
book.save
La instrucción SQL resultante de ambos book.save
y Book.create
se ve algo así:
/* Ten en cuenta que `created_at` y `updated_at` se establecen automáticamente. */
INSERT INTO "books" ("title", "author", "created_at", "updated_at") VALUES (?, ?, ?, ?) RETURNING "id" [["title", "Metaprogramming Ruby 2"], ["author", "Paolo Perrotta"], ["created_at", "2024-02-22 20:01:18.469952"], ["updated_at", "2024-02-22 20:01:18.469952"]]
5.2 Leer
Active Record proporciona una rica API para acceder a datos dentro de una base de datos. Puedes consultar un solo registro o múltiples registros, filtrarlos por cualquier atributo, ordenarlos, agruparlos, seleccionar campos específicos y hacer cualquier cosa que puedas hacer con SQL.
# Devuelve una colección con todos los libros.
books = Book.all
# Devuelve un solo libro.
first_book = Book.first
last_book = Book.last
book = Book.take
Lo anterior resulta en el siguiente SQL:
-- Book.all
SELECT "books".* FROM "books"
-- Book.first
SELECT "books".* FROM "books" ORDER BY "books"."id" ASC LIMIT ? [["LIMIT", 1]]
-- Book.last
SELECT "books".* FROM "books" ORDER BY "books"."id" DESC LIMIT ? [["LIMIT", 1]]
-- Book.take
SELECT "books".* FROM "books" LIMIT ? [["LIMIT", 1]]
También podemos encontrar libros específicos con find_by
y where
. Mientras que find_by
devuelve un solo registro, where
devuelve una lista de registros:
# Devuelve el primer libro con un título dado o `nil` si no se encuentra ningún libro.
book = Book.find_by(title: "Metaprogramming Ruby 2")
# Alternativa a Book.find_by(id: 42). Lanzará una excepción si no se encuentra ningún libro coincidente.
book = Book.find(42)
Lo anterior resulta en este SQL:
SELECT "books".* FROM "books" WHERE "books"."author" = ? LIMIT ? [["author", "J.R.R. Tolkien"], ["LIMIT", 1]]
SELECT "books".* FROM "books" WHERE "books"."id" = ? LIMIT ? [["id", 42], ["LIMIT", 1]]
# Encuentra todos los libros con un autor dado, ordena por created_at en orden cronológico inverso.
Book.where(author: "Douglas Adams").order(created_at: :desc)
resultando en este SQL:
SELECT "books".* FROM "books" WHERE "books"."author" = ? ORDER BY "books"."created_at" DESC [["author", "Douglas Adams"]]
Hay muchos más métodos de Active Record para leer y consultar registros. Puedes aprender más sobre ellos en la guía de Consultas de Active Record.
5.3 Actualizar
Una vez que un objeto de Active Record ha sido recuperado, sus atributos pueden ser modificados y puede ser guardado en la base de datos.
book = Book.find_by(title: "The Lord of the Rings")
book.title = "The Lord of the Rings: The Fellowship of the Ring"
book.save
Una abreviatura para esto es usar un hash que mapea nombres de atributos al valor deseado, así:
book = Book.find_by(title: "The Lord of the Rings")
book.update(title: "The Lord of the Rings: The Fellowship of the Ring")
el update
resulta en el siguiente SQL:
/* Ten en cuenta que `updated_at` se establece automáticamente. */
UPDATE "books" SET "title" = ?, "updated_at" = ? WHERE "books"."id" = ? [["title", "The Lord of the Rings: The Fellowship of the Ring"], ["updated_at", "2024-02-22 20:51:13.487064"], ["id", 104]]
Esto es útil cuando se actualizan varios atributos a la vez. Similar a create
, usar update
comprometerá los registros actualizados a la base de datos.
Si deseas actualizar varios registros en bloque sin callbacks o validaciones, puedes actualizar la base de datos directamente usando update_all
:
Book.update_all(status: "ya poseo")
5.4 Eliminar
De igual manera, una vez recuperado, un objeto de Active Record puede ser destruido, lo que lo elimina de la base de datos.
book = Book.find_by(title: "The Lord of the Rings")
book.destroy
El destroy
resulta en este SQL:
DELETE FROM "books" WHERE "books"."id" = ? [["id", 104]]
Si deseas eliminar varios registros en bloque, puedes usar el método destroy_by
o destroy_all
:
# Encuentra y elimina todos los libros de Douglas Adams.
Book.destroy_by(author: "Douglas Adams")
# Elimina todos los libros.
Book.destroy_all
6 Validaciones
Active Record te permite validar el estado de un modelo antes de que se escriba en la base de datos. Hay varios métodos que permiten diferentes tipos de validaciones. Por ejemplo, validar que un valor de atributo no esté vacío, sea único, no esté ya en la base de datos, siga un formato específico y muchos más.
Métodos como save
, create
y update
validan un modelo antes de persistirlo en la base de datos. Cuando un modelo es inválido, estos métodos devuelven false
y no se realizan operaciones en la base de datos. Todos estos métodos tienen una contraparte con signo de exclamación (es decir, save!
, create!
y update!
), que son más estrictos en que lanzan una excepción ActiveRecord::RecordInvalid
cuando la validación falla. Un ejemplo rápido para ilustrar:
class User < ApplicationRecord
validates :name, presence: true
end
irb> user = User.new
irb> user.save
=> false
irb> user.save!
ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
Puedes aprender más sobre validaciones en la guía de Validaciones de Active Record.
7 Callbacks
Los callbacks de Active Record te permiten adjuntar código a ciertos eventos en el ciclo de vida de tus modelos. Esto te permite añadir comportamiento a tus modelos ejecutando código cuando ocurren esos eventos, como cuando creas un nuevo registro, lo actualizas, lo destruyes, etc.
class User < ApplicationRecord
after_create :log_new_user
private
def log_new_user
puts "Se registró un nuevo usuario"
end
end
irb> @user = User.create
Se registró un nuevo usuario
Puedes aprender más sobre callbacks en la guía de Callbacks de Active Record.
8 Migraciones
Rails proporciona una forma conveniente de gestionar cambios en un esquema de base de datos a través de migraciones. Las migraciones se escriben en un lenguaje específico de dominio y se almacenan en archivos que se ejecutan contra cualquier base de datos que Active Record soporte.
Aquí hay una migración que crea una nueva tabla llamada publications
:
class CreatePublications < ActiveRecord::Migration[7.2]
def change
create_table :publications do |t|
t.string :title
t.text :description
t.references :publication_type
t.references :publisher, polymorphic: true
t.boolean :single_issue
t.timestamps
end
end
end
Ten en cuenta que el código anterior es independiente de la base de datos: se ejecutará en MySQL, MariaDB, PostgreSQL, SQLite y otros.
Rails lleva un registro de qué migraciones han sido comprometidas a la base de datos y las almacena en una tabla vecina en esa misma base de datos llamada schema_migrations
.
Para ejecutar la migración y crear la tabla, ejecutarías bin/rails db:migrate
, y para revertirla y eliminar la tabla, bin/rails db:rollback
.
Puedes aprender más sobre migraciones en la guía de Migraciones de Active Record.
9 Asociaciones
Las asociaciones de Active Record te permiten definir relaciones entre modelos. Las asociaciones se pueden usar para describir relaciones uno a uno, uno a muchos y muchos a muchos. Por ejemplo, una relación como "Autor tiene muchos Libros" puede definirse de la siguiente manera:
class Author < ApplicationRecord
has_many :books
end
La clase Author
ahora tiene métodos para añadir y eliminar libros a un autor, y mucho más.
Puedes aprender más sobre asociaciones en la guía de Asociaciones de Active Record.
Comentarios
Se te anima a ayudar a mejorar la calidad de esta guía.
Por favor contribuye si ves algún error tipográfico o errores fácticos. Para comenzar, puedes leer nuestra sección de contribuciones a la documentación.
También puedes encontrar contenido incompleto o cosas que no están actualizadas. Por favor agrega cualquier documentación faltante para main. Asegúrate de revisar Guías Edge primero para verificar si los problemas ya están resueltos o no en la rama principal. Revisa las Guías de Ruby on Rails para estilo y convenciones.
Si por alguna razón detectas algo que corregir pero no puedes hacerlo tú mismo, por favor abre un issue.
Y por último, pero no menos importante, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.