将时间戳添加到现有表格

196

我需要在一个已有的表中加入时间戳(created_atupdated_at)。我尝试了下面的代码,但它没有起作用。

class AddTimestampsToUser < ActiveRecord::Migration
    def change_table
        add_timestamps(:users)
    end
end
24个回答

238

时间戳助手仅在create_table块中可用。您可以通过手动指定列类型来添加这些列:

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
尽管这种方法没有您上面指定的add_timestamps方法那样简洁,但Rails仍会将这些列视为时间戳列,并正常更新值。

11
在 Rails 4 中,这对我没有起作用。下面 "mu is too short" 提供的解决方案是可行的。 - newUserNameHere
24
rails g migration AddTimestampsToUser created_at:datetime updated_at:datetime是生成上述数据库迁移的快捷方式。 - Konstantine Kalbazov
5
运行该迁移会导致错误 PG::NotNullViolation: ERROR: column "created_at" contains null value,这是因为我的表已经包含了违反非空约束的数据。除了先删除非空约束再重新添加之外,是否有更好的方法来解决这个问题? - M. Habib
1
@M.Habib 我不这么认为,但是这个答案很好地将所有内容封装在一个迁移中。 - littleforest
1
@M.Habib 取决于您认为哪个默认值最合理,您可以执行 add_column :users, :updated_at, :datetime, null: false, default: Time.zone.nowTime.zone.now 只是一个示例,您应该使用适合您逻辑的任何值。 - Delong Gao
在Rails 6上它对我没用... 我使用了以下代码:def change change_table :users do |t| t.timestamps end end - Syph

96

迁移只是两个类方法(或者在 3.1 版本中是实例方法):updown(在 3.1 版本中有时还会有一个 change 实例方法)。您希望将更改放入 up 方法中:

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

如果您使用的是3.1版本,您也可以使用change(感谢Dave):
class AddTimestampsToUser < ActiveRecord::Migration
  def change
    change_table(:users) { |t| t.timestamps }
  end
end

也许你会混淆def changedef change_tablechange_table。请参考迁移指南了解更多细节。

1
好的,现在有change方法了,虽然在这种情况下不是问题 :) - Dave Newton
@Dave:说得没错,我选择了通用性以避免版本问题,但change也值得一提,所以我会加上它。 - mu is too short
我知道这个“down”方法在3.1版本后就要改变了,Rails会自动解析它。你听说过这件事吗? - Michael Durrant
@Michael:我一直在使用MongoDB来开发我正在进行的3.1应用程序,所以我没有使用过3.1 AR迁移。文档表明,一切都朝着实例方法的方向发展(原因未知)。 - mu is too short
@MichaelDurrant,目前有许多“更改”无法涵盖的情况,如果上/下消失了,会有一些生气的人 :) (在您的更改迁移中添加一个“除非”条款以避免迁移冲突,并尝试回滚...)即使在您发表此评论3年后,我认为它也没有改变。 :) - frandroid
@RichPeck:哇,你是怎么在那么多灰尘中找到这段代码的? - mu is too short

87

@user1899434的回答提到,“现有”表在这里可能意味着已经存在记录的表,你可能不想删除这些记录。因此,当你添加null: false时间戳时(这是默认值,通常是理想的),这些现有记录都无效了。

但我认为可以通过将这两个步骤合并为一个迁移,并使用更语义化的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
你可以用其他时间戳来替换 DateTime.now,比如说如果你想让已经存在的记录被创建/更新为初始时间。

2
太棒了。谢谢!只有一个注意点 - 如果我们想让我们的代码遵守正确的时区,应该使用 Time.zone.now - John Gallagher
7
将默认值设置为 Time.zone.now 存在问题,因为它会返回在运行迁移时创建的时间实例,并将该时间作为默认值。新对象不会获得新的时间实例。 - Tovi Newman
这个答案应该在顶部! - Shankar Thyagarajan

83

你的原始代码非常接近正确,你只需要使用不同的方法名称。如果你正在使用Rails 3.1或更高版本,则需要定义一个change方法而不是change_table

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

如果您使用的是旧版本,则需要定义updown方法,而不是change_table方法:

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

  def down
    remove_timestamps(:users)
  end
end

1
那应该是被接受的答案。 - gogaz

40
class AddTimestampsToUser < ActiveRecord::Migration
  def change
    change_table :users do |t|
      t.timestamps
    end
  end
end

可用的转换包括

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


14

Nick Davies答案在为已有数据的表添加时间戳列方面是最完整的。唯一的缺点是它会在db:rollback上引发ActiveRecord::IrreversibleMigration

应该按照以下方式进行修改,以在两个方向上都能工作:

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

这段代码在我的Rails 4.2.7上并没有按照原本的预期工作(我认为change_column_default在那个版本中不支持fromto参数?),但是我借鉴了这个思路,创建了up/down方法来代替单一的change方法,结果非常完美! - Gar

11

使用 Time.current 是一个良好的风格。参考 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

或者

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

这在Rails 6中对我有效! - Sprachprofi
这在Rails 6中对我起作用了! - Sprachprofi

10
大多数答案的问题在于,如果您默认使用Time.zone.now,则所有记录的默认时间将是运行迁移时的时间,这可能不是您想要的。在rails 5中,您可以改用now()。这将设置现有记录的时间戳为运行迁移的时间,并将新插入记录的开始时间作为提交事务的开始时间。
``` class AddTimestampsToUsers < ActiveRecord::Migration def change add_timestamps :users, default: -> { 'now()' }, null: false end end ```

1
您可能不希望现有对象的时间戳设置为迁移运行时的时间。这将导致数据/分析结果具有误导性。如果没有其他来源提供数据,则将其保留为空值是更好的选择。 - bigtex777
@bigtex777 你说得有道理。我认为设置现有记录的时间戳取决于您的需求。如果将它们保留为空,可能会导致在其上添加约束时出现问题。将它们设置为过去很久以前的日期也可能是有意义的,以表示它们参与了此迁移。理想情况下,您不应该在表创建后再向表中添加时间戳。 - jlesse

9
def change
  add_timestamps :table_name
end

1
PG::NotNullViolation: ERROR: column "created_at" contains null values - MIA

8

我正在使用 Rails 5.0,但这些选项都没有生效。

唯一有效的方法是将类型设置为 :timestamp 而不是 :datetime

def change
    add_column :users, :created_at, :timestamp
    add_column :users, :updated_at, :timestamp
end

这就是我需要的答案。当我将datetime指定为类型时,Rspec测试失败了(它们无法默认设置created_at)。 - BelgoCanadian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接