Como definir um valor padrão para uma coluna datetime para registrar o tempo de criação em uma migração?

114

Considere o script de criação de tabela abaixo:

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

É possível definir o valor padrão como a hora atual?

Estou tentando encontrar um equivalente independente de banco de dados em trilhos para as definições de coluna SQL fornecidas abaixo:

Sintaxe Oracle

start_at DATE DEFAULT SYSDATE() 

Sintaxe MySQL

start_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP

OU

start_at DATETIME DEFAULT NOW()
Harish Shetty
fonte

Respostas:

174

Isso é suportado agora no Rails 5.

Aqui está um exemplo de migração:

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

Veja a discussão em https://github.com/rails/rails/issues/27077 e responda lá por prathamesh-sonpatki

Vai
fonte
14
Ótima resposta. Apenas como um aparte, esteja ciente de que no Postgres CURRENT_TIMESTAMPserá a hora do início da transação atual, então vários registros criados na mesma transação obterão o mesmo valor. Se você deseja a hora atual real em que a instrução é executada (ignorando o contexto da transação), faça check-out CLOCK_TIMESTAMP.
Abe Voelker,
Eu adicionei algo como isto: add_column :table_name, :start_date, :datetime, default: -> { 'CURRENT_TIMESTAMP' }e é assim que parece no schema.rb t.datetime "start_date", default: -> { "now()" }Mas quando eu criei um novo registro, ele não estava sendo preenchido. Alguma ideia do porquê?
Sandip Subedi
@SandipSubedi o mesmo para mim. Sobre trilhos 5.2.3.
courtimas
1
@courtsimas você precisa fazer post.reloadpara obter esses valores. É explicado aqui: stackoverflow.com/questions/53804787/…
Sandip Subedi
1
@SandipSubedi Percebi isso 5 minutos depois do meu comentário. Assim como com a geração uuid. Obrigado!
courtimas
119

Você pode adicionar uma função em um modelo como este:

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

Para que o modelo defina a hora atual no modelo.

Você também pode colocar algum código sql na migração para definir o valor padrão no nível do banco de dados, algo como:

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

Definindo algo assim:

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

faz com que a função Time.now seja executada durante a migração, então a tabela no banco de dados é criada assim:

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

mas acho que não é o que você quer.

Szymon Lipiński
fonte
1
Atualmente estou definindo o valor no retorno de chamada: before_create. <br> Eu estava procurando algum tipo de magia AR aqui. Passei algum tempo olhando o código do Rails, mas não achei nenhuma solução. Pensei em perguntar por aí para ver se há alternativas.
Harish Shetty,
Eu sugeriria fazer isso com um retorno de chamada em before_create.
Jonnii
1
Não quero alterar a tabela do banco de dados porque desejo manter meu código de banco de dados neutro. Eu esperava que o AR tivesse algum mecanismo para definir o valor padrão para o campo Datetime semelhante ao campo created_at.
Harish Shetty,
9
Esteja ciente de que o retorno de chamada before_create é executado antes de inserir no banco de dados, mas depois de instanciar seu objeto (como com new()). Portanto self.foo = Time.now, substituirá o valor que você pode atribuir new(). Eu sugiro em self.foo = Time.current unless self.foo.present?vez disso.
Fatih
2
Não que, ao usar o execute, isso coloque uma linha :starts_at, :default => 'now()'no schema.rb. No entanto, isso não funcionará ao usar o rake db:schema:dumpque substituirá o schema.rb com :starts_at, :default => '2015-05-29 09:46:33'(ou qualquer que seja a data de quando você iniciar o script) ... triste ....
astreal
13

O Active Record marca a data e hora automaticamente para criar e atualizar as operações se a tabela tiver campos nomeados created_at/ created_onou updated_at/ updated_on. Fonte - api.rubyonrails.org

Você não precisa fazer mais nada, exceto ter essa coluna.

Jim
fonte
Já tenho esses campos na minha tabela. Preciso de um campo adicional para meu agendador para conter a data de início. Atualmente, estou usando: before_create callback para definir a data atual. Se eu encontrar esse cenário com frequência, terei que recorrer a escrever um plugin para alterar o tratamento de valor padrão no método 'to_sql' da classe ColumnDefinition.
Harish Shetty,
9

Eu estava procurando uma solução semelhante, mas acabei usando https://github.com/FooBarWidget/default_value_for .

O default_value_forplugin permite definir valores padrão para modelos ActiveRecord de forma declarativa. Por exemplo:

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

Eu costumo fazer:

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

Então, você schema.rbterá algo como:

create_table "your_table", force: :cascade do |t|
  t.datetime "your_column", default: "now()"
end
Arturo Herrero
fonte
A desvantagem: esta solução só funciona quando você executa rake db:migrate, não quando carrega seu arquivo de esquema com algo semelhante rake db:schema:load.
Alter Lagos
8

Se você precisar alterar uma coluna DateTime existente no Rails 5 (ao invés de criar uma nova tabela conforme especificado em outras respostas) para que ela possa tirar proveito do recurso de data padrão, você pode criar uma migração como esta:

class MakeStartsAtDefaultDateForFoo < ActiveRecord::Migration[5.0]
  def change
    change_column :foos, :starts_at, :datetime, default: -> { 'CURRENT_TIMESTAMP' }
  end
end
Matt Long
fonte
Má forma de votar em algo negativo e não explicar qual foi o problema com a resposta. Alguém tem uma ideia de por que isso foi rejeitado? Ele está exibindo a sintaxe se você quiser alterar uma coluna em vez de criar uma.
Matt Long
Não fui eu que votei negativamente, mas estou tendo dificuldade em ver como essa resposta difere da resposta de testamento que precede a sua em um ano. A questão é sobre como definir um padrão e sua resposta tem a mesma cláusula lambda.
nurettin
2
@nurettin Eu entendi seu ponto, mas em minha defesa, a sintaxe para criar uma nova coluna é sutilmente diferente de alterar uma coluna existente. Fornecer essa sintaxe para aqueles que encontraram essa questão por meio de pesquisa na web enquanto tentam adicionar um padrão ao modelo de dados atual em vez de criar um novo modelo / tabela provavelmente é muito útil. Não? Você está assumindo que todos sabem que podem usar o mesmo lambda para um change_column. Talvez eles devessem perceber isso, mas é por isso que respondi aqui - então eles não precisam ir a nenhum outro lugar para descobrir isso. Felicidades!
Matt Long,
4
FWIW Acabei de usar essa sintaxe e agradeço que, para minha pesquisa no Google e meu problema específico, essa resposta tenha me ajudado muito.
Jay Killeen
1
Não vejo nada de errado em ser uma resposta em vez de um comentário. Votos positivos. Acho que os downvoters estão apenas lendo descuidadamente (como a maioria dos fechamentos de perguntas!; P).
iconoclasta
-1

Na resposta dada por @ szymon-lipiński (Szymon Lipiński), o método de execução não funcionou para mim. Estava gerando um erro de sintaxe do MySQL.

A sintaxe do MySQL que funcionou para mim é esta.

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

Portanto, definir o valor padrão para uma coluna datetime no script de migração pode ser feito da seguinte maneira:

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
fonte