2011-03-07 20 views
56

Tengo una tabla en una aplicación de Rails con cientos de miles de registros, y solo tienen una marca de tiempo created_at. Estoy agregando la capacidad de editar estos registros, por lo que quiero agregar una marca de tiempo updated_at a la tabla. En mi migración para agregar la columna, deseo actualizar todas las filas para que el nuevo updated_at coincida con el anterior created_at, ya que es el predeterminado para las filas recién creadas en Rails. Podría hacer un find(:all) e iterar a través de los registros, pero eso tomaría horas debido al tamaño de la tabla. Lo que realmente quiero hacer es:Actualizar una columna al valor de otra en la migración de Rails

UPDATE table_name SET updated_at = created_at; 

¿Existe una manera más agradable de hacer eso en una migración rieles usando ActiveRecord en lugar de ejecutar SQL crudo?

Respuesta

80

crearía una migración

rails g migration set_updated_at_values 

y en su interior escribir algo como:

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

    def self.down 
    end 
end 

De esta manera se consiguen dos cosas

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

Nota: también puede ejecutar sql sin formato dentro de una migración, si la consulta se vuelve demasiado difícil de escribir con activerecord. Sólo tiene que escribir lo siguiente:

Yourmodel.connection.execute("update your_models set ... <complicated query> ...") 
+0

+1 - Hace poco tuve que hacer exactamente esto y utilicé SQL sobre ActiveRecord. Es lo más rápido que puede llegar. –

+38

'Yourmodel.update_all 'update_at = created_at'' es más bonito, ¿no? Funciona en un alcance también. –

19

Puede usar update_all que funciona muy similar al SQL sin formato. Esas son todas las opciones que tiene.

BTW personalmente no le presto mucha atención a las migraciones. A veces, el SQL en bruto es realmente la mejor solución. En general, el código de migraciones no se reutiliza. Esta es una acción única, así que no me preocupo por la pureza del código.

+1

Eso depende de sus necesidades de implementación. Realmente me gusta usar las migraciones, ya que permiten la implementación repetible en plataformas existentes y obtienen el mismo resultado. Tenemos varias etapas de implementación: desarrollo, prueba, qa/aceptación, una plataforma de prueba de concepto (para probar clientes), una plataforma de producción: tenemos que ser capaces de migrar los datos existentes a la versión recién implementada sin falta. Agregar una columna y asegurarse de que los datos estén correctos en nuestro caso NO es una acción única. – nathanvda

+0

Escribo sobre el uso de 'update_all' dentro del archivo de migración :-) También puede ejecutar SQL sin procesar dentro del archivo de migración. Sin embargo 'update_all' es un poco más elegante. Ambos realizarán exactamente lo mismo. –

+0

Generalmente, 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 muy bien: http://complicated-simplicity.com/2010/05/using-models-in-rails-migrations/ –

-2

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

records = ModelName.all; records do |r|; r.update_attributes(:updated_at => r.created_at); r.save!; end;` 
+0

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

+0

Cuando lo probé, iba a aproximadamente 50 registros por segundo en mi máquina de desarrollo (no en un servidor). – jrdioko

+3

Siempre evite la iteración si es posible, evite usar 'todo' que carga cada registro en la RAM de una vez, y dado que la actualización de atributos ya hace un ahorro automáticamente, ¡la llamada adicional para guardar! hará que toda la operación tome el doble de tiempo. – ryan0

4

Como escribió gregdan, puede utilizar update_all. Puede 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 declaración UPDATE, al menos en Rails 4.

+0

Esto en 4.2 genera 'SET'posts '.' Email '=' options'', las opciones son una cadena literal – lulalala

+0

Confirmar que este consejo tampoco me funciona. No estoy seguro de que sea una solución completamente incorrecta. No envíes @martin answer – woto

+0

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' –

Cuestiones relacionadas