Dodaj sygnatury czasowe do istniejącej tabeli

173

Muszę dodać znaczniki czasu ( created_at& updated_at) do istniejącej tabeli. Wypróbowałem następujący kod, ale nie zadziałał.

class AddTimestampsToUser < ActiveRecord::Migration
    def change_table
        add_timestamps(:users)
    end
end
leonel
źródło

Odpowiedzi:

211

Pomocnik znacznika czasu jest dostępny tylko w create_tablebloku. Możesz dodać te kolumny, określając typy kolumn ręcznie:

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

Chociaż nie ma tej samej zwięzłej składni, co add_timestampsmetoda, którą określiłeś powyżej, Railsy nadal będą traktować te kolumny jako kolumny ze znacznikami czasu i normalnie aktualizować wartości.

Ben Simpson
źródło
10
To nie zadziałało w Railsach 4. Poniższe rozwiązanie "mu jest za krótkie" działa.
newUserNameHere
21
rails g migration AddTimestampsToUser created_at:datetime updated_at:datetime- skrót do wygenerowania migracji powyżej.
Konstantine Kalbazov
2
Uruchomienie tej migracji prowadzi do błędu, PG::NotNullViolation: ERROR: column "created_at" contains null value ponieważ moja tabela zawiera już dane, które naruszają ograniczenie niepuste. Czy jest jakiś lepszy sposób na zrobienie tego niż najpierw usunięcie niezerowego ograniczenia, a następnie dodanie go później?
M. Habib
1
@ M.Habib Nie sądzę, ale ta odpowiedź ładnie podsumowuje to wszystko w jednej migracji.
littleforest
1
@ M.Habib zależy od tego, co uważasz za najbardziej sensowne dla wartości domyślnej, co możesz zrobić add_column :users, :updated_at, :datetime, null: false, default: Time.zone.now. Time.zone.nowto tylko przykład, powinieneś użyć dowolnej wartości, która ma sens dla twojej logiki.
Delong Gao
91

Migracje to tylko dwie metody klas (lub metody instancji w 3.1): upi down(a czasami changemetoda instancji w 3.1). Chcesz, aby zmiany zostały wprowadzone do upmetody:

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

Jeśli jesteś w wersji 3.1, możesz również użyć change(dzięki Dave):

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

Być może jesteś mylące def change, def change_tablei change_table.

Więcej informacji zawiera przewodnik migracji .

mu jest za krótkie
źródło
1
(Cóż, teraz jest changemetoda, chociaż w tym przypadku nie problem :)
Dave Newton
@Dave: To prawda, wybrałem generyczny, aby uniknąć problemów z wersją, ale changewarto o tym wspomnieć, więc też to dodam.
mu jest za krótkie
To prawda, ale słyszałem, że to się naprawdę zmienia w wersji 3.1 i „spadek” naprawdę odchodzi. Szyny do automatycznego określenia metody w dół. Słyszałeś o tym?
Michael Durrant,
@Michael: Używam MongoDB wyłącznie z aplikacją 3.1, nad którą pracuję, więc nie pracowałem z migracjami 3.1 AR. Dokumentacja wskazuje, że wszystko zmierza w kierunku metod instancji (z nieznanych powodów).
mu jest za krótkie
@MichaelDurrant, istnieje wiele scenariuszy, których „zmiana” nie obejmuje w tej chwili, jeśli znikną w górę / w dół, pojawią się rozgniewani ludzie :) (dodaj klauzulę „chyba” w migracji zmian, aby uniknąć kolizji migracji, i spróbuj cofam to ...) Nawet 3 lata po tym, jak napisałeś ten komentarz, nie sądzę, żeby to się zmieniło. :)
frandroid
76

Twój oryginalny kod jest bardzo zbliżony do właściwego, wystarczy użyć innej nazwy metody. Jeśli używasz Railsów 3.1 lub nowszych, musisz zdefiniować changemetodę zamiast change_table:

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

Jeśli używasz starszej wersji, musisz zdefiniować upi downmetody zamiast change_table:

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

  def down
    remove_timestamps(:users)
  end
end
georgebrock
źródło
59

@ user1899434 odpowiedział na fakt, że „istniejąca” tabela może oznaczać tabelę z już zawartymi rekordami, których możesz nie chcieć usunąć. Dlatego po dodaniu znaczników czasu z wartością null: false, która jest wartością domyślną i często pożądaną, wszystkie istniejące rekordy są nieprawidłowe.

Ale myślę, że odpowiedź można poprawić, łącząc dwa kroki w jedną migrację, a także używając bardziej semantycznej metody 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

Możesz zastąpić inną sygnaturę czasową DateTime.now, na przykład gdybyś chciał zamiast tego utworzyć / zaktualizować istniejące wcześniej rekordy.

Nick Davies
źródło
2
Niesamowity. Dziękuję Ci! Jedna uwaga - Time.zone.nowtego należy użyć, jeśli chcemy, aby nasz kod działał zgodnie z właściwą strefą czasową.
John Gallagher,
4
Występuje problem z ustawieniem wartości domyślnej, Time.zone.nowktóra polega na zwróceniu instancji Time, która jest tworzona podczas uruchamiania migracji, i po prostu używa tego czasu jako domyślnego. Nowe obiekty nie otrzymają nowej instancji Time.
Tovi Newman
38
class AddTimestampsToUser < ActiveRecord::Migration
  def change
    change_table :users do |t|
      t.timestamps
    end
  end
end

Dostępne są transformacje

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
źródło
10

Odpowiedź Nicka Daviesa jest najbardziej kompletna pod względem dodawania kolumn sygnatur czasowych do tabeli z istniejącymi danymi. Jedynym minusem jest to, że wzrośnie ActiveRecord::IrreversibleMigrationnadb:rollback .

Należy go tak zmodyfikować, aby działał w obu kierunkach:

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
źródło
To nie działało dokładnie tak, jak napisano dla mnie na Railsach 4.2.7 (myślę, że change_column_defaultnie obsługuje fromiw totej wersji?), Ale wziąłem ten pomysł i stworzyłem up/downmetody zamiast jednej changemetody i działało to jak urok!
gar
8
def change
  add_timestamps :table_name
end
Ian Vaughan
źródło
4

nie jestem pewien, kiedy dokładnie to zostało wprowadzone, ale w railsach 5.2.1 możesz to zrobić:

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

aby uzyskać więcej informacji, zobacz „ Korzystanie z metody zmiany ” w dokumentacji migracji aktywnych rekordów.

Rafa Loretto
źródło
Nie udało mi się to z Migration [5.1]; potem zmieniłem numer na [5.2] i Railsy poinformowały mnie, że mogę używać tylko 5.1, 5.0 lub 4.2. Próbowałem z 5.0 bez powodzenia, potem z 4.2 z sukcesem.
Ma
Stare, wiem, ale jeśli masz istniejące rekordy dodaj: , null: truepo:my_table
jomar
2

Zrobiłem prostą funkcję, że można zadzwonić, aby dodać do każdej tabeli (zakładając, że masz istniejącej bazy danych) created_at i updated_at dziedzinach:

  # 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
zrozumiałem
źródło
2

add_timestamps (nazwa_tabeli, opcje = {}) public

Dodaje kolumny sygnatur czasowych (created_at i updated_at) do table_name. Dodatkowe opcje (takie jak null: false) są przekazywane do #add_column.

class AddTimestampsToUsers < ActiveRecord::Migration
  def change
    add_timestamps(:users, null: false)
  end
end
almawhoob
źródło
1

Odpowiedzi podane wcześniej wydają się prawidłowe, jednak napotkałem problemy, jeśli moja tabela zawiera już wpisy.

Otrzymałbym „BŁĄD: kolumna created_atzawiera nullwartości”.

Aby naprawić, użyłem:

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

Następnie użyłem gem migracji_data, aby dodać czas dla bieżących projektów migracji, takich jak:

def data
  Project.update_all created_at: Time.now
end

Wtedy wszystkie projekty utworzone po tej migracji zostaną poprawnie zaktualizowane. Upewnij się, że serwer również został zrestartowany, aby Railsy ActiveRecordzaczęły śledzić sygnatury czasowe w rekordzie.

dbrody
źródło
1

Wiele odpowiedzi tutaj, ale swoją też opublikuję, ponieważ żadna z poprzednich naprawdę nie zadziałała :)

Jak niektórzy zauważyli, #add_timestampsniestety dodaje null: falseograniczenie, co spowoduje, że stare wiersze będą nieprawidłowe, ponieważ nie zawierają tych wartości. Większość odpowiedzi sugeruje, że ustawiliśmy jakąś domyślną wartość ( Time.zone.now), ale nie chciałbym tego robić, ponieważ te domyślne znaczniki czasu dla starych danych nie będą poprawne. Nie widzę wartości dodawania nieprawidłowych danych do tabeli.

Więc moja migracja była po prostu:

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

Nie null: false, żadnych innych ograniczeń. Stare wiersze będą nadal obowiązywały z created_atas NULLi update_atas NULL(do czasu wykonania aktualizacji wiersza). Nowe wiersze będą miały created_ati updated_atwypełnione zgodnie z oczekiwaniami.

Kostis
źródło
1

Problem z większością odpowiedzi tutaj polega na tym, że jeśli domyślnie Time.zone.nowwszystkie rekordy będą miały czas uruchomienia migracji jako domyślny czas, co prawdopodobnie nie jest tym, czego chcesz. W szynach 5 możesz zamiast tego użyć now(). Spowoduje to ustawienie sygnatur czasowych dla istniejących rekordów jako czasu uruchomienia migracji i jako czasu rozpoczęcia transakcji zatwierdzania dla nowo wstawianych rekordów.

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

jlesse
źródło
1

Używanie Time.currentto dobry styl 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

lub

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
źródło
1

To proste, aby dodać znacznik czasu do istniejącej tabeli.

class AddTimeStampToCustomFieldMeatadata < ActiveRecord::Migration
  def change
    add_timestamps :custom_field_metadata
  end
end
Dinesh Vaitage
źródło
0

Dla tych, którzy nie używają Railsów, ale używają activerecord, poniżej również dodaje się kolumnę do istniejącego modelu, na przykład dla pola liczb całkowitych.

ActiveRecord::Schema.define do
  change_table 'MYTABLE' do |table|
    add_column(:mytable, :my_field_name, :integer)
  end
end
Piotr
źródło
0

To changenie jest change_tabledla Railsów 4.2:

class AddTimestampsToUsers < ActiveRecord::Migration
  def change
    add_timestamps(:users)
  end
end
Igor T.
źródło
0

Wydaje się, że to czyste rozwiązanie w Railsach 5.0.7 (odkryłem metodę 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
źródło
0

Jestem na szynach 5.0 i żadna z tych opcji nie działała.

Jedyną rzeczą, która działała, było użycie typu: timestamp, a nie: datetime

def change
    add_column :users, :created_at, :timestamp
    add_column :users, :updated_at, :timestamp
end
Vishnu Narang
źródło
-1

Osobiście korzystałem z następujących i zaktualizowałem wszystkie poprzednie rekordy o aktualną godzinę / datę:

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
źródło
-2

Napotkałem ten sam problem na Rails 5, próbując użyć

change_table :my_table do |t|
    t.timestamps
end

Udało mi się ręcznie dodać kolumny sygnatur czasowych za pomocą następujących elementów:

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
źródło
Czy to nie zawsze ustawi wartość domyślną z czasem w momencie uruchomienia migracji? (więc nie jest to dynamiczny znacznik czasu obsługiwany przez DB)
Guillaume Petit
w przypadku rekordów, które już istnieją w Twojej bazie danych, tak, ustawia parametry created_at i updated_at na datę i godzinę uruchomienia migracji. Jednak bez wcześniejszego posiadania tych wartości, nie wiem, jak inaczej zainicjować te wartości. EDYCJA: To byłoby po prostu uważane za początek historii tego rzędu
Andres Rosales