Agregar marcas de tiempo a una tabla existente

173

Necesito agregar marcas de tiempo ( created_at& updated_at) a una tabla existente. Intenté el siguiente código pero no funcionó.

class AddTimestampsToUser < ActiveRecord::Migration
    def change_table
        add_timestamps(:users)
    end
end
Leonel
fuente

Respuestas:

211

El asistente de marca de tiempo solo está disponible en el create_tablebloque. Puede agregar estas columnas especificando los tipos de columna manualmente:

class AddTimestampsToUser < ActiveRecord::Migration
  def change_table
    add_column :users, :created_at, :datetime, null: false
    add_column :users, :updated_at, :datetime, null: false
  end
end

Si bien esto no tiene la misma sintaxis concisa que el add_timestampsmétodo que especificó anteriormente, Rails seguirá tratando estas columnas como columnas de marca de tiempo y actualizará los valores normalmente.

Ben Simpson
fuente
10
Esto no funcionó para mí en Rails 4. La siguiente solución de "mu es demasiado corta" está funcionando.
newUserNameHere
21
rails g migration AddTimestampsToUser created_at:datetime updated_at:datetime- un atajo para generar la migración anterior.
Konstantine Kalbazov
2
Ejecutar esta migración conduce a un error PG::NotNullViolation: ERROR: column "created_at" contains null value porque mi tabla ya contiene datos que violan la restricción no nula. ¿Alguna mejor manera de hacerlo que eliminar la contracción no nula al principio y luego agregarla?
M. Habib
1
@ M.Habib No lo creo, pero esta respuesta lo resume todo en una sola migración.
littleforest
1
@ M.Habib depende de lo que creas que tiene más sentido para el valor predeterminado que podrías hacer add_column :users, :updated_at, :datetime, null: false, default: Time.zone.now. Time.zone.nowes solo un ejemplo, debe usar cualquier valor que tenga sentido para su lógica.
Delong Gao
91

Las migraciones son solo dos métodos de clase (o métodos de instancia en 3.1): upy down(y a veces un changemétodo de instancia en 3.1). Desea que sus cambios entren en el upmétodo:

class AddTimestampsToUser < ActiveRecord::Migration
  def self.up # Or `def up` in 3.1
    change_table :users do |t|
      t.timestamps
    end
  end
  def self.down # Or `def down` in 3.1
    remove_column :users, :created_at
    remove_column :users, :updated_at
  end
end

Si está en 3.1, entonces también podría usar change(gracias Dave):

class AddTimestampsToUser < ActiveRecord::Migration
  def change
    change_table(:users) { |t| t.timestamps }
  end
end

Tal vez estás confundiendo def change, def change_tabley change_table.

Consulte la guía de migración para más detalles.

mu es demasiado corto
fuente
1
(Bueno, changeahora está el método, aunque en este caso, no es el problema :)
Dave Newton
@Dave: Es cierto, elegí genérico para evitar los problemas de versión, pero changevale la pena mencionarlo, así que también lo agregaré.
mu es demasiado corto el
Es cierto, pero he escuchado que eso realmente está cambiando con 3.1 y que el 'down' realmente está desapareciendo. Rieles para descubrir el método down automáticamente. ¿Has oído hablar de eso?
Michael Durrant
@Michael: He estado usando MongoDB exclusivamente con la aplicación 3.1 en la que estoy trabajando, así que no he trabajado con migraciones 3.1 AR. Los documentos indican que todo se está moviendo hacia métodos de instancia (por razones desconocidas).
mu es demasiado corto el
@MichaelDurrant, hay muchos escenarios que "cambio" no cubre en este momento, si arriba / abajo desaparece habrá gente enojada :) (agregue una cláusula "a menos que" en su migración de cambio para evitar colisiones migratorias, e intente haciendo retroceder eso ...) Incluso 3 años después de haber hecho este comentario, no creo que esté cambiando. :)
frandroid
76

Su código original está muy cerca de la derecha, solo necesita usar un nombre de método diferente. Si está utilizando Rails 3.1 o posterior, debe definir un changemétodo en lugar de change_table:

class AddTimestampsToUser < ActiveRecord::Migration
  def change
    add_timestamps(:users)
  end
end

Si está utilizando una versión anterior, debe definir métodos upy downmétodos en lugar de change_table:

class AddTimestampsToUser < ActiveRecord::Migration
  def up
    add_timestamps(:users)
  end

  def down
    remove_timestamps(:users)
  end
end
georgebrock
fuente
58

La respuesta de @ user1899434 recogió el hecho de que una tabla "existente" aquí podría significar una tabla con registros ya en ella, registros que quizás no desee eliminar. Entonces, cuando agrega marcas de tiempo con nulo: falso, que es el valor predeterminado y, a menudo, deseable, todos los registros existentes no son válidos.

Pero creo que esa respuesta puede mejorarse, combinando los dos pasos en una migración, y usando el método más semántico add_timestamps:

def change
  add_timestamps :projects, default: Time.zone.now
  change_column_default :projects, :created_at, nil
  change_column_default :projects, :updated_at, nil
end

Podría sustituir alguna otra marca de tiempo DateTime.now, como si quisiera crear / actualizar registros preexistentes al comienzo de los tiempos.

Nick Davies
fuente
2
Asombroso. ¡Gracias! Solo una nota: Time.zone.nowes lo que se debe usar si queremos que nuestro código obedezca a la zona horaria correcta.
John Gallagher
44
Hay un problema con la configuración predeterminada, Time.zone.nowque devolverá la instancia de Time que se crea cuando se ejecuta la migración y solo usará ese tiempo como predeterminado. Los nuevos objetos no obtendrán una nueva instancia de Time.
Tovi Newman
38
class AddTimestampsToUser < ActiveRecord::Migration
  def change
    change_table :users do |t|
      t.timestamps
    end
  end
end

Las transformaciones disponibles son

change_table :table do |t|
  t.column
  t.index
  t.timestamps
  t.change
  t.change_default
  t.rename
  t.references
  t.belongs_to
  t.string
  t.text
  t.integer
  t.float
  t.decimal
  t.datetime
  t.timestamp
  t.time
  t.date
  t.binary
  t.boolean
  t.remove
  t.remove_references
  t.remove_belongs_to
  t.remove_index
  t.remove_timestamps
end

http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html

Pradeep Sanjaya
fuente
10

La respuesta de Nick Davies es la más completa en términos de agregar columnas de marca de tiempo a una tabla con datos existentes. Su único inconveniente es que aumentará ActiveRecord::IrreversibleMigrationen a db:rollback.

Debe modificarse así para trabajar en ambas direcciones:

def change
  add_timestamps :campaigns, default: DateTime.now
  change_column_default :campaigns, :created_at, from: DateTime.now, to: nil
  change_column_default :campaigns, :updated_at, from: DateTime.now, to: nil
end
lightyrs
fuente
Esto no funcionó exactamente como está escrito para mí en Rails 4.2.7 (creo que change_column_defaultno es compatible fromy toen esa versión?), ¡Pero tomé esta idea y creé up/downmétodos en lugar de un solo changemétodo y funcionó de maravilla!
gar
8
def change
  add_timestamps :table_name
end
Ian Vaughan
fuente
4

no estoy seguro de cuándo se introdujo exactamente esto, pero en rails 5.2.1 puede hacer esto:

class AddTimestampsToMyTable < ActiveRecord::Migration[5.2]
  def change
    add_timestamps :my_table
  end
end

para obtener más información, consulte " uso del método de cambio " en los documentos de migraciones de registros activos.

Arrecife Loretto
fuente
No lo hice funcionar con Migration [5.1]; luego cambié el número a [5.2] y Rails me dijo que solo podía usar 5.1, 5.0 o 4.2. Lo intenté con 5.0 sin éxito, luego en 4.2 con éxito.
Es Ma
Viejo, lo sé, pero si tiene registros existentes agregue: , null: truedespués del:my_table
jomar
2

Hice una función simple que puede llamar para agregar a cada tabla (suponiendo que tenga una base de datos existente) los campos created_at y updated_at :

  # add created_at and updated_at to each table found.
  def add_datetime
    tables = ActiveRecord::Base.connection.tables
    tables.each do |t|
      ActiveRecord::Base.connection.add_timestamps t  
    end    
  end
Roger
fuente
2

add_timestamps (table_name, options = {}) public

Agrega columnas de marcas de tiempo (created_at y updated_at) a table_name. Las opciones adicionales (como null: false) se envían a #add_column.

class AddTimestampsToUsers < ActiveRecord::Migration
  def change
    add_timestamps(:users, null: false)
  end
end
almawhoob
fuente
1

Las respuestas anteriores parecen correctas, sin embargo, tuve problemas si mi tabla ya tiene entradas.

Obtendría 'ERROR: la columna created_atcontiene nullvalores'.

Para arreglarlo, usé:

def up
  add_column :projects, :created_at, :datetime, default: nil, null: false
  add_column :projects, :updated_at, :datetime, default: nil, null: false
end

Luego usé la gema migración_datos para agregar el tiempo para los proyectos actuales en la migración, tales como:

def data
  Project.update_all created_at: Time.now
end

Luego, todos los proyectos creados después de esta migración se actualizarán correctamente. Asegúrese de que el servidor también se reinicie para que Rails ActiveRecordcomience a rastrear las marcas de tiempo en el registro.

dbrody
fuente
1

Muchas respuestas aquí, pero también publicaré las mías porque ninguna de las anteriores realmente funcionó para mí :)

Como algunos han notado, #add_timestampsdesafortunadamente agrega la null: falserestricción, lo que hará que las filas antiguas no sean válidas porque no tienen estos valores rellenados. La mayoría de las respuestas aquí sugieren que establezcamos un valor predeterminado (Time.zone.now ), pero no me gustaría hacerlo porque estas marcas de tiempo predeterminadas para datos antiguos no serán correctas. No veo el valor en agregar datos incorrectos a la tabla.

Entonces mi migración fue simplemente:

class AddTimestampsToUser < ActiveRecord::Migration
  def change_table
    add_column :projects, :created_at, :datetime
    add_column :projects, :updated_at, :datetime
  end
end

No null: false, no hay otras restricciones. Las filas antiguas continuarán siendo válidas con created_atas NULLy update_atcomo NULL(hasta que se realice alguna actualización en la fila). Las nuevas filas tendrán created_aty se completarán updated_atcomo se esperaba.

Kostis
fuente
1

El problema con la mayoría de las respuestas aquí es que si usa Time.zone.nowtodos los registros de manera predeterminada , el tiempo de ejecución de la migración será el tiempo predeterminado, lo que probablemente no sea lo que desea. En los rieles 5 puedes usarlos now(). Esto establecerá las marcas de tiempo para los registros existentes como la hora en que se ejecutó la migración y como la hora de inicio de la transacción de confirmación para los registros recién insertados.

class AddTimestampsToUsers < ActiveRecord::Migration def change add_timestamps :users, default: -> { 'now()' }, null: false end end

jlesse
fuente
1

Usar Time.currentes un buen estilo https://github.com/rubocop-hq/rails-style-guide#timenow

def change
  change_table :users do |t|
    t.timestamps default: Time.current
    t.change_default :created_at, from: Time.current, to: nil
    t.change_default :updated_at, from: Time.current, to: nil
  end
end

o

def change
  add_timestamps :users, default: Time.current
  change_column_default :users, :created_at, from: Time.current, to: nil
  change_column_default :users, :updated_at, from: Time.current, to: nil
end
shilovk
fuente
1

Este es uno simple para agregar marca de tiempo en la tabla existente.

class AddTimeStampToCustomFieldMeatadata < ActiveRecord::Migration
  def change
    add_timestamps :custom_field_metadata
  end
end
Dinesh Vaitage
fuente
0

Para aquellos que no usan Rails pero sí usan activerecord, lo siguiente también agrega una columna a un modelo existente, por ejemplo, para un campo entero.

ActiveRecord::Schema.define do
  change_table 'MYTABLE' do |table|
    add_column(:mytable, :my_field_name, :integer)
  end
end
Peter
fuente
0

Es change, no change_tablepara rieles 4.2:

class AddTimestampsToUsers < ActiveRecord::Migration
  def change
    add_timestamps(:users)
  end
end
Igor T.
fuente
0

Esto parece una solución limpia en Rails 5.0.7 (descubrió el método change_column_null):

def change
  add_timestamps :candidate_offices, default: nil, null: true
  change_column_null(:candidate_offices, :created_at, false, Time.zone.now)
  change_column_null(:candidate_offices, :created_at, false, Time.zone.now)
end
Wes Gamble
fuente
0

Estoy en rails 5.0 y ninguna de estas opciones funcionó.

Lo único que funcionó fue usar el tipo to be: timestamp y not: datetime

def change
    add_column :users, :created_at, :timestamp
    add_column :users, :updated_at, :timestamp
end
Vishnu Narang
fuente
-1

Personalmente utilicé lo siguiente y actualizó todos los registros anteriores con la hora / fecha actual:

add_column :<table>, :created_at, :datetime, default: Time.zone.now, null: false
add_column :<table>, :updated_at, :datetime, default: Time.zone.now, null: false
Jaime
fuente
-2

Me encontré con el mismo problema en Rails 5 tratando de usar

change_table :my_table do |t|
    t.timestamps
end

Pude agregar las columnas de marca de tiempo manualmente con lo siguiente:

change_table :my_table do |t|
    t.datetime :created_at, null: false, default: DateTime.now
    t.datetime :updated_at, null: false, default: DateTime.now
end
Andres Rosales
fuente
¿No establecerá esto siempre el valor predeterminado con el tiempo en el momento en que se ejecutó la migración? (por lo que no es realmente una marca de tiempo dinámica manejada por el DB)
Guillaume Petit
para los registros que ya existen en su base de datos, sí, establecerá created_at y updated_at a la fecha y hora en que se ejecutó la migración. Sin embargo, sin tener esos valores de antemano, idk de qué otra manera inicializaría esos valores. EDITAR: Solo se consideraría el comienzo de la historia de esa fila
Andres Rosales