Jak ustawić domyślną wartość kolumny typu data i godzina, aby rejestrować czas tworzenia podczas migracji?

114

Rozważ poniższy skrypt tworzenia tabeli:

create_table :foo do |t|
  t.datetime :starts_at, :null => false
end

Czy można ustawić domyślną wartość jako aktualny czas?

Próbuję znaleźć niezależny odpowiednik DB w szynach dla definicji kolumn SQL podanych poniżej:

Składnia Oracle

start_at DATE DEFAULT SYSDATE() 

Składnia MySQL

start_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP

LUB

start_at DATETIME DEFAULT NOW()
Harish Shetty
źródło

Odpowiedzi:

174

Jest to teraz obsługiwane w Railsach 5.

Oto przykładowa migracja:

class CreatePosts < ActiveRecord::Migration[5.0]
  def change
    create_table :posts do |t|
      t.datetime :modified_at, default: -> { 'CURRENT_TIMESTAMP' }
      t.timestamps
    end
  end 
end

Zobacz dyskusję na https://github.com/rails/rails/issues/27077 i odpowiedz tam przez prathamesh-sonpatki

Będzie
źródło
14
Świetna odpowiedź. Na marginesie, pamiętaj, że w Postgres CURRENT_TIMESTAMPbędzie to czas rozpoczęcia bieżącej transakcji, więc wiele rekordów utworzonych w tej samej transakcji otrzyma tę samą wartość. Jeśli chcesz mieć aktualny czas wykonania instrukcji (ignorując kontekst transakcji), sprawdź CLOCK_TIMESTAMP.
Abe Voelker
Dodałem coś takiego: add_column :table_name, :start_date, :datetime, default: -> { 'CURRENT_TIMESTAMP' }i tak wygląda w schema.rb t.datetime "start_date", default: -> { "now()" }Ale kiedy utworzyłem nowy rekord, nie jest on zapełniany. Każdy pomysł, dlaczego?
Sandip Subedi
@SandipSubedi to samo dla mnie. Na szynach 5.2.3.
courtimas
1
@courtsimas, które musisz zrobić, post.reloadaby uzyskać te wartości. Wyjaśniono to tutaj: stackoverflow.com/questions/53804787/…
Sandip Subedi
1
@SandipSubedi Zrozumiałem, że 5 minut po moim komentarzu. Podobnie jak w przypadku generowania uuidów. Dzięki!
courtimas
119

Możesz dodać funkcję w modelu takim jak ten:

  before_create :set_foo_to_now
  def set_foo_to_now
    self.foo = Time.now
  end

Aby model ustawił aktualny czas w modelu.

Możesz również umieścić w migracji kod sql, aby ustawić wartość domyślną na poziomie bazy danych, na przykład:

execute 'alter table foo alter column starts_at set default now()'

Ustawienie czegoś takiego:

create_table :foo do |t|
  t.datetime :starts_at, :null => false, :default => Time.now
end

powoduje wykonanie funkcji Time.now podczas migracji, więc tabela w bazie danych jest tworzona w następujący sposób:

create table foo ( starts_at timestamp not null default '2009-01-01 00:00:00');

ale myślę, że nie tego chcesz.

Szymon Lipiński
źródło
1
Obecnie ustawiam wartość w wywołaniu zwrotnym: before_create. <br> Szukałem tutaj jakiegoś rodzaju magii AR. Spędziłem trochę czasu przyglądając się kodowi Railsów, ale nie znalazłem żadnego rozwiązania. Pomyślałem, że zapytam, czy są jakieś alternatywy.
Harish Shetty
Sugerowałbym zrobienie tego z wywołaniem zwrotnym na before_create.
jonnii
1
Nie chcę zmieniać tabeli DB, ponieważ chcę, aby mój kod był neutralny. Miałem nadzieję, że AR ma jakiś mechanizm do ustawiania domyślnej wartości dla pola Datetime podobnego do pola created_at.
Harish Shetty
9
Należy pamiętać, że wywołanie zwrotne before_create jest uruchamiane przed wstawieniem do bazy danych, ale po utworzeniu instancji obiektu (np new(). With ). Więc self.foo = Time.nowzastąpi wartość, której możesz nadać new(). Proponuję self.foo = Time.current unless self.foo.present?zamiast tego.
Fatih
2
Nie to, że podczas korzystania z execute, spowoduje to umieszczenie linii :starts_at, :default => 'now()'w schema.rb. Jednak to nie zadziała, gdy użyjesz, rake db:schema:dumpktóry zastąpi schema.rb :starts_at, :default => '2015-05-29 09:46:33'(lub inną datą uruchomienia skryptu) ... smutno ...
astreal
13

Moduł Active Record automatycznie tworzy i aktualizuje znaczniki czasu operacji, jeśli tabela zawiera pola o nazwie created_at/ created_onlub updated_at/ updated_on. Źródło - api.rubyonrails.org

Nie musisz robić nic więcej poza posiadaniem tej kolumny.

Jim
źródło
Mam już te pola w mojej tabeli. Potrzebuję dodatkowego pola, aby mój harmonogram mógł przechowywać datę rozpoczęcia. Obecnie używam: before_create callback do ustawienia aktualnej daty. Jeśli często napotykam ten scenariusz, muszę uciec się do napisania wtyczki, aby zmienić domyślną obsługę wartości w metodzie „to_sql” klasy ColumnDefinition.
Harish Shetty
10

Zwyklę robię:

def change
  execute("
    ALTER TABLE your_table
    ALTER COLUMN your_column
    SET DEFAULT CURRENT_TIMESTAMP
  ")
end

Więc twój schema.rbbędzie miał coś takiego:

create_table "your_table", force: :cascade do |t|
  t.datetime "your_column", default: "now()"
end
Arturo Herrero
źródło
Wada: to rozwiązanie działa tylko po uruchomieniu rake db:migrate, a nie po załadowaniu pliku schematu z czymś w rodzaju rake db:schema:load.
Alter Lagos
9

Szukałem podobnych rozwiązań, ale skończyłem na https://github.com/FooBarWidget/default_value_for .

default_value_forWtyczka pozwala zdefiniować domyślne wartości dla modeli ActiveRecord w sposób deklaratywny. Na przykład:

class User < ActiveRecord::Base
  default_value_for :name, "(no name)"
  default_value_for :last_seen do
    Time.now
  end
end

u = User.new
u.name       # => "(no name)"
u.last_seen  # => Mon Sep 22 17:28:38 +0200 2008
Giovanni Cappellotto
źródło
8

Jeśli trzeba zmienić to istniejące kolumny DateTime w Rails 5 (zamiast tworzenia nowej tabeli, jak określono w innych odpowiedzi) tak, że może skorzystać z możliwości domyślna data, można utworzyć migrację takiego:

class MakeStartsAtDefaultDateForFoo < ActiveRecord::Migration[5.0]
  def change
    change_column :foos, :starts_at, :datetime, default: -> { 'CURRENT_TIMESTAMP' }
  end
end
Matt Long
źródło
Zły formularz, aby głosować na coś negatywnego i nie wyjaśniać, jaki problem został wzięty z odpowiedzią. Czy ktoś ma pomysł, dlaczego ten głos został odrzucony? Wyświetla składnię, jeśli chcesz zmienić kolumnę zamiast ją tworzyć.
Matt Long
Nie jestem tym, który przegłosował, ale mam trudności ze zrozumieniem, jak ta odpowiedź różni się od odpowiedzi testamentu, która poprzedza Twoją o rok. Pytanie dotyczy ustawienia wartości domyślnej, a Twoja odpowiedź zawiera taką samą klauzulę lambda.
nurettin
2
@nurettin Rozumiem, ale na moją obronę składnia tworzenia nowej kolumny jest nieco inna niż zmiana istniejącej kolumny. Zapewnienie takiej składni dla tych, którzy znaleźli to pytanie poprzez wyszukiwanie w Internecie, próbując dodać wartość domyślną do swojego obecnego modelu danych, zamiast tworzyć nowy model / tabelę, jest prawdopodobnie całkiem pomocne. Nie? Zakładasz, że wszyscy wiedzą, że mogą użyć tej samej lambdy dla kolumny change_column. Może powinni zdawać sobie z tego sprawę, ale dlatego odpowiedziałem tutaj - więc nie muszą nigdzie indziej, aby to zrozumieć. Twoje zdrowie!
Matt Long
4
FWIW Właśnie użyłem tej składni i doceniam to, że w przypadku mojej wyszukiwarki Google i konkretnego problemu ta odpowiedź pomogła mi najbardziej.
Jay Killeen
1
Nie widzę nic złego w tym, że jest to raczej odpowiedź niż komentarz. Głosowano za. Myślę, że ci, którzy przegrywają, po prostu czytają niedbale (jak większość zamykających pytania!; P).
ikonoklast
-1

W odpowiedzi udzielonej przez @ szymon-lipiński (Szymon Lipiński) metoda execute nie zadziałała. Zgłaszał błąd składni MySQL.

Oto składnia MySQL, która działała dla mnie.

execute "ALTER TABLE mytable CHANGE `column_name` `column_name` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP"

Tak więc ustawienie domyślnej wartości kolumny z datą i godziną w skrypcie migracji można wykonać w następujący sposób:

def up
  create_table :foo do |t|
    t.datetime :starts_at, :null => false
  end

  execute "ALTER TABLE `foo` CHANGE `starts_at` `starts_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP"
end
Sony Mathew
źródło