125

Consider the table creation script below:

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

Is it's possible to set the default value as the current time?

I am trying to find a DB independent equivalent in rails for the SQL column definitions given below:

Oracle Syntax

start_at DATE DEFAULT SYSDATE() 

MySQL Syntax

start_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP

OR

start_at DATETIME DEFAULT NOW()
iconoclast
  • 21,213
  • 15
  • 102
  • 138
Harish Shetty
  • 64,083
  • 21
  • 152
  • 198

8 Answers8

197

This is supported now in Rails 5.

Here is a sample 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

See discussion at https://github.com/rails/rails/issues/27077 and answer there by prathamesh-sonpatki

Will
  • 2,768
  • 1
  • 17
  • 15
  • 20
    Great answer. Purely as an aside, be aware that in Postgres `CURRENT_TIMESTAMP` will be the time of the start of the current transaction, so multiple records created in the same transaction will get that same value. If you want the actual current time the statement executes (ignoring transaction context), check out `CLOCK_TIMESTAMP`. – Abe Voelker Oct 08 '17 at 21:16
  • I added something like this: `add_column :table_name, :start_date, :datetime, default: -> { 'CURRENT_TIMESTAMP' }` and this is what it loooks like in the schema.rb `t.datetime "start_date", default: -> { "now()" }` But when I created new record, it's not not being populated. Any idea why? – Sandip Subedi Jul 26 '19 at 13:08
  • @SandipSubedi same for me. On rails 5.2.3. – courtsimas Aug 05 '19 at 18:16
  • 1
    @courtsimas you need to do `post.reload` to get those values. It's explained here: https://stackoverflow.com/questions/53804787/rails-5-2-and-active-record-migration-with-current-timestamp – Sandip Subedi Aug 05 '19 at 18:32
  • 1
    @SandipSubedi I realized that 5 minutes after my comment. Just like with uuid generation. Thanks! – courtsimas Aug 05 '19 at 19:07
  • @SandipSubedi any idea why `default: -> { "now()" }` is printed on the schema instead of `default: -> { 'CURRENT_TIMESTAMP' }` ? – tambakoo Oct 02 '19 at 10:14
  • Haven't realized the difference. But on my `schema.rb` it get changed depending on who is running. And it hasn't been an issue for me. – Sandip Subedi Oct 02 '19 at 16:29
  • @SandipSubedi I'm facing a similar situation. Nothing is breaking, I suspect it's a postgres version thing. – tambakoo Oct 04 '19 at 06:13
123

You can add a function in a model like this:

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

So that the model will set the current time in the model.

You can also place some sql code in the migration for setting the default value at the database level, something like:

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

Setting something like this:

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

causes executing the Time.now function during migrating so then the table in database is created like this:

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

but I think that it is not what you want.

Szymon Lipiński
  • 27,098
  • 17
  • 75
  • 77
  • 1
    Currently I am setting the value in the :before_create callback.
    I was looking for some type of AR magic here. I spent some time looking at the Rails code, but I didn't find any solution. I thought I will ask around to see if there are any alternatives.
    – Harish Shetty Oct 17 '09 at 00:00
  • I would suggest doing it with a callback on before_create. – jonnii Oct 17 '09 at 00:01
  • 1
    I don't want to alter the DB table as I want to keep my code DB neutral. I was hoping that AR had some mechanism to set the default value for the Datetime field similar to created_at field. – Harish Shetty Oct 17 '09 at 00:09
  • Be careful with Time.now. Usually you want to use Time.zone.now. – Jeewes Feb 19 '14 at 19:01
  • 9
    Be aware that **before_create** callback runs before inserting to database but after you instantiate your object (like with `new()`). So `self.foo = Time.now` will override the value you might give to `new()`. I suggest `self.foo = Time.current unless self.foo.present?` instead. – Fatih Dec 10 '14 at 14:23
  • 2
    Not that, when using the execute, this will put a line `:starts_at, :default => 'now()'` in the schema.rb. However this won't work when using `rake db:schema:dump` which will override the schema.rb with `:starts_at, :default => '2015-05-29 09:46:33'` (or whatever the date when you launch the script)... sad.... – astreal May 29 '15 at 09:50
16

If you need to change an existing DateTime column in Rails 5 (rather than creating a new table as specified in other answers) so that it can take advantage of the default date capability, you can create a migration like this:

class MakeStartsAtDefaultDateForFoo < ActiveRecord::Migration[5.0]
  def change
    change_column :foos, :starts_at, :datetime, default: -> { 'CURRENT_TIMESTAMP' }
  end
end
Matt Long
  • 24,438
  • 4
  • 73
  • 99
  • 2
    Bad form to vote something down and not explain what issue was taken with the answer. Anybody have an idea why this was voted down? It's displaying the syntax if you wanted to change a column instead of creating one. – Matt Long Jan 12 '18 at 23:06
  • I'm not the one who downvoted, but I'm having difficulty seeing how this answer differs from will's answer that precedes yours by one year. The question is about setting a default, and your answer has the same lambda clause. – nurettin Mar 02 '18 at 16:45
  • 2
    @nurettin I get your point, but in my defense, the syntax for **creating** a new column is subtly different from **changing** an existing column. Providing that syntax for those who found this question via web search while trying to add a default to their current data model rather than creating a new model/table altogether is probably pretty helpful. No? You are assuming everybody knows they can use the same lambda for a change_column. Maybe they should realize that, but that's why I answered here--so they don't have to go anywhere else to figure that out. Cheers! – Matt Long Mar 02 '18 at 18:24
  • I understand why you did it, but it looks more like a comment than an answer is all I'm saying. No intention to annoy, just answering your question. – nurettin Mar 03 '18 at 19:40
  • @nurettin Probably better as a comment--except for the syntax highlighting ;-) Thanks for the feedback. I'll see if anybody else concurs with another down vote. – Matt Long Mar 05 '18 at 21:38
  • 5
    FWIW I just now used this syntax and appreciate that for my google search and particular problem this answer helped me the most. – Jay Killeen Mar 22 '18 at 00:43
  • 1
    I don't see anything wrong with this being an answer rather than a comment. Upvoted. I think the downvoters are just reading carelessly (like most of the question closers! ;p ). – iconoclast Jun 13 '18 at 20:15
  • Found this useful as was looking for migrating on existing model and found this. – DavidM Aug 01 '23 at 16:04
12

Active Record automatically timestamps create and update operations if the table has fields named created_at/created_on or updated_at/updated_on. Source - api.rubyonrails.org

You don't need to do anything else except to have that column.

alexandre-rousseau
  • 2,321
  • 26
  • 33
Jim
  • 5,557
  • 1
  • 20
  • 18
  • I already have those fields in my table. I need an additional field for my scheduler to hold the start date. Currently, I am using :before_create callback to set the current date. If I encounter this scenario frequently, I have to resort to writing a plugin to alter the default value handling in 'to_sql' method of ColumnDefinition class. – Harish Shetty Oct 17 '09 at 07:45
10

I was searching for a similar solutions but I ended using https://github.com/FooBarWidget/default_value_for.

The default_value_for plugin allows one to define default values for ActiveRecord models in a declarative manner. For example:

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
  • 4,597
  • 1
  • 30
  • 33
9

I usually do:

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

So, your schema.rb is going to have something like:

create_table "your_table", force: :cascade do |t|
  t.datetime "your_column", default: "now()"
end
Arturo Herrero
  • 12,772
  • 11
  • 42
  • 73
4

Did you know that upserts fail unless you have a default updated_at/created_at????

there is no migration flag which automatically does this, you have to manually include an options object with a default key

create_table :table_foos do |t|
  #...
  # date with timestamp
  t.datetime :last_something_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
  
  # standard timestamps
  t.timestamps({default: -> { "CURRENT_TIMESTAMP" }})
end
Blair Anderson
  • 19,463
  • 8
  • 77
  • 114
-1

In the answer given by @szymon-lipiński (Szymon Lipiński), the execute method didn't work for me. It was throwing a MySQL syntax error.

The MySQL syntax which worked for me is this.

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

So to set the default value for a datetime column in migration script can be done as follows:

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
  • 2,929
  • 2
  • 22
  • 29