Actualizar una columna al valor de otra en la migración de Rails

80

Tengo una tabla en una aplicación Rails con cientos de miles de registros y solo tienen una created_atmarca de tiempo. Estoy agregando la capacidad de editar estos registros, por lo que quiero agregar una updated_atmarca de tiempo a la tabla. En mi migración para agregar la columna, quiero actualizar todas las filas para que la nueva updated_atcoincida con la anterior created_at, ya que ese es el valor predeterminado para las filas recién creadas en Rails. Podría hacer una find(:all)iteración a través de los registros, pero eso llevaría horas debido al tamaño de la tabla. Lo que realmente quiero hacer es:

UPDATE table_name SET updated_at = created_at;

¿Hay una manera mejor de hacerlo en una migración de Rails usando ActiveRecord en lugar de ejecutar SQL sin formato?

jrdioko
fuente

Respuestas:

136

Crearía una migración

rails g migration set_updated_at_values

y dentro escribe algo como:

class SetUpdatedAt < ActiveRecord::Migration
  def self.up
    Yourmodel.update_all("updated_at=created_at")
  end

  def self.down
  end
end

Así logras dos cosas

  • este es un proceso repetible, con cada posible implementación (donde sea necesario) se ejecuta
  • esto es eficiente. No puedo pensar en una solución más rubyesca (que sea tan eficiente).

Nota: también puede ejecutar sql sin procesar dentro de una migración, si la consulta se vuelve demasiado difícil de escribir usando activerecord. Solo escribe lo siguiente:

Yourmodel.connection.execute("update your_models set ... <complicated query> ...")
nathanvda
fuente
+1 - Recientemente tuve que hacer exactamente esto y usé SQL sobre un ActiveRecord. Es lo más rápido posible.
Peter Brown
40
Yourmodel.update_all 'update_at=created_at'es mejor, no? También funciona en un telescopio.
Marc-André Lafortune
Según la guía de Rails : "el esquema de la base de datos no debería modificarse si haces una upseguida de down" . Así que considérelo def changesolo en su lugar.
EliadL
1
@EliadL algunas observaciones: 1) no estamos cambiando el esquema, solo el contenido de la base de datos. Y 2) en el momento en que se escribió esta respuesta, el changemétodo aún no existía, pero en este caso todavía prefiero usar explícito upy downser más explícito (si desea controlar lo que downdebe hacer).
nathanvda
20

Puede usar update_all, que funciona de manera muy similar a SQL sin formato . Esas son todas las opciones que tienes.

Por cierto, personalmente, no presto mucha atención a las migraciones. A veces, SQL sin formato es realmente la mejor solución. Generalmente, el código de migraciones no se reutiliza. Esta es una acción de una sola vez, así que no me preocupo por la pureza del código.

Greg Dan
fuente
2
Eso depende de sus necesidades de implementación. Realmente me gusta usar las migraciones, porque permiten implementar de forma repetible en plataformas existentes y obtener el mismo resultado. Tenemos varias etapas de implementación: desarrollo, prueba, control de calidad / aceptación, una plataforma de prueba de concepto (para clientes de prueba), una plataforma de producción: necesitamos poder migrar los datos existentes a la versión recién implementada sin fallas. Agregar una columna y asegurarse de que los datos estén bien, en nuestro caso, NO es una acción única.
nathanvda
Escribo sobre el uso del update_allarchivo de migración interno :-) También puede ejecutar SQL sin procesar dentro del archivo de migración. Sin embargo, update_alles un poco más elegante. Ambos funcionarán exactamente igual.
Greg Dan
Por lo general, es una buena idea declarar el modelo en la migración, ya que esto evitará problemas si el modelo original se redefine más adelante. Acabo de encontrar este artículo que explica todo bastante bien: complicado-simplicity.com/2010/05/…
François Beausoleil
Con update_allno tengo idea de cómo establecer un valor de columna al de otra, como solicitó el OP. Por favor demuestre.
nathanvda
14

Como escribió gregdan, puede usar update_all. Puedes hacer algo como esto:

Model.where(...).update_all('updated_at = created_at')

La primera parte es su conjunto típico de condiciones. La última parte dice cómo hacer las asignaciones. Esto producirá una UPDATEdeclaración, al menos en Rails 4.

Martin Streicher
fuente
Esto en 4.2 genera SET'posts'.'email' = 'options', las opciones son una cadena literal
lulalala
Confirmar este consejo tampoco funciona para mí. No estoy seguro de que sea una solución completamente incorrecta. Don't donwvote @martin answer
woto
Aquí está el resultado de la consola de Rails:User.update_all('updated_at = created_at') SQL (0.4ms) UPDATE "users" SET updated_at = created_at
Martin Streicher
2

Puede ejecutar directamente el siguiente comando en su rails console ActiveRecord::Base.connection.execute("UPDATE TABLE_NAME SET COL2 = COL1")

Por ejemplo: quiero actualizar el sku de mi tabla de elementos con remote_id of items tables. el comando será el siguiente:
ActiveRecord::Base.connection.execute("UPDATE items SET sku = remote_id")

Sarwan Kumar
fuente
En realidad, esta es la forma más segura de "historia", porque en el futuro (cuando algunos ejecuten migraciones, el modelo Yourmodelya se puede eliminar. Trate de evitar el uso de modelos en las migraciones.)
Foton
0

Esta es una forma general de resolución, sin necesidad de escribir consultas, ya que las consultas están sujetas a riesgos.

  class Demo < ActiveRecord::Migration
    def change
     add_column :events, :time_zone, :string
     Test.all.each do |p|
       p.update_attributes(time_zone: p.check.last.time_zone)
     end
     remove_column :sessions, :time_zone
    end
  end
Wilson Varghese
fuente
-4

Como operación de una sola vez, lo haría en el rails console. ¿Realmente tomará horas? Quizás si hay millones de registros ...

records = ModelName.all; records do |r|; r.update_attributes(:updated_at => r.created_at); r.save!; end;`
ghoppe
fuente
Eso es esencialmente lo que probé primero, pero debido a que hay cientos de miles de registros que deben cambiarse, eso llevará horas (¿días?).
jrdioko
Cuando lo probé, iba a unos 50 registros por segundo en mi máquina de desarrollo (no en un servidor).
jrdioko
4
Siempre evite la iteración si es posible, evite usar 'all' que carga todos los registros en la RAM a la vez, y dado que update_attributes ya guarda automáticamente, ¡la llamada adicional para guardar! hará que toda la operación tome el doble de tiempo.
ryan0