Tambahkan cap waktu ke tabel yang ada

173

Saya perlu menambahkan cap waktu ( created_at& updated_at) ke tabel yang ada. Saya mencoba kode berikut tetapi tidak berhasil.

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

Jawaban:

211

Pembantu cap waktu hanya tersedia di create_tableblok. Anda dapat menambahkan kolom ini dengan menentukan jenis kolom secara manual:

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

Meskipun ini tidak memiliki sintaks singkat yang sama dengan add_timestampsmetode yang telah Anda tentukan di atas, Rails masih akan memperlakukan kolom ini sebagai kolom timestamp, dan memperbarui nilai-nilai secara normal.

Ben Simpson
sumber
10
Ini tidak bekerja untuk saya di Rails 4. Solusi di bawah ini dengan "mu terlalu pendek" bekerja.
newUserNameHere
21
rails g migration AddTimestampsToUser created_at:datetime updated_at:datetime- pintasan untuk menghasilkan migrasi di atas.
Konstantine Kalbazov
2
Menjalankan migrasi ini menyebabkan kesalahan PG::NotNullViolation: ERROR: column "created_at" contains null value karena tabel saya sudah berisi data yang melanggar bukan batasan nol. Adakah cara yang lebih baik untuk melakukan ini daripada menghapus contraint yang tidak nol pada awalnya dan kemudian menambahkannya nanti?
M. Habib
1
@ M. Habib Saya kira tidak, tapi jawaban ini merangkum semuanya dalam satu migrasi dengan baik.
littleforest
1
@ M. Habib tergantung pada apa yang menurut Anda paling masuk akal untuk nilai default, yang bisa Anda lakukan add_column :users, :updated_at, :datetime, null: false, default: Time.zone.now. Time.zone.nowhanyalah sebuah contoh, Anda harus menggunakan nilai apa pun yang masuk akal untuk logika Anda.
Delong Gao
91

Migrasi hanyalah dua metode kelas (atau metode instance pada 3.1): updan down(dan terkadang changemetode instance pada 3.1). Anda ingin perubahan Anda masuk ke upmetode:

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

Jika Anda berada di 3.1 maka Anda juga dapat menggunakan change(terima kasih Dave):

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

Mungkin Anda membingungkan def change, def change_tabledan change_table.

Lihat panduan migrasi untuk detail lebih lanjut.

mu terlalu pendek
sumber
1
(Yah, ada changemetode sekarang, meskipun dalam kasus ini, bukan masalahnya :)
Dave Newton
@ Dave: Cukup benar, saya menggunakan generik untuk menghindari masalah versi tetapi changepatut disebutkan jadi saya akan menambahkannya juga.
mu terlalu pendek
Benar mu tapi aku pernah mendengar bahwa itu benar-benar berubah dengan 3.1 dan 'turun' benar-benar hilang. Rel untuk mengetahui metode turun secara otomatis. Pernahkah Anda mendengar tentang itu?
Michael Durrant
@Michael: Saya telah menggunakan MongoDB secara eksklusif dengan aplikasi 3.1 yang saya kerjakan jadi saya belum bekerja dengan migrasi 3.1 AR. Dokumen menunjukkan bahwa semuanya bergerak menuju metode instan (untuk alasan yang tidak diketahui).
mu terlalu pendek
@MichaelDurrant, ada banyak skenario yang "tidak berubah" mencakup sekarang, jika naik / turun akan ada beberapa orang yang marah :) (tambahkan klausa "kecuali" dalam migrasi perubahan Anda untuk menghindari tabrakan migrasi, dan coba memutar kembali ...) Bahkan 3 tahun setelah Anda membuat komentar ini, saya tidak berpikir itu berubah. :)
frandroid
76

Kode asli Anda sangat dekat dengan kanan, Anda hanya perlu menggunakan nama metode yang berbeda. Jika Anda menggunakan Rails 3.1 atau yang lebih baru, Anda perlu mendefinisikan changemetode alih-alih change_table:

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

Jika Anda menggunakan versi yang lebih lama, Anda perlu mendefinisikan updan downmetode alih-alih change_table:

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

  def down
    remove_timestamps(:users)
  end
end
georgebrock
sumber
59

Tanggapan @ user1899434 menjawab pada fakta bahwa tabel "yang ada" di sini bisa berarti tabel dengan catatan yang sudah ada di dalamnya, catatan yang mungkin tidak ingin Anda jatuhkan. Jadi, ketika Anda menambahkan cap waktu dengan null: false, yang merupakan default dan sering diinginkan, semua catatan yang ada semuanya tidak valid.

Tapi saya pikir jawaban itu dapat ditingkatkan, dengan menggabungkan dua langkah menjadi satu migrasi, serta menggunakan metode add_timestamps yang lebih semantik:

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

Anda dapat mengganti stempel waktu lain untuk DateTime.now, seperti jika Anda ingin catatan yang sudah ada dibuat / diperbarui pada waktu fajar sebagai gantinya.

Nick Davies
sumber
2
Luar biasa. Terima kasih! Hanya satu catatan - Time.zone.nowadalah apa yang harus digunakan jika kita ingin kode kita mematuhi zona waktu yang benar.
John Gallagher
4
Ada masalah dengan pengaturan default Time.zone.nowyang akan mengembalikan contoh waktu yang dibuat ketika migrasi dijalankan dan cukup gunakan waktu itu sebagai default. Objek baru tidak akan mendapatkan instance Waktu baru.
Tovi Newman
38
class AddTimestampsToUser < ActiveRecord::Migration
  def change
    change_table :users do |t|
      t.timestamps
    end
  end
end

Transformasi yang tersedia adalah

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

Jawaban Nick Davies adalah yang paling lengkap dalam hal menambahkan kolom cap waktu ke tabel dengan data yang ada. Satu-satunya downside adalah bahwa ia akan naik ActiveRecord::IrreversibleMigrationpada db:rollback.

Ini harus dimodifikasi agar berfungsi di kedua arah:

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
sumber
Ini tidak bekerja persis seperti yang ditulis untuk saya di Rails 4.2.7 (saya pikir change_column_defaulttidak mendukung fromdan todalam versi itu?), Tapi saya mengambil ide ini dan menciptakan up/downmetode, bukan metode tunggal changedan itu bekerja seperti pesona!
gar
8
def change
  add_timestamps :table_name
end
Ian Vaughan
sumber
4

tidak yakin kapan tepatnya ini diperkenalkan, tetapi di rel 5.2.1 Anda bisa melakukan ini:

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

untuk lebih lanjut lihat " menggunakan metode perubahan " di dokumentasi migrasi catatan aktif.

Karang Loretto
sumber
Saya tidak membuatnya bekerja dengan Migrasi [5.1]; kemudian saya mengubah nomornya menjadi [5.2] dan Rails memberi tahu saya bahwa saya hanya bisa menggunakan 5.1, 5.0 atau 4.2. Saya sudah mencoba dengan 5.0 tanpa hasil, kemudian dengan 4.2 dengan sukses
Is Ma
Tua, saya tahu, tetapi jika Anda memiliki catatan yang ada tambahkan: , null: truesetelah:my_table
jomar
2

Saya membuat fungsi sederhana yang dapat Anda panggil untuk ditambahkan ke setiap tabel (dengan asumsi Anda memiliki database yang sudah ada) bidang Created_at dan 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
sumber
2

add_timestamps (table_name, options = {}) publik

Menambahkan kolom cap waktu (Created_at dan updated_at) ke table_name. Opsi tambahan (seperti null: false) diteruskan ke #add_column.

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

Jawaban sebelumnya tampaknya benar tetapi saya menghadapi masalah jika meja saya sudah memiliki entri.

Saya akan mendapatkan 'GALAT: kolom created_atberisi nullnilai'.

Untuk memperbaikinya, saya menggunakan:

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

Saya kemudian menggunakan gem migration_data untuk menambahkan waktu untuk proyek saat ini pada migrasi seperti:

def data
  Project.update_all created_at: Time.now
end

Kemudian semua proyek yang dibuat setelah migrasi ini akan diperbarui dengan benar. Pastikan server juga dinyalakan ulang sehingga Rails ActiveRecordmulai melacak cap waktu pada catatan.

dbrody
sumber
1

Banyak jawaban di sini, tetapi saya juga akan memposting jawaban saya karena tidak ada yang sebelumnya benar-benar bekerja untuk saya :)

Seperti yang telah dicatat oleh beberapa orang, #add_timestampssayangnya menambahkan null: falsebatasan, yang akan menyebabkan baris lama menjadi tidak valid karena mereka tidak memiliki nilai-nilai ini. Sebagian besar jawaban di sini menyarankan agar kami menetapkan beberapa nilai default (Time.zone.now ), tetapi saya tidak ingin melakukannya karena cap waktu default untuk data lama ini tidak akan benar. Saya tidak melihat nilai dalam menambahkan data yang salah ke tabel.

Jadi migrasi saya hanyalah:

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

Tidak null: false, tidak ada batasan lain. Baris lama akan terus valid dengan created_atas NULL, dan update_atas NULL(hingga beberapa pembaruan dilakukan ke baris). Baris baru akan memiliki created_atdan updated_atmengisi seperti yang diharapkan.

Kostis
sumber
1

Masalah dengan sebagian besar jawaban di sini adalah bahwa jika Anda default ke Time.zone.nowsemua catatan akan memiliki waktu migrasi dijalankan sebagai waktu default mereka, yang mungkin bukan yang Anda inginkan. Di rel 5 Anda bisa menggunakan now(). Ini akan mengatur stempel waktu untuk catatan yang ada saat migrasi dijalankan, dan sebagai waktu mulai transaksi komit untuk catatan yang baru dimasukkan.

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

jlesse
sumber
1

Menggunakan Time.currentadalah gaya yang baik 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

atau

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

Ini sederhana untuk menambahkan cap waktu di tabel yang ada.

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

Bagi mereka yang tidak menggunakan Rails tetapi menggunakan activerecord, berikut ini juga menambahkan kolom ke model yang sudah ada, contohnya adalah untuk bidang integer.

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

Ini change, bukan change_tableuntuk Rails 4.2:

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

Ini sepertinya solusi bersih di Rails 5.0.7 (menemukan metode 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
sumber
0

Saya di kereta 5.0 dan tidak ada opsi ini bekerja.

Satu-satunya hal yang berhasil adalah menggunakan tipe menjadi: timestamp dan bukan: datetime

def change
    add_column :users, :created_at, :timestamp
    add_column :users, :updated_at, :timestamp
end
Wisnu Narang
sumber
-1

Saya pribadi menggunakan yang berikut ini, dan memperbarui semua catatan sebelumnya dengan waktu / tanggal saat ini:

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

Saya mengalami masalah yang sama pada Rails 5 mencoba menggunakan

change_table :my_table do |t|
    t.timestamps
end

Saya dapat menambahkan kolom cap waktu secara manual dengan yang berikut:

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
sumber
tidakkah ini selalu menetapkan nilai default dengan waktu saat migrasi dijalankan? (jadi bukan cap waktu dinamis yang ditangani oleh DB)
Guillaume Petit
untuk catatan yang sudah ada di db Anda, ya, itu akan mengatur create_at dan updated_at ke datetime migrasi dijalankan. Tanpa memiliki nilai-nilai itu sebelumnya, idk bagaimana lagi Anda menginisialisasi nilai-nilai itu. EDIT: Ini hanya akan dianggap sebagai awal dari sejarah baris itu
Andres Rosales