NO LEA ESTE ARCHIVO EN GITHUB, LAS GUÍAS ESTÁN PUBLICADAS EN https://guides.rubyonrails.org.

Migraciones de Active Record

Las migraciones son una característica de Active Record que te permite evolucionar tu esquema de base de datos con el tiempo. En lugar de escribir modificaciones de esquema en SQL puro, las migraciones te permiten usar un Lenguaje Específico de Dominio (DSL) en Ruby para describir cambios en tus tablas.

Después de leer esta guía, sabrás:


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 columna string o text.
  • 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). Usa nil para NULL.
  • limit Establece el número máximo de caracteres para una columna string y el número máximo de bytes para columnas text/binary/integer.
  • null Permite o no permite valores NULL en la columna.
  • precision Especifica la precisión para columnas decimal/numeric/datetime/time.
  • scale Especifica la escala para las columnas decimal y numeric, 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 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:

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:

  1. Invertir el orden de create_table y reversible.
  2. Reemplazar create_table con drop_table.
  3. Finalmente, reemplazar up con down 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().

  1. 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.
  1. 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 columna id 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.

  2. 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ón gen_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.