Comment définir une valeur par défaut pour une colonne datetime pour enregistrer l'heure de création dans une migration?

114

Considérez le script de création de table ci-dessous:

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

Est-il possible de définir la valeur par défaut comme heure actuelle?

J'essaie de trouver un équivalent indépendant de la base de données dans les rails pour les définitions de colonne SQL données ci-dessous:

Syntaxe d'Oracle

start_at DATE DEFAULT SYSDATE() 

Syntaxe MySQL

start_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP

OU

start_at DATETIME DEFAULT NOW()
Harish Shetty
la source

Réponses:

174

Ceci est désormais pris en charge dans Rails 5.

Voici un exemple de migration:

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

Voir la discussion sur https://github.com/rails/rails/issues/27077 et y répondre par prathamesh-sonpatki

Volonté
la source
14
Très bonne réponse. À titre informatif, sachez que dans Postgres CURRENT_TIMESTAMPsera l'heure du début de la transaction en cours, de sorte que plusieurs enregistrements créés dans la même transaction auront la même valeur. Si vous voulez l'heure actuelle d'exécution de l'instruction (en ignorant le contexte de transaction), vérifiez CLOCK_TIMESTAMP.
Abe Voelker
J'ai ajouté quelque chose comme ceci: add_column :table_name, :start_date, :datetime, default: -> { 'CURRENT_TIMESTAMP' }et c'est à quoi ça ressemble dans le schema.rb t.datetime "start_date", default: -> { "now()" }Mais quand j'ai créé un nouvel enregistrement, il n'est pas rempli . Une idée pourquoi?
Sandip Subedi
@SandipSubedi pareil pour moi. Sur rails 5.2.3.
courtsimas
1
@courtsimas que vous devez faire post.reloadpour obtenir ces valeurs. C'est expliqué ici: stackoverflow.com/questions/53804787/…
Sandip Subedi
1
@SandipSubedi je m'en suis rendu compte 5 minutes après mon commentaire. Tout comme avec la génération uuid. Merci!
courtsimas
119

Vous pouvez ajouter une fonction dans un modèle comme celui-ci:

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

Pour que le modèle définisse l'heure actuelle dans le modèle.

Vous pouvez également placer du code SQL dans la migration pour définir la valeur par défaut au niveau de la base de données, quelque chose comme:

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

Définir quelque chose comme ceci:

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

provoque l'exécution de la fonction Time.now pendant la migration, de sorte que la table de la base de données est créée comme ceci:

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

mais je pense que ce n'est pas ce que tu veux.

Szymon Lipiński
la source
1
Actuellement, je règle la valeur dans le rappel: before_create. <br> Je cherchais un type de magie AR ici. J'ai passé du temps à regarder le code Rails, mais je n'ai trouvé aucune solution. J'ai pensé que je demanderais aux alentours de voir s'il y avait des alternatives.
Harish Shetty
Je suggérerais de le faire avec un rappel sur before_create.
jonnii
1
Je ne veux pas modifier la table DB car je veux garder mon code DB neutre. J'espérais que AR disposait d'un mécanisme pour définir la valeur par défaut du champ Datetime similaire au champ created_at.
Harish Shetty
9
Sachez que le rappel before_create s'exécute avant l'insertion dans la base de données mais après avoir instancié votre objet (comme avec new()). Donc self.foo = Time.now, remplacera la valeur que vous pourriez donner new(). Je suggère à la self.foo = Time.current unless self.foo.present?place.
Fatih
2
Non pas que, lors de l'utilisation de l'exécution, cela mettra une ligne :starts_at, :default => 'now()'dans le schema.rb. Cependant, cela ne fonctionnera pas lors de l'utilisation rake db:schema:dumpqui remplacera le schéma.rb avec :starts_at, :default => '2015-05-29 09:46:33'(ou quelle que soit la date à laquelle vous lancez le script) ... triste ....
astreal
13

Les horodatages Active Record créent et mettent à jour automatiquement des opérations si la table contient des champs nommés created_at/ created_onou updated_at/ updated_on. La source - api.rubyonrails.org

Vous n'avez rien d'autre à faire que d'avoir cette colonne.

Jim
la source
J'ai déjà ces champs dans ma table. J'ai besoin d'un champ supplémentaire pour que mon planificateur contienne la date de début. Actuellement, j'utilise: before_create callback pour définir la date actuelle. Si je rencontre fréquemment ce scénario, je dois recourir à l'écriture d'un plugin pour modifier la gestion des valeurs par défaut dans la méthode 'to_sql' de la classe ColumnDefinition.
Harish Shetty
10

Je fais habituellement:

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

Donc, vous schema.rballez avoir quelque chose comme:

create_table "your_table", force: :cascade do |t|
  t.datetime "your_column", default: "now()"
end
Arturo Herrero
la source
L'inconvénient: cette solution ne fonctionne que lorsque vous exécutez rake db:migrate, pas lorsque vous chargez votre fichier de schéma avec quelque chose comme rake db:schema:load.
Alter Lagos le
9

Je cherchais des solutions similaires mais j'ai fini par utiliser https://github.com/FooBarWidget/default_value_for .

Le default_value_forplugin permet de définir des valeurs par défaut pour les modèles ActiveRecord de manière déclarative. Par exemple:

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
la source
8

Si vous devez modifier une colonne DateTime existante dans Rails 5 (plutôt que de créer une nouvelle table comme spécifié dans d'autres réponses) afin qu'elle puisse profiter de la fonctionnalité de date par défaut, vous pouvez créer une migration comme celle-ci:

class MakeStartsAtDefaultDateForFoo < ActiveRecord::Migration[5.0]
  def change
    change_column :foos, :starts_at, :datetime, default: -> { 'CURRENT_TIMESTAMP' }
  end
end
Matt Long
la source
Mauvaise forme pour voter contre quelque chose et ne pas expliquer quel problème a été soulevé avec la réponse Quelqu'un a-t-il une idée de la raison pour laquelle cela a été rejeté? Il affiche la syntaxe si vous souhaitez modifier une colonne au lieu d'en créer une.
Matt Long
Ce n'est pas moi qui ai voté contre, mais j'ai du mal à voir en quoi cette réponse diffère de la réponse de Will qui précède la vôtre d'un an. La question concerne la définition d'une valeur par défaut, et votre réponse a la même clause lambda.
nurettin
2
@nurettin Je comprends votre point de vue, mais pour ma défense, la syntaxe pour créer une nouvelle colonne est subtilement différente de la modification d' une colonne existante. Fournir cette syntaxe à ceux qui ont trouvé cette question via une recherche sur le Web tout en essayant d'ajouter une valeur par défaut à leur modèle de données actuel plutôt que de créer un nouveau modèle / tableau est probablement très utile. Non? Vous supposez que tout le monde sait qu'il peut utiliser le même lambda pour un change_column. Peut-être qu'ils devraient s'en rendre compte, mais c'est pourquoi j'ai répondu ici - pour qu'ils n'aient pas à aller ailleurs pour comprendre cela. À votre santé!
Matt Long
4
FWIW Je viens d'utiliser cette syntaxe et j'apprécie le fait que pour ma recherche Google et mon problème particulier, cette réponse m'a le plus aidé.
Jay Killeen
1
Je ne vois rien de mal à ce que ce soit une réponse plutôt qu'un commentaire. J'ai voté pour. Je pense que les downvoters ne lisent que négligemment (comme la plupart des questionnaires!; P).
iconoclaste
-1

Dans la réponse donnée par @ szymon-lipiński (Szymon Lipiński), la méthode d'exécution n'a pas fonctionné pour moi. Il lançait une erreur de syntaxe MySQL.

La syntaxe MySQL qui a fonctionné pour moi est la suivante.

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

Ainsi, pour définir la valeur par défaut d'une colonne datetime dans le script de migration, vous pouvez procéder comme suit:

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
la source