Adicionar carimbos de data / hora a uma tabela existente

173

Eu preciso adicionar registros de data created_ate hora ( & updated_at) a uma tabela existente. Eu tentei o código a seguir, mas não funcionou.

class AddTimestampsToUser < ActiveRecord::Migration
    def change_table
        add_timestamps(:users)
    end
end
leonel
fonte

Respostas:

211

O auxiliar de carimbo de data / hora está disponível apenas no create_tablebloco. Você pode adicionar essas colunas especificando os tipos de coluna 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

Embora isso não tenha a mesma sintaxe concisa que o add_timestampsmétodo especificado acima, o Rails ainda tratará essas colunas como colunas de carimbo de data e hora e atualizará os valores normalmente.

Ben Simpson
fonte
10
Isso não funcionou para mim no Rails 4. A solução abaixo de "mu is too short" está funcionando.
newUserNameHere
21
rails g migration AddTimestampsToUser created_at:datetime updated_at:datetime- um atalho para gerar a migração acima.
Konstantine Kalbazov
2
A execução dessa migração leva a erros, PG::NotNullViolation: ERROR: column "created_at" contains null value porque minha tabela já contém dados que violam restrições não nulas. Existe uma maneira melhor de fazer isso do que remover primeiro a contraint não nula e depois adicioná-la mais tarde?
M. Habib
1
@ M.Habib Acho que não, mas esta resposta resume tudo em uma migração bem.
littleforest
1
@ M.Habib depende do que você acha que faz mais sentido para o valor padrão, você poderia fazer add_column :users, :updated_at, :datetime, null: false, default: Time.zone.now. Time.zone.nowé apenas um exemplo, você deve usar qualquer valor que faça sentido para sua lógica.
Delong Gao
91

As migrações são apenas dois métodos de classe (ou métodos de instância na 3.1): upe down(e às vezes um changemétodo de instância na 3.1). Você deseja que suas alterações entrem no 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

Se você estiver na versão 3.1, também poderá usar change(obrigado Dave):

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

Talvez você está confuso def change, def change_tablee change_table.

Consulte o guia de migração para obter mais detalhes.

mu é muito curto
fonte
1
(Bem, há o changemétodo agora, embora, neste caso, não é a questão :)
Dave Newton
@ Dave: É verdade, eu fui genérico para evitar os problemas de versão, mas changevale a pena mencionar, então eu vou adicionar isso também.
mu é muito curto
Verdade mu, mas ouvi dizer que isso está realmente mudando com o 3.1 e o 'down' está realmente desaparecendo. Trilhos para descobrir o método down automaticamente. Você já ouviu falar sobre isso?
Michael Durrant
@ Michael: Eu tenho usado o MongoDB exclusivamente com o aplicativo 3.1 em que estou trabalhando, por isso não trabalhei com migrações 3.1 de AR. Os documentos indicam que tudo está mudando para métodos de instância (por razões desconhecidas).
mu é muito curta
@MichaelDurrant, existem muitos cenários que a "mudança" não cobre no momento, se for para cima / para baixo, haverá algumas pessoas irritadas :) (adicione uma cláusula "a menos que" na sua migração de alterações para evitar colisões de migração e tente revertendo isso ...) Mesmo três anos depois de você fazer esse comentário, acho que não está mudando. :)
frandroid
76

Seu código original está muito próximo da direita, você só precisa usar um nome de método diferente. Se você estiver usando o Rails 3.1 ou posterior, precisará definir um changemétodo em vez de change_table:

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

Se você estiver usando uma versão mais antiga, precisará definir upe downmétodos em vez de change_table:

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

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

A resposta de @ user1899434 se deu conta de que uma tabela "existente" aqui poderia significar uma tabela com registros já nela, registros que talvez você não queira descartar. Portanto, quando você adiciona carimbos de data / hora com null: false, que é o padrão e geralmente desejável, esses registros existentes são todos inválidos.

Mas acho que essa resposta pode ser melhorada, combinando as duas etapas em uma migração e usando o método add_timestamps mais semântico:

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

Você pode substituir outro carimbo de data / hora DateTime.now, como se desejasse que os registros preexistentes fossem criados / atualizados logo no início.

Nick Davies
fonte
2
Surpreendente. Obrigado! Apenas uma observação - Time.zone.nowé o que deve ser usado se quisermos que nosso código obedeça ao fuso horário correto.
John Gallagher
4
Há um problema ao definir o padrão, Time.zone.nowque retornará a instância Time criada quando a migração for executada e usará esse horário como padrão. Novos objetos não receberão uma nova instância de Time.
Tovi Newman
38
class AddTimestampsToUser < ActiveRecord::Migration
  def change
    change_table :users do |t|
      t.timestamps
    end
  end
end

As transformações disponíveis são

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
fonte
10

A resposta de Nick Davies é a mais completa em termos de adição de colunas de carimbo de data / hora a uma tabela com dados existentes. Sua única desvantagem é que ele vai aumentar ActiveRecord::IrreversibleMigrationem umdb:rollback .

Deve ser modificado da seguinte maneira para funcionar nas duas direções:

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
fonte
Isso não funcionou exatamente como foi escrito para mim no Rails 4.2.7 (acho change_column_defaultque não suporta frome tonessa versão?), Mas peguei essa ideia e criei up/downmétodos em vez de um único changemétodo e funcionou como um encanto!
gar
8
def change
  add_timestamps :table_name
end
Ian Vaughan
fonte
4

Não tenho certeza de quando exatamente isso foi introduzido, mas no Rails 5.2.1 você pode fazer isso:

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

para obter mais informações, consulte " usando o método de alteração " nos documentos de migração de registros ativos.

Recife Loretto
fonte
Eu não fiz isso funcionar com a migração [5.1]; então mudei o número para [5.2] e o Rails me disse que eu só podia usar 5.1, 5.0 ou 4.2. Eu tentei com 5.0 sem sucesso, em 4.2 com sucesso.
Is Ma
Velho, eu sei, mas se você tiver registros existentes, adicione: , null: trueafter the:my_table
jomar
2

Eu criei uma função simples que você pode chamar para adicionar a cada tabela (supondo que você tenha um banco de dados existente) os campos created_at e 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
fonte
2

add_timestamps (table_name, options = {}) public

Adiciona colunas de registro de data e hora (created_at e updated_at) ao nome_tabela. Opções adicionais (como null: false) são encaminhadas para #add_column.

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

As respostas anteriores parecem corretas, no entanto, eu enfrentei problemas se minha tabela já tiver entradas.

Eu receberia 'ERRO: a coluna created_atcontém nullvalores'.

Para consertar, eu usei:

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

Em seguida, usei o gem migration_data para adicionar o tempo para projetos atuais na migração, como:

def data
  Project.update_all created_at: Time.now
end

Todos os projetos criados após essa migração serão atualizados corretamente. Verifique se o servidor também foi reiniciado para que o Rails ActiveRecordcomece a rastrear os carimbos de data e hora no registro.

dbrody
fonte
1

Muitas respostas aqui, mas vou postar as minhas também porque nenhuma das anteriores realmente funcionou para mim :)

Como alguns observaram, #add_timestampsinfelizmente , adiciona a null: falserestrição, o que fará com que as linhas antigas sejam inválidas porque elas não têm esses valores preenchidos. A maioria das respostas aqui sugere que definimos algum valor padrão (Time.zone.now ), mas eu não gostaria de fazer isso porque esses carimbos de data e hora padrão para dados antigos não estarão corretos. Não vejo o valor em adicionar dados incorretos à tabela.

Então, minha migração foi simplesmente:

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

Não null: false, não há outras restrições. As linhas antigas continuarão sendo válidas com created_atas NULLe update_atas NULL(até que alguma atualização seja realizada na linha). Novas linhas terão created_ate serão updated_atpreenchidas conforme o esperado.

Kostis
fonte
1

O problema com a maioria das respostas aqui é que, se você usar como padrão Time.zone.nowtodos os registros, terá o tempo em que a migração foi executada como o tempo padrão, o que provavelmente não é o que você deseja. Nos trilhos 5, você pode usar now(). Isso definirá os registros de data e hora dos registros existentes como a hora em que a migração foi executada e como a hora de início da transação de confirmação dos registros inseridos recentemente.

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

jlesse
fonte
1

Usar Time.currenté um bom 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

ou

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
fonte
1

É simples adicionar um registro de data e hora na tabela existente.

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

Para aqueles que não usam o Rails, mas usam o activerecord, o seguinte também adiciona uma coluna a um modelo existente, por exemplo, um campo inteiro.

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

É change, não change_tablepara Rails 4.2:

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

Parece uma solução limpa no Rails 5.0.7 (descoberto o 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
fonte
0

Estou nos trilhos 5.0 e nenhuma dessas opções funcionou.

A única coisa que funcionou foi usar o tipo a ser: timestamp e não: datetime

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

Eu pessoalmente usei o seguinte e ele atualizou todos os registros anteriores com a hora / data atual:

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
fonte
-2

Corri para o mesmo problema no Rails 5 tentando usar

change_table :my_table do |t|
    t.timestamps
end

Consegui adicionar as colunas de carimbo de data / hora manualmente com o seguinte:

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
fonte
isso sempre não definirá o valor padrão com o horário no momento em que a migração foi executada? (portanto, não é realmente um carimbo de data / hora dinâmico tratado pelo DB)
Guillaume Petit
para os registros que já existem no seu banco de dados, sim, ele definirá o created_at e updated_at para a data e hora em que a migração foi executada. Porém, sem ter esses valores antecipadamente, idk, de que outra forma você inicializaria esses valores. Edição: Seria apenas considerado o começo da história dessa linha
Andres Rosales