1 Visión General de las Migraciones
Las migraciones son una forma conveniente de evolucionar tu esquema de base de datos con el tiempo de manera reproducible. Usan un DSL en Ruby para que no tengas que escribir SQL a mano, permitiendo que tu esquema y cambios sean independientes de la base de datos. Recomendamos que leas las guías de Conceptos Básicos de Active Record y Asociaciones de Active Record para aprender más sobre algunos de los conceptos mencionados aquí.
Puedes pensar en cada migración como una nueva 'versión' de la base de datos. Un esquema comienza sin nada en él, y cada migración lo modifica para agregar o eliminar tablas, columnas o índices. Active Record sabe cómo actualizar tu esquema a lo largo de esta línea de tiempo, llevándolo desde cualquier punto en la historia hasta la última versión. Lee más sobre cómo Rails sabe qué migración en la línea de tiempo ejecutar.
Active Record actualiza tu archivo db/schema.rb
para que coincida con la estructura actualizada de tu base de datos. Aquí tienes un ejemplo de una migración:
# db/migrate/20240502100843_create_products.rb
class CreateProducts < ActiveRecord::Migration[7.2]
def change
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
Esta migración agrega una tabla llamada products
con una columna de tipo string llamada name
y una columna de tipo texto llamada description
. También se agregará implícitamente una columna de clave primaria llamada id
, ya que es la clave primaria predeterminada para todos los modelos de Active Record. La macro timestamps
agrega dos columnas, created_at
y updated_at
. Estas columnas especiales son gestionadas automáticamente por Active Record si existen.
# db/schema.rb
ActiveRecord::Schema[7.2].define(version: 2024_05_02_100843) do
# Estas son extensiones que deben estar habilitadas para soportar esta base de datos
enable_extension "plpgsql"
create_table "products", force: :cascade do |t|
t.string "name"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
Definimos el cambio que queremos que ocurra avanzando en el tiempo. Antes de que se ejecute esta migración, no habrá tabla. Después de que se ejecute, la tabla existirá. Active Record también sabe cómo revertir esta migración; si retrocedemos en esta migración, eliminará la tabla. Lee más sobre cómo retroceder migraciones en la sección de Retroceso.
Después de definir el cambio que queremos que ocurra avanzando en el tiempo, es esencial considerar la reversibilidad de la migración. Mientras Active Record puede gestionar la progresión hacia adelante de la migración, asegurando la creación de la tabla, el concepto de reversibilidad se vuelve crucial. Con migraciones reversibles, no solo la migración crea la tabla cuando se aplica, sino que también permite una funcionalidad de retroceso suave. En caso de revertir la migración anterior, Active Record maneja inteligentemente la eliminación de la tabla, manteniendo la consistencia de la base de datos a lo largo del proceso. Consulta la sección de Reversión de Migraciones para más detalles.
2 Generación de Archivos de Migración
2.1 Creación de una Migración Independiente
Las migraciones se almacenan como archivos en el directorio db/migrate
, uno para cada clase de migración.
El nombre del archivo tiene la forma YYYYMMDDHHMMSS_create_products.rb
, contiene una marca de tiempo UTC que identifica la migración seguida de un guion bajo seguido del nombre de la migración. El nombre de la clase de la migración (versión CamelCased) debe coincidir con la última parte del nombre del archivo.
Por ejemplo, 20240502100843_create_products.rb
debe definir la clase CreateProducts
y 20240502101659_add_details_to_products.rb
debe definir la clase AddDetailsToProducts
. Rails usa esta marca de tiempo para determinar qué migración debe ejecutarse y en qué orden, así que si estás copiando una migración de otra aplicación o generando un archivo tú mismo, ten en cuenta su posición en el orden. Puedes leer más sobre cómo se usan las marcas de tiempo en la sección de Control de Versión de Migraciones de Rails.
Al generar una migración, Active Record automáticamente antepone la marca de tiempo actual al nombre del archivo de la migración. Por ejemplo, ejecutar el siguiente comando creará un archivo de migración vacío donde el nombre del archivo está compuesto por una marca de tiempo antepuesta al nombre subrayado de la migración.
$ bin/rails generate migration AddPartNumberToProducts
# db/migrate/20240502101659_add_part_number_to_products.rb
class AddPartNumberToProducts < ActiveRecord::Migration[7.2]
def change
end
end
El generador puede hacer mucho más que anteponer una marca de tiempo al nombre del archivo. Basado en convenciones de nombres y argumentos adicionales (opcionales), también puede comenzar a esbozar la migración.
Las siguientes secciones cubrirán las diversas formas en que puedes crear migraciones basadas en convenciones y argumentos adicionales.
2.2 Creación de una Nueva Tabla
Cuando deseas crear una nueva tabla en tu base de datos, puedes usar una migración con el formato "CreateXXX" seguido de una lista de nombres de columna y tipos. Esto generará un archivo de migración que configura la tabla con las columnas especificadas.
$ bin/rails generate migration CreateProducts name:string part_number:string
genera
class CreateProducts < ActiveRecord::Migration[7.2]
def change
create_table :products do |t|
t.string :name
t.string :part_number
t.timestamps
end
end
end
El archivo generado con su contenido es solo un punto de partida, y puedes agregar o eliminar de él según lo consideres necesario editando el archivo db/migrate/YYYYMMDDHHMMSS_create_products.rb
.
2.3 Agregar Columnas
Cuando deseas agregar una nueva columna a una tabla existente en tu base de datos, puedes usar una migración con el formato "AddColumnToTable" seguido de una lista de nombres de columna y tipos. Esto generará un archivo de migración que contiene las declaraciones apropiadas de add_column
.
$ bin/rails generate migration AddPartNumberToProducts part_number:string
Esto generará la siguiente migración:
class AddPartNumberToProducts < ActiveRecord::Migration[7.2]
def change
add_column :products, :part_number, :string
end
end
Si deseas agregar un índice en la nueva columna, también puedes hacerlo.
$ bin/rails generate migration AddPartNumberToProducts part_number:string:index
Esto generará las declaraciones apropiadas de add_column
y add_index
:
class AddPartNumberToProducts < ActiveRecord::Migration[7.2]
def change
add_column :products, :part_number, :string
add_index :products, :part_number
end
end
No estás limitado a una columna generada mágicamente. Por ejemplo:
$ bin/rails generate migration AddDetailsToProducts part_number:string price:decimal
Esto generará una migración de esquema que agrega dos columnas adicionales a la tabla products
.
class AddDetailsToProducts < ActiveRecord::Migration[7.2]
def change
add_column :products, :part_number, :string
add_column :products, :price, :decimal
end
end
2.4 Eliminación de Columnas
De manera similar, si el nombre de la migración es de la forma "RemoveColumnFromTable" y es seguido por una lista de nombres de columna y tipos, se creará una migración que contiene las declaraciones apropiadas de remove_column
.
$ bin/rails generate migration RemovePartNumberFromProducts part_number:string
Esto generará las declaraciones apropiadas de remove_column
:
class RemovePartNumberFromProducts < ActiveRecord::Migration[7.2]
def change
remove_column :products, :part_number, :string
end
end
2.5 Creación de Asociaciones
Las asociaciones de Active Record se utilizan para definir relaciones entre diferentes modelos en tu aplicación, permitiéndoles interactuar entre sí a través de sus relaciones y facilitando el trabajo con datos relacionados. Para aprender más sobre asociaciones, puedes consultar la guía de Conceptos Básicos de Asociaciones.
Un caso de uso común para las asociaciones es crear referencias de clave externa entre tablas. El generador acepta tipos de columna como references
para facilitar este proceso. References son una forma abreviada de crear columnas, índices, claves externas o incluso columnas de asociación polimórfica.
Por ejemplo,
$ bin/rails generate migration AddUserRefToProducts user:references
genera la siguiente llamada a add_reference
:
class AddUserRefToProducts < ActiveRecord::Migration[7.2]
def change
add_reference :products, :user, null: false, foreign_key: true
end
end
La migración anterior crea una clave externa llamada user_id
en la tabla products
, donde user_id
es una referencia a la columna id
en la tabla users
. También crea un índice para la columna user_id
. El esquema se ve de la siguiente manera:
create_table "products", force: :cascade do |t|
t.bigint "user_id", null: false
t.index ["user_id"], name: "index_products_on_user_id"
end
belongs_to
es un alias de references
, por lo que lo anterior podría escribirse alternativamente como:
$ bin/rails generate migration AddUserRefToProducts user:belongs_to
generando una migración y esquema que es el mismo que el anterior.
También hay un generador que producirá tablas de unión si JoinTable
es parte del nombre:
$ bin/rails generate migration CreateJoinTableUserProduct user product
producirá la siguiente migración:
class CreateJoinTableUserProduct < ActiveRecord::Migration[7.2]
def change
create_join_table :users, :products do |t|
# t.index [:user_id, :product_id]
# t.index [:product_id, :user_id]
end
end
end
2.6 Otros Generadores que Crean Migraciones
Además del generador migration
, los generadores model
, resource
y
scaffold
crearán migraciones apropiadas para agregar un nuevo modelo. Esta migración ya contendrá instrucciones para crear la tabla relevante. Si le dices a Rails qué columnas deseas, entonces también se crearán declaraciones para agregar estas columnas. Por ejemplo, al ejecutar:
$ bin/rails generate model Product name:string description:text
Esto creará una migración que se ve así:
class CreateProducts < ActiveRecord::Migration[7.2]
def change
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
end
Puedes agregar tantos pares de nombre de columna/tipo como desees.
2.7 Pasando Modificadores
Al generar migraciones, puedes pasar modificadores de tipo comúnmente usados modificadores de columna directamente en la línea de comandos. Estos modificadores, encerrados entre llaves y siguiendo el tipo de campo, te permiten adaptar las características de tus columnas de base de datos sin necesidad de editar manualmente el archivo de migración después.
Por ejemplo, al ejecutar:
$ bin/rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplier:references{polymorphic}
producirá una migración que se ve así
class AddDetailsToProducts < ActiveRecord::Migration[7.2]
def change
add_column :products, :price, :decimal, precision: 5, scale: 2
add_reference :products, :supplier, polymorphic: true
end
end
CONSEJO: Para obtener más ayuda con los generadores, ejecuta bin/rails generate --help
. Alternativamente, también puedes ejecutar bin/rails generate model --help
o bin/rails generate migration --help
para obtener ayuda con generadores específicos.
3 Actualización de Migraciones
Una vez que hayas creado tu archivo de migración usando uno de los generadores de la sección anterior, puedes actualizar el archivo de migración generado en la carpeta db/migrate
para definir más cambios que deseas hacer a tu esquema de base de datos.
3.1 Creación de una Tabla
El método create_table
es uno de los tipos de migración más fundamentales, pero la mayoría de las veces, será generado para ti al usar un generador de modelo, recurso o scaffold. Un uso típico sería
create_table :products do |t|
t.string :name
end
Este método crea una tabla products
con una columna llamada name
.
3.1.1 Asociaciones
Si estás creando una tabla para un modelo que tiene una asociación, puedes usar el tipo :references
para crear el tipo de columna apropiado. Por ejemplo:
create_table :products do |t|
t.references :category
end
Esto creará una columna category_id
. Alternativamente, puedes usar belongs_to
como un alias para references
:
create_table :products do |t|
t.belongs_to :category
end
También puedes especificar el tipo de columna y la creación de índices usando la opción :polymorphic
:
create_table :taggings do |t|
t.references :taggable, polymorphic: true
end
Esto creará las columnas taggable_id
, taggable_type
y los índices apropiados.
3.1.2 Claves Primarias
Por defecto, create_table
creará implícitamente una clave primaria llamada id
para ti. Puedes cambiar el nombre de la columna con la opción :primary_key
, como se muestra a continuación:
class CreateUsers < ActiveRecord::Migration[7.2]
def change
create_table :users, primary_key: "user_id" do |t|
t.string :username
t.string :email
t.timestamps
end
end
end
Esto generará el siguiente esquema:
create_table "users", primary_key: "user_id", force: :cascade do |t|
t.string "username"
t.string "email"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
También puedes pasar un array a :primary_key
para una clave primaria compuesta. Lee más sobre claves primarias compuestas.
class CreateUsers < ActiveRecord::Migration[7.2]
def change
create_table :users, primary_key: [:id, :name] do |t|
t.string :name
t.string :email
t.timestamps
end
end
end
Si no quieres una clave primaria en absoluto, puedes pasar la opción id: false
.
class CreateUsers < ActiveRecord::Migration[7.2]
def change
create_table :users, id: false do |t|
t.string :username
t.string :email
t.timestamps
end
end
end
3.1.3 Opciones de Base de Datos
Si necesitas pasar opciones específicas de la base de datos, puedes colocar un fragmento de SQL en la opción :options
. Por ejemplo:
create_table :products, options: "ENGINE=BLACKHOLE" do |t|
t.string :name, null: false
end
Esto añadirá ENGINE=BLACKHOLE
a la declaración SQL utilizada para crear la tabla.
Un índice puede ser creado en las columnas creadas dentro del bloque create_table
pasando index: true
o un hash de opciones a la opción :index
:
create_table :users do |t|
t.string :name, index: true
t.string :email, index: { unique: true, name: 'unique_emails' }
end
3.1.4 Comentarios
Puedes pasar la opción :comment
con cualquier descripción para la tabla que se almacenará en la base de datos misma y puede ser vista con herramientas de administración de bases de datos, como MySQL Workbench o PgAdmin III. Los comentarios pueden ayudar a los miembros del equipo a entender mejor el modelo de datos y a generar documentación en aplicaciones con bases de datos grandes. Actualmente, solo los adaptadores MySQL y PostgreSQL soportan comentarios.
class AddDetailsToProducts < ActiveRecord::Migration[7.2]
def change
add_column :products, :price, :decimal, precision: 8, scale: 2, comment: "El precio del producto en USD"
add_column :products, :stock_quantity, :integer, comment: "La cantidad actual de stock del producto"
end
end
3.2 Creación de una Tabla de Unión
El método de migración create_join_table
crea una tabla de unión HABTM (has and belongs to many). Un uso típico sería:
create_join_table :products, :categories
Esta migración creará una tabla categories_products
con dos columnas llamadas category_id
y product_id
.
Estas columnas tienen la opción :null
establecida en false
por defecto, lo que significa que debes proporcionar un valor para poder guardar un registro en esta tabla. Esto puede ser anulado especificando la opción :column_options
:
create_join_table :products, :categories, column_options: { null: true }
Por defecto, el nombre de la tabla de unión proviene de la unión de los dos primeros argumentos proporcionados a create_join_table
, en orden alfabético. En este caso, la tabla se llamaría categories_products
.
Para personalizar el nombre de la tabla, proporciona una opción :table_name
:
create_join_table :products, :categories, table_name: :categorization
Esto crea una tabla de unión con el nombre categorization
.
Además, create_join_table
acepta un bloque, que puedes usar para agregar índices (que no se crean por defecto) o cualquier columna adicional que elijas.
create_join_table :products, :categories do |t|
t.index :product_id
t.index :category_id
end
3.3 Cambiando Tablas
Si deseas cambiar una tabla existente en su lugar, hay change_table
.
Se utiliza de manera similar a create_table
pero el objeto generado dentro del bloque tiene acceso a una serie de funciones especiales, por ejemplo:
change_table :products do |t|
t.remove :description, :name
t.string :part_number
t.index :part_number
t.rename :upccode, :upc_code
end
Esta migración eliminará las columnas description
y name
, creará una nueva columna de tipo string llamada part_number
y añadirá un índice en ella. Finalmente, cambia el nombre de la columna upccode
a upc_code
.
3.4 Cambiando Columnas
Similar a los métodos remove_column
y add_column
que cubrimos anteriormente, Rails también proporciona el método de migración change_column
.
change_column :products, :part_number, :text
Esto cambia la columna part_number
en la tabla de productos a un campo de tipo :text
.
NOTA: El comando change_column
es irreversible. Para asegurar que tu migración se pueda revertir de manera segura, necesitarás proporcionar tu propia migración reversible
. Consulta la sección de Migraciones Reversibles para más detalles.
Además de change_column
, los métodos change_column_null
y change_column_default
se utilizan para cambiar una restricción de nulidad y valores predeterminados de una columna.
change_column_default :products, :approved, from: true, to: false
Esto cambia el valor predeterminado del campo :approved
de true a false. Este cambio solo se aplicará a registros futuros, cualquier registro existente no cambiará. Usa change_column_default
para cambiar una restricción de nulidad.
change_column_null :products, :name, false
Esto establece el campo :name
en productos como una columna NOT NULL
. Este cambio se aplica a registros existentes también, por lo que necesitas asegurarte de que todos los registros existentes tengan un :name
que no sea nulo.
Establecer la restricción de nulidad en true
implica que la columna aceptará un valor nulo, de lo contrario se aplica la restricción NOT NULL
y se debe pasar un valor para persistir el registro en la base de datos.
NOTA: También podrías escribir la migración change_column_default
anterior como change_column_default :products, :approved, false
, pero a diferencia del ejemplo anterior, esto haría que tu migración sea irreversible.
3.5 Modificadores de Columna
Los modificadores de columna pueden aplicarse al crear o cambiar una columna:
comment
Agrega un comentario para la columna.collation
Especifica la colación para una columnastring
otext
.default
Permite establecer un valor predeterminado en la columna. Ten en cuenta que si estás usando un valor dinámico (como una fecha), el valor predeterminado solo se calculará la primera vez (es decir, en la fecha en que se aplica la migración). Usanil
paraNULL
.limit
Establece el número máximo de caracteres para una columnastring
y el número máximo de bytes para columnastext/binary/integer
.null
Permite o no permite valoresNULL
en la columna.precision
Especifica la precisión para columnasdecimal/numeric/datetime/time
.scale
Especifica la escala para las columnasdecimal
ynumeric
, representando el número de dígitos después del punto decimal.
NOTA: Para add_column
o change_column
no hay opción para agregar índices. Necesitan ser añadidos por separado usando add_index
.
Algunos adaptadores pueden soportar opciones adicionales; consulta la documentación específica del adaptador para más información.
NOTA: null
y default
no pueden especificarse a través de la línea de comandos al generar migraciones.
3.6 Referencias
El método add_reference
permite la creación de una columna apropiadamente nombrada que actúa como la conexión entre una o más asociaciones.
add_reference :users, :role
Esta migración creará una columna de clave externa llamada role_id
en la tabla de usuarios. role_id
es una referencia a la columna id
en la tabla roles
. Además, crea un índice para la columna role_id
, a menos que se le indique explícitamente que no lo haga con la opción index: false
.
Consulta también la guía de Asociaciones de Active Record para aprender más.
El método add_belongs_to
es un alias de add_reference
.
add_belongs_to :taggings, :taggable, polymorphic: true
La opción polimórfica creará dos columnas en la tabla de etiquetado que pueden usarse para asociaciones polimórficas: taggable_type
y taggable_id
.
Consulta esta guía para aprender más sobre asociaciones polimórficas.
Una clave externa puede ser creada con la opción foreign_key
.
add_reference :users, :role, foreign_key: true
Para más opciones de add_reference
, visita la documentación de la API.
Las referencias también pueden ser eliminadas:
remove_reference :products, :user, foreign_key: true, index: false
3.7 Claves Externas
Aunque no es obligatorio, podrías querer agregar restricciones de clave externa para garantizar la integridad referencial.
add_foreign_key :articles, :authors
La llamada a add_foreign_key
añade una nueva restricción a la tabla articles
. La restricción garantiza que exista una fila en la tabla authors
donde la columna id
coincida con el articles.author_id
para asegurar que todos los revisores listados en la tabla de artículos sean autores válidos listados en la tabla de autores.
NOTA: Al usar references
en una migración, estás creando una nueva columna en la tabla y tendrás la opción de agregar una clave externa usando foreign_key: true
a esa columna. Sin embargo, si deseas agregar una clave externa a una columna existente, puedes usar add_foreign_key
.
Si el nombre de la columna de la tabla a la que estamos agregando la clave externa no se puede derivar de la tabla con la clave primaria referenciada, entonces puedes usar la opción :column
para especificar el nombre de la columna. Además, puedes usar la opción :primary_key
si la clave primaria referenciada no es :id
.
Por ejemplo, para agregar una clave externa en articles.reviewer
que hace referencia a authors.email
:
add_foreign_key :articles, :authors, column: :reviewer, primary_key: :email
Esto añadirá una restricción a la tabla articles
que garantiza que exista una fila en la tabla authors
donde la columna email
coincida con el campo articles.reviewer
.
Varias otras opciones como name
, on_delete
, if_not_exists
, validate
y deferrable
son soportadas por add_foreign_key
.
Las claves externas también pueden ser eliminadas usando remove_foreign_key
:
# dejar que Active Record determine el nombre de la columna
remove_foreign_key :accounts, :branches
# eliminar clave externa para una columna específica
remove_foreign_key :accounts, column: :owner_id
NOTA: Active Record solo soporta claves externas de una sola columna. execute
y structure.sql
son necesarios para usar claves externas compuestas. Consulta Volcado de Esquema y Tú.
3.8 Claves Primarias Compuestas
A veces, el valor de una sola columna no es suficiente para identificar de manera única cada fila de una tabla, pero una combinación de dos o más columnas sí lo identifica de manera única. Esto puede ser el caso cuando se utiliza un esquema de base de datos heredado sin una sola columna id
como clave primaria, o al modificar esquemas para particionamiento o multitenencia.
Puedes crear una tabla con una clave primaria compuesta pasando la opción :primary_key
a create_table
con un valor de array:
class CreateProducts < ActiveRecord::Migration[7.2]
def change
create_table :products, primary_key: [:customer_id, :product_sku] do |t|
t.integer :customer_id
t.string :product_sku
t.text :description
end
end
end
Las tablas con claves primarias compuestas requieren pasar valores de array en lugar de IDs de enteros a muchos métodos. Consulta también la guía de Claves Primarias Compuestas de Active Record para aprender más.
3.9 Ejecutar SQL
Si los ayudantes proporcionados por Active Record no son suficientes, puedes usar el método execute
para ejecutar comandos SQL. Por ejemplo,
class UpdateProductPrices < ActiveRecord::Migration[7.2]
def up
execute "UPDATE products SET price = 'free'"
end
def down
execute "UPDATE products SET price = 'original_price' WHERE price = 'free';"
end
end
En este ejemplo, estamos actualizando la columna price
de la tabla de productos a 'free' para todos los registros.
ADVERTENCIA: Modificar datos directamente en migraciones debe abordarse con precaución. Considera si este es el mejor enfoque para tu caso de uso, y ten en cuenta posibles desventajas como la mayor complejidad y el mantenimiento, riesgos para la integridad de los datos y la portabilidad de la base de datos. Consulta la documentación de Migraciones de Datos para más detalles.
Para más detalles y ejemplos de métodos individuales, consulta la documentación de la API.
En particular, la documentación para
ActiveRecord::ConnectionAdapters::SchemaStatements
que proporciona los métodos disponibles en los métodos change
, up
y down
.
Para métodos disponibles en relación con el objeto generado por create_table
, consulta
ActiveRecord::ConnectionAdapters::TableDefinition
.
Y para el objeto generado por change_table
, consulta
ActiveRecord::ConnectionAdapters::Table
.
3.10 Usando el Método change
El método change
es la forma principal de escribir migraciones. Funciona para la mayoría de los casos en los que Active Record sabe cómo revertir automáticamente las acciones de una migración. A continuación se muestran algunas de las acciones que change
admite:
add_check_constraint
add_column
add_foreign_key
add_index
add_reference
add_timestamps
change_column_comment
(debes proporcionar opciones:from
y:to
)change_column_default
(debes proporcionar opciones:from
y:to
)change_column_null
change_table_comment
(debes proporcionar opciones:from
y:to
)create_join_table
create_table
disable_extension
drop_join_table
drop_table
(debes proporcionar opciones de creación de tabla y bloque)enable_extension
remove_check_constraint
(debes proporcionar la expresión de restricción original)remove_column
(debes proporcionar el tipo original y opciones de columna)remove_columns
(debes proporcionar el tipo original y opciones de columna)remove_foreign_key
(debes proporcionar otra tabla y opciones originales)remove_index
(debes proporcionar columnas y opciones originales)remove_reference
(debes proporcionar opciones originales)remove_timestamps
(debes proporcionar opciones originales)rename_column
rename_index
rename_table
change_table
también es reversible, siempre que el bloque solo llame a operaciones reversibles como las enumeradas anteriormente.
Si necesitas usar cualquier otro método, deberías usar reversible
o escribir los métodos up
y down
en lugar de usar el método change
.
3.11 Usando reversible
Si deseas que una migración haga algo que Active Record no sabe cómo revertir, entonces puedes usar reversible
para especificar qué hacer al ejecutar una migración y qué más hacer al revertirla.
class ChangeProductsPrice < ActiveRecord::Migration[7.2]
def change
reversible do |direction|
change_table :products do |t|
direction.up { t.change :price, :string }
direction.down { t.change :price, :integer }
end
end
end
end
Esta migración cambiará el tipo de la columna price
a una cadena, o de nuevo a un entero cuando se revierta la migración. Observa el bloque que se pasa a direction.up
y direction.down
respectivamente.
Alternativamente, puedes usar up
y down
en lugar de change
:
class ChangeProductsPrice < ActiveRecord::Migration[7.2]
def up
change_table :products do |t|
t.change :price, :string
end
end
def down
change_table :products do |t|
t.change :price, :integer
end
end
end
Además, reversible
es útil al ejecutar consultas SQL en bruto o realizar operaciones de base de datos que no tienen un equivalente directo en métodos de ActiveRecord. Puedes usar reversible
para especificar qué hacer al ejecutar una migración y qué más hacer al revertirla. Por ejemplo:
class ExampleMigration < ActiveRecord::Migration[7.2]
def change
create_table :distributors do |t|
t.string :zipcode
end
reversible do |direction|
direction.up do
# crear una vista de distributors
execute <<-SQL
CREATE VIEW distributors_view AS
SELECT id, zipcode
FROM distributors;
SQL
end
direction.down do
execute <<-SQL
DROP VIEW distributors_view;
SQL
end
end
add_column :users, :address, :string
end
end
Usar reversible
asegurará que las instrucciones se ejecuten en el orden correcto también. Si la migración de ejemplo anterior se revierte, el bloque down
se ejecutará después de que se elimine la columna users.address
y antes de que se elimine la tabla distributors
.
3.12 Usando los Métodos up
/down
También puedes usar el estilo antiguo de migración usando los métodos up
y down
en lugar del método change
.
El método up
debe describir la transformación que te gustaría hacer a tu esquema, y el método down
de tu migración debe revertir las transformaciones realizadas por el método up
. En otras palabras, el esquema de la base de datos debería ser el mismo si haces un up
seguido de un down
.
Por ejemplo, si creas una tabla en el método up
, debes eliminarla en el método down
. Es prudente realizar las transformaciones en el orden inverso en que se hicieron en el método up
. El ejemplo en la sección reversible
es equivalente a:
class ExampleMigration < ActiveRecord::Migration[7.2]
def up
create_table :distributors do |t|
t.string :zipcode
end
# crear una vista de distributors
execute <<-SQL
CREATE VIEW distributors_view AS
SELECT id, zipcode
FROM distributors;
SQL
add_column :users, :address, :string
end
def down
remove_column :users, :address
execute <<-SQL
DROP VIEW distributors_view;
SQL
drop_table :distributors
end
end
3.13 Lanzar un error para evitar reversiones
A veces, tu migración hará algo que es simplemente irreversible; por ejemplo, podría destruir algunos datos.
En tales casos, puedes levantar ActiveRecord::IrreversibleMigration
en tu bloque down
.
class IrreversibleMigrationExample < ActiveRecord::Migration[7.2]
def up
drop_table :example_table
end
def down
raise ActiveRecord::IrreversibleMigration, "Esta migración no se puede revertir porque destruye datos."
end
end
Si alguien intenta revertir tu migración, se mostrará un mensaje de error diciendo que no se puede hacer.
3.14 Revertir Migraciones Anteriores
Puedes usar la capacidad de Active Record para revertir migraciones usando el método revert
:
require_relative "20121212123456_example_migration"
class FixupExampleMigration < ActiveRecord::Migration[7.2]
def change
revert ExampleMigration
create_table(:apples) do |t|
t.string :variety
end
end
end
El método revert
también acepta un bloque de instrucciones para revertir. Esto podría ser útil para revertir partes seleccionadas de migraciones anteriores.
Por ejemplo, imaginemos que ExampleMigration
se compromete y luego se decide que una vista de Distributors ya no es necesaria.
class DontUseDistributorsViewMigration < ActiveRecord::Migration[7.2]
def change
revert do
# código copiado de ExampleMigration
create_table :distributors do |t|
t.string :zipcode
end
reversible do |direction|
direction.up do
# crear una vista de distributors
execute <<-SQL
CREATE VIEW distributors_view AS
SELECT id, zipcode
FROM distributors;
SQL
end
direction.down do
execute <<-SQL
DROP VIEW distributors_view;
SQL
end
end
# El resto de la migración estaba bien
end
end
end
La misma migración también podría haberse escrito sin usar revert
, pero esto habría involucrado algunos pasos más:
- Invertir el orden de
create_table
yreversible
. - Reemplazar
create_table
condrop_table
. - Finalmente, reemplazar
up
condown
y viceversa.
Todo esto se maneja con revert
.
4 Ejecución de Migraciones
Rails proporciona un conjunto de comandos para ejecutar ciertos conjuntos de migraciones.
El primer comando relacionado con migraciones que probablemente usarás será bin/rails db:migrate
. En su forma más básica, simplemente ejecuta el método change
o up
para todas las migraciones que aún no se han ejecutado. Si no hay tales migraciones, sale. Ejecutará estas migraciones en orden basado en la fecha de la migración.
Ten en cuenta que ejecutar el comando db:migrate
también invoca el comando db:schema:dump
, que actualizará tu archivo db/schema.rb
para que coincida con la estructura de tu base de datos.
Si especificas una versión de destino, Active Record ejecutará las migraciones necesarias (change, up, down) hasta que haya alcanzado la versión especificada. La versión es el prefijo numérico en el nombre del archivo de la migración. Por ejemplo, para migrar a la versión 20240428000000 ejecuta:
$ bin/rails db:migrate VERSION=20240428000000
Si la versión 20240428000000 es mayor que la versión actual (es decir, está migrando hacia arriba), esto ejecutará el método change
(o up
) en todas las migraciones hasta e incluyendo 20240428000000, y no ejecutará ninguna migración posterior. Si migra hacia abajo, esto ejecutará el método down
en todas las migraciones hasta, pero sin incluir, 20240428000000.
4.1 Retroceso
Una tarea común es revertir la última migración. Por ejemplo, si cometiste un error en ella y deseas corregirlo. En lugar de rastrear el número de versión asociado con la migración anterior, puedes ejecutar:
$ bin/rails db:rollback
Esto revertirá la última migración, ya sea revirtiendo el método change
o ejecutando el método down
. Si necesitas deshacer varias migraciones, puedes proporcionar un parámetro STEP
:
$ bin/rails db:rollback STEP=3
Las últimas 3 migraciones serán revertidas.
En algunos casos donde modificas una migración local y te gustaría revertir esa migración específica antes de migrar de nuevo, puedes usar el comando db:migrate:redo
. Al igual que con el comando db:rollback
, puedes usar el parámetro STEP
si necesitas retroceder más de una versión, por ejemplo:
$ bin/rails db:migrate:redo STEP=3
NOTA: Podrías obtener el mismo resultado usando db:migrate
. Sin embargo, estos están ahí para conveniencia para que no necesites especificar explícitamente la versión a la que migrar.
4.1.1 Transacciones
En bases de datos que soportan transacciones DDL, cambiando el esquema en una sola transacción, cada migración se envuelve en una transacción.
Una transacción asegura que si una migración falla a mitad de camino, cualquier cambio que se haya aplicado exitosamente se revierta, manteniendo la consistencia de la base de datos. Esto significa que ya sea que todas las operaciones dentro de la transacción se ejecuten exitosamente, o ninguna de ellas lo haga, evitando que la base de datos quede en un estado inconsistente si ocurre un error durante la transacción.
Si la base de datos no soporta transacciones DDL con declaraciones que cambian el esquema, entonces cuando una migración falla, las partes de ella que han tenido éxito no se revertirán. Tendrás que revertir los cambios manualmente.
Hay consultas que no puedes ejecutar dentro de una transacción, y para estas situaciones puedes desactivar las transacciones automáticas con disable_ddl_transaction!
:
class ChangeEnum < ActiveRecord::Migration[7.2]
disable_ddl_transaction!
def up
execute "ALTER TYPE model_size ADD VALUE 'new_value'"
end
end
NOTA: Recuerda que aún puedes abrir tus propias transacciones, incluso si estás en una Migración con self.disable_ddl_transaction!.
4.2 Configuración de la Base de Datos
El comando bin/rails db:setup
creará la base de datos, cargará el esquema e inicializará con los datos de semillas.
4.3 Preparación de la Base de Datos
El comando bin/rails db:prepare
es similar a bin/rails db:setup
, pero opera de manera idempotente, por lo que se puede llamar de manera segura varias veces, pero solo realizará las tareas necesarias una vez.
- Si la base de datos aún no ha sido creada, el comando se ejecutará como lo hace
bin/rails db:setup
. - Si la base de datos existe pero las tablas no han sido creadas, el comando cargará el esquema, ejecutará cualquier migración pendiente, volcará el esquema actualizado y finalmente cargará los datos de semillas. Consulta la documentación de Datos de Semillas para más detalles.
- Si tanto la base de datos como las tablas existen pero los datos de semillas no han sido cargados, el comando solo cargará los datos de semillas.
- Si la base de datos, tablas y datos de semillas están todos en su lugar, el comando no hará nada.
NOTA: Una vez que la base de datos, tablas y datos de semillas están todos establecidos, el comando no intentará recargar los datos de semillas, incluso si los datos de semillas previamente cargados o el archivo de semillas existente han sido alterados o eliminados. Para recargar los datos de semillas, puedes ejecutar manualmente bin/rails db:seed
.
4.4 Restablecimiento de la Base de Datos
El comando bin/rails db:reset
eliminará la base de datos y la configurará de nuevo. Esto es funcionalmente equivalente a bin/rails db:drop db:setup
.
NOTA: Esto no es lo mismo que ejecutar todas las migraciones. Solo usará el contenido del archivo actual db/schema.rb
o db/structure.sql
. Si una migración no se puede revertir, bin/rails db:reset
puede no ayudarte. Para obtener más información sobre el volcado del esquema, consulta la sección Volcado de Esquema y Tú.
4.5 Ejecución de Migraciones Específicas
Si necesitas ejecutar una migración específica hacia arriba o hacia abajo, los comandos db:migrate:up
y db:migrate:down
lo harán. Solo especifica la versión apropiada y la migración correspondiente tendrá su método change
, up
o down
invocado, por ejemplo:
$ bin/rails db:migrate:up VERSION=20240428000000
Al ejecutar este comando, se ejecutará el método change
(o el método up
) para la migración con la versión "20240428000000".
Primero, este comando verificará si la migración existe y si ya ha sido realizada y si es así, no hará nada.
Si la versión especificada no existe, Rails lanzará una excepción.
$ bin/rails db:migrate VERSION=00000000000000
rails aborted!
ActiveRecord::UnknownMigrationVersionError:
No migration with version number 00000000000000.
4.6 Ejecución de Migraciones en Diferentes Entornos
Por defecto, ejecutar bin/rails db:migrate
se ejecutará en el entorno development
.
Para ejecutar migraciones contra otro entorno, puedes especificarlo usando la variable de entorno RAILS_ENV
al ejecutar el comando. Por ejemplo, para ejecutar migraciones contra el entorno test
, podrías ejecutar:
$ bin/rails db:migrate RAILS_ENV=test
4.7 Cambiando la Salida de las Migraciones
Por defecto, las migraciones te dicen exactamente lo que están haciendo y cuánto tiempo tomó. Una migración creando una tabla y agregando un índice podría producir una salida como esta:
== CreateProducts: migrating =================================================
-- create_table(:products)
-> 0.0028s
== CreateProducts: migrated (0.0028s) ========================================
Se proporcionan varios métodos en las migraciones que te permiten controlar todo esto:
Método | Propósito |
---|---|
suppress_messages |
Toma un bloque como argumento y suprime cualquier salida generada por el bloque. |
say |
Toma un argumento de mensaje y lo muestra tal cual. Se puede pasar un segundo argumento booleano para especificar si se debe o no sangrar. |
say_with_time |
Muestra texto junto con cuánto tiempo tomó ejecutar su bloque. Si el bloque devuelve un entero, asume que es el número de filas afectadas. |
Por ejemplo, toma la siguiente migración:
class CreateProducts < ActiveRecord::Migration[7.2]
def change
suppress_messages do
create_table :products do |t|
t.string :name
t.text :description
t.timestamps
end
end
say "Created a table"
suppress_messages { add_index :products, :name }
say "and an index!", true
say_with_time 'Waiting for a while' do
sleep 10
250
end
end
end
Esto generará la siguiente salida:
== CreateProducts: migrating =================================================
-- Created a table
-> and an index!
-- Waiting for a while
-> 10.0013s
-> 250 rows
== CreateProducts: migrated (10.0054s) =======================================
Si quieres que Active Record no muestre nada, entonces ejecutar bin/rails db:migrate VERBOSE=false
suprimirá toda la salida.
4.8 Control de Versión de Migraciones de Rails
Rails lleva un registro de qué migraciones se han ejecutado a través de la tabla schema_migrations
en la base de datos. Cuando ejecutas una migración, Rails inserta una fila en la tabla schema_migrations
con el número de versión de la migración, almacenado en la columna version
. Esto permite a Rails determinar qué migraciones ya se han aplicado a la base de datos.
Por ejemplo, si tienes un archivo de migración llamado 20240428000000_create_users.rb, Rails extraerá el número de versión (20240428000000) del nombre del archivo y lo insertará en la tabla schema_migrations después de que la migración se haya ejecutado con éxito.
Puedes ver el contenido de la tabla schema_migrations directamente en tu herramienta de gestión de bases de datos o usando la consola de Rails:
rails dbconsole
Luego, dentro de la consola de la base de datos, puedes consultar la tabla schema_migrations:
SELECT * FROM schema_migrations;
Esto te mostrará una lista de todos los números de versión de migración que se han aplicado a la base de datos. Rails utiliza esta información para determinar qué migraciones necesitan ejecutarse cuando ejecutas los comandos rails db:migrate o rails db:migrate:up.
5 Cambiar Migraciones Existentes
Ocasionalmente, cometerás un error al escribir una migración. Si ya has ejecutado la migración, entonces no puedes simplemente editar la migración y ejecutar la migración nuevamente: Rails piensa que ya ha ejecutado la migración y, por lo tanto, no hará nada cuando ejecutes bin/rails db:migrate
. Debes revertir la migración (por ejemplo, con bin/rails db:rollback
), editar tu migración y luego ejecutar bin/rails db:migrate
para ejecutar la versión corregida.
En general, editar migraciones existentes que ya han sido comprometidas en el control de versiones no es una buena idea. Estarás creando trabajo extra para ti y tus compañeros de trabajo y causarás grandes dolores de cabeza si la versión existente de la migración ya se ha ejecutado en máquinas de producción. En su lugar, deberías escribir una nueva migración que realice los cambios que necesitas.
Sin embargo, editar una migración recién generada que aún no ha sido comprometida en el control de versiones (o, más generalmente, no se ha propagado más allá de tu máquina de desarrollo) es común.
El método revert
puede ser útil al escribir una nueva migración para deshacer migraciones anteriores en su totalidad o en parte (consulta Revertir Migraciones Anteriores arriba).
6 Volcado de Esquema y Tú
6.1 ¿Para qué son los Archivos de Esquema?
Las migraciones, por poderosas que sean, no son la fuente autorizada para tu esquema de base de datos. Tu base de datos sigue siendo la fuente de verdad.
Por defecto, Rails genera db/schema.rb
que intenta capturar el estado actual de tu esquema de base de datos.
Tiende a ser más rápido y menos propenso a errores crear una nueva instancia de la base de datos de tu aplicación cargando el archivo de esquema a través de bin/rails db:schema:load
que reproducir todo el historial de migraciones. Las migraciones antiguas pueden fallar al aplicarse correctamente si esas migraciones utilizan dependencias externas cambiantes o dependen de código de aplicación que evoluciona por separado de tus migraciones.
CONSEJO: Los archivos de esquema también son útiles si deseas un vistazo rápido a qué atributos tiene un objeto Active Record. Esta información no está en el código del modelo y con frecuencia se distribuye a través de varias migraciones, pero la información está bien resumida en el archivo de esquema.
6.2 Tipos de Volcados de Esquema
El formato del volcado de esquema generado por Rails está controlado por la configuración config.active_record.schema_format
definida en config/application.rb
. Por defecto, el formato es :ruby
, o alternativamente se puede establecer en :sql
.
6.2.1 Usando el esquema predeterminado :ruby
Cuando se selecciona :ruby
, el esquema se almacena en db/schema.rb
. Si miras este archivo, encontrarás que se parece mucho a una migración muy grande:
ActiveRecord::Schema[7.2].define(version: 2008_09_06_171750) do
create_table "authors", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "products", force: true do |t|
t.string "name"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
t.string "part_number"
end
end
En muchos sentidos, esto es exactamente lo que es. Este archivo se crea al inspeccionar la base de datos y expresar su estructura usando create_table
, add_index
, y así sucesivamente.
6.2.2 Usando el volcado de esquema :sql
Sin embargo, db/schema.rb
no puede expresar todo lo que tu base de datos puede soportar, como triggers, secuencias, procedimientos almacenados, etc.
Mientras que las migraciones pueden usar execute
para crear constructos de base de datos que no son soportados por el DSL de migraciones de Ruby, estos constructos pueden no poder ser reconstituidos por el volcado de esquema.
Si estás utilizando características como estas, deberías establecer el formato de esquema en :sql
para obtener un archivo de esquema preciso que sea útil para crear nuevas instancias de base de datos.
Cuando el formato de esquema está establecido en :sql
, la estructura de la base de datos se volcará usando una herramienta específica para la base de datos en db/structure.sql
. Por ejemplo, para PostgreSQL, se utiliza la utilidad pg_dump
. Para MySQL y MariaDB, este archivo contendrá la salida de SHOW CREATE TABLE
para las diversas tablas.
Para cargar el esquema desde db/structure.sql
, ejecuta bin/rails db:schema:load
. Cargar este archivo se hace ejecutando las declaraciones SQL que contiene. Por definición, esto creará una copia perfecta de la estructura de la base de datos.
6.3 Volcados de Esquema y Control de Versiones
Debido a que los archivos de esquema se utilizan comúnmente para crear nuevas bases de datos, se recomienda encarecidamente que verifiques tu archivo de esquema en el control de versiones.
Pueden ocurrir conflictos de fusión en tu archivo de esquema cuando dos ramas modifican el esquema. Para resolver estos conflictos, ejecuta bin/rails db:migrate
para regenerar el archivo de esquema.
Las aplicaciones Rails recién generadas ya tendrán la carpeta de migraciones incluida en el árbol de git, así que todo lo que tienes que hacer es asegurarte de agregar cualquier nueva migración que agregues y comprometerlas.
7 Active Record e Integridad Referencial
El patrón Active Record sugiere que la inteligencia debe residir principalmente en tus modelos en lugar de en la base de datos. En consecuencia, características como triggers o restricciones, que delegan parte de esa inteligencia de nuevo en la base de datos, no siempre son favorecidas.
Las validaciones como validates :foreign_key, uniqueness: true
son una forma en que los modelos pueden hacer cumplir la integridad de los datos. La opción :dependent
en las asociaciones permite que los modelos destruyan automáticamente objetos secundarios cuando se destruye el padre. Como cualquier cosa que opere a nivel de aplicación, estos no pueden garantizar la integridad referencial, por lo que algunas personas los complementan con restricciones de clave externa en la base de datos.
En la práctica, las restricciones de clave externa e índices únicos generalmente se consideran más seguras cuando se hacen cumplir a nivel de base de datos. Aunque Active Record no proporciona soporte directo para trabajar con estas características a nivel de base de datos, aún puedes usar el método execute para ejecutar comandos SQL arbitrarios.
Vale la pena enfatizar que, aunque el patrón Active Record enfatiza mantener la inteligencia dentro de los modelos, descuidar la implementación de claves externas y restricciones únicas a nivel de base de datos puede potencialmente llevar a problemas de integridad. Por lo tanto, es recomendable complementar el patrón AR con restricciones a nivel de base de datos donde sea apropiado. Estas restricciones deben tener sus contrapartes definidas explícitamente en tu código usando asociaciones y validaciones para garantizar la integridad de los datos en ambas capas de aplicación y base de datos.
8 Migraciones y Datos de Semillas
El propósito principal de la función de migración de Rails es emitir comandos que modifiquen el esquema usando un proceso consistente. Las migraciones también se pueden usar para agregar o modificar datos. Esto es útil en una base de datos existente que no se puede destruir y recrear, como una base de datos de producción.
class AddInitialProducts < ActiveRecord::Migration[7.2]
def up
5.times do |i|
Product.create(name: "Product ##{i}", description: "A product.")
end
end
def down
Product.delete_all
end
end
Para agregar datos iniciales después de que se crea una base de datos, Rails tiene una función integrada de 'semillas' que acelera el proceso. Esto es especialmente útil al recargar la base de datos con frecuencia en entornos de desarrollo y prueba, o al configurar datos iniciales para producción.
Para comenzar con esta característica, abre db/seeds.rb
y agrega algo de código Ruby, luego ejecuta bin/rails db:seed
.
NOTA: El código aquí debe ser idempotente para que pueda ejecutarse en cualquier momento en cada entorno.
["Action", "Comedy", "Drama", "Horror"].each do |genre_name|
MovieGenre.find_or_create_by!(name: genre_name)
end
Esto es generalmente una forma mucho más limpia de configurar la base de datos de una aplicación en blanco.
9 Migraciones Antiguas
El db/schema.rb
o db/structure.sql
es una instantánea del estado actual de tu base de datos y es la fuente autorizada para reconstruir esa base de datos. Esto hace posible eliminar o podar archivos de migración antiguos.
Cuando eliminas archivos de migración en el directorio db/migrate/
, cualquier entorno donde bin/rails db:migrate
se ejecutó cuando esos archivos aún existían tendrá una referencia al timestamp de migración específico para ellos dentro de una tabla interna de la base de datos de Rails llamada schema_migrations
. Puedes leer más sobre esto en la sección de Control de Versión de Migraciones de Rails.
Si ejecutas el comando bin/rails db:migrate:status
, que muestra el estado (arriba o abajo) de cada migración, deberías ver ********** NO FILE **********
mostrado junto a cualquier archivo de migración eliminado que una vez se ejecutó en un entorno específico pero que ya no se puede encontrar en el directorio db/migrate/
.
9.1 Migraciones desde Engines
Al tratar con migraciones desde Engines, hay una advertencia a considerar. Las tareas Rake para instalar migraciones desde engines son idempotentes, lo que significa que tendrán el mismo resultado sin importar cuántas veces se llamen. Las migraciones presentes en la aplicación principal debido a una instalación previa se omiten, y las que faltan se copian con un nuevo timestamp inicial. Si eliminaste migraciones antiguas de engines y ejecutaste la tarea de instalación nuevamente, obtendrías nuevos archivos con nuevos timestamps, y db:migrate
intentaría ejecutarlas nuevamente.
Por lo tanto, generalmente deseas preservar las migraciones provenientes de engines. Tienen un comentario especial como este:
# Esta migración proviene de blorgh (originalmente 20210621082949)
10 Miscelánea
10.1 Usando UUIDs en lugar de IDs para Claves Primarias
Por defecto, Rails usa enteros autoincrementados como claves primarias para registros de base de datos. Sin embargo, hay escenarios donde usar Identificadores Únicos Universales (UUIDs) como claves primarias puede ser ventajoso, especialmente en sistemas distribuidos o cuando la integración con servicios externos es necesaria. Los UUIDs proporcionan un identificador globalmente único sin depender de una autoridad centralizada para generar IDs.
10.1.1 Habilitando UUIDs en Rails
Antes de usar UUIDs en tu aplicación Rails, necesitarás asegurarte de que tu base de datos soporte almacenarlos. Además, es posible que necesites configurar tu adaptador de base de datos para trabajar con UUIDs.
NOTA: Si estás usando una versión de PostgreSQL anterior a la 13, es posible que aún necesites habilitar la extensión pgcrypto para acceder a la función gen_random_uuid()
.
- Configuración de Rails
En el archivo de configuración de tu aplicación Rails (config/application.rb
), agrega la siguiente línea para configurar Rails para generar UUIDs como claves primarias por defecto:
```ruby
config.generators do |g|
g.orm :active_record, primary_key_type: :uuid
end
```
Esta configuración instruye a Rails para usar UUIDs como el tipo de clave primaria predeterminado para los modelos de ActiveRecord.
Agregar Referencias con UUIDs:
Al crear asociaciones entre modelos usando referencias, asegúrate de especificar el tipo de dato como :uuid para mantener la consistencia con el tipo de clave primaria. Por ejemplo:
create_table :posts, id: :uuid do |t| t.references :author, type: :uuid, foreign_key: true # Otras columnas... t.timestamps end
En este ejemplo, la columna
author_id
en la tabla de posts hace referencia a la columnaid
de la tabla de autores. Al establecer explícitamente el tipo en:uuid
, aseguras que la columna de clave externa coincide con el tipo de dato de la clave primaria a la que hace referencia. Ajusta la sintaxis según sea necesario para otras asociaciones y bases de datos.Cambios de Migración
Al generar migraciones para tus modelos, notarás que especifica el id para que sea de tipo
uuid:
$ bin/rails g migration CreateAuthors
class CreateAuthors < ActiveRecord::Migration[7.2] def change create_table :authors, id: :uuid do |t| t.timestamps end end end
que resulta en el siguiente esquema:
create_table "authors", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end
En esta migración, la columna
id
se define como una clave primaria UUID con un valor predeterminado generado por la funcióngen_random_uuid()
.
Los UUIDs están garantizados para ser globalmente únicos en diferentes sistemas, haciéndolos adecuados para arquitecturas distribuidas. También simplifican la integración con sistemas externos o APIs al proporcionar un identificador único que no depende de la generación centralizada de IDs, y a diferencia de los enteros autoincrementados, los UUIDs no exponen información sobre el número total de registros en una tabla, lo cual puede ser beneficioso para propósitos de seguridad.
Sin embargo, los UUIDs también pueden impactar el rendimiento debido a su tamaño y son más difíciles de indexar. Los UUIDs tendrán un peor rendimiento para escrituras y lecturas en comparación con claves primarias y claves externas de enteros.
NOTA: Por lo tanto, es esencial evaluar las compensaciones y considerar los requisitos específicos de tu aplicación antes de decidir usar UUIDs como claves primarias.
10.2 Migraciones de Datos
Las migraciones de datos implican transformar o mover datos dentro de tu base de datos. En Rails, generalmente no se recomienda realizar migraciones de datos usando archivos de migración. Aquí está el porqué:
- Separación de Preocupaciones: Los cambios de esquema y los cambios de datos tienen diferentes ciclos de vida y propósitos. Los cambios de esquema alteran la estructura de tu base de datos, mientras que los cambios de datos alteran el contenido.
- Complejidad de Reversión: Las migraciones de datos pueden ser difíciles de revertir de manera segura y predecible.
- Rendimiento: Las migraciones de datos pueden tardar mucho tiempo en ejecutarse y pueden bloquear tus tablas, afectando el rendimiento y la disponibilidad de la aplicación.
En su lugar, considera usar el
maintenance_tasks
gem. Este gem proporciona un marco para crear y gestionar migraciones de datos y otras tareas de mantenimiento de una manera que sea segura y fácil de gestionar sin interferir con las migraciones de esquema.
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.