在Rails 5+中为现有的数据库表添加时间戳。

20
尝试向现有表添加时间戳。 根据Api文档add_timestamps
以下是我的迁移代码:
  def change
    add_timestamps(:products, null: false)
  end

返回的翻译文本为:

出现错误:

*-- add_timestamps(:products, {:null=>false})
rails aborted!
StandardError: An error has occurred, this and all later migrations canceled:
SQLite3::SQLException: Cannot add a NOT NULL column with default value NULL: ALTER TABLE "products" ADD "created_at" datetime NOT NULL*

我也尝试了这个线程中的所有解决方案

同样的错误... Rails 5.1.4 Ruby 2.4.0


实际上,您可以在不传递“选项”的情况下添加列,这就是Rails 5中的add_timestamps所做的,“null:false”是默认值。如果我错了,请纠正我。 - Sebastián Palma
我也尝试过不传递选项,但是出现了相同的错误。 - D.K.S
6个回答

42

如果现有表格不为空,则无法向具有非空约束的列添加新列,因为表格中的现有行将立即具有空值,从而导致条件不满足。

相反,您应该采取以下三个步骤引入新列:

def change
  # add new column but allow null values
  add_timestamps :products, null: true 

  # backfill existing records with created_at and updated_at
  # values that make clear that the records are faked
  long_ago = DateTime.new(2000, 1, 1)
  Product.update_all(created_at: long_ago, updated_at: long_ago)

  # change to not null constraints
  change_column_null :products, :created_at, false
  change_column_null :products, :updated_at, false
end

3
在迁移中使用ActiveRecord模型是有风险的,比如如果你以后重命名了“Product”,那么迁移就会失败。更好的方式是仅使用SQL语句:update "UPDATE products SET created_at = NOW(), updated_at = NOW()" 这种方法总是可行的。 - Meekohi

37

在我看来,在迁移中使用activerecord查询或者甚至是SQL来操作现有的数据是错误的。

正确的Rails 5.2+方式是:

class AddTimestampsToCars < ActiveRecord::Migration[5.2]
  def change
    add_timestamps :cars, null: false, default: -> { 'NOW()' }
  end
end

这是一个过程,因此如果需要,您可以将日期设置为过去。

来源:https://github.com/rails/rails/pull/20005


3
我喜欢@spickermann的方法,因为它考虑到了现有记录,并且可能已经迁移到生产环境,他的方法确保数据持久性。
然而,你们中的许多人可能会发现自己处于这种情况中,但仍然处于开发阶段,这意味着没有真正的敏感数据可能会丢失...这使你在如何更改表格时有一点更多的自由。
如果你的代码和记录仅存在本地(如果你仍然没有创建记录,请跳过第1步),并且该表格是在最后一次迁移中创建的,我的建议是:
1.删除该表格中的所有记录。
2.转到迁移文件并通过添加t.timestamps来编辑它,使其看起来像这样:
    class CreateInstitutionalLegals < ActiveRecord::Migration[5.0]
      def change
        create_table :institutional_legals do |t|
          # Your original migration content goes here
          .
          .
          t.timestamps # This is your addition
        end
      end
    end

3.- 接下来,进入控制台并输入rails:db:redo。如此处所解释的那样,该命令是进行回滚然后再次迁移的快捷方式。

现在您会发现您的模式已经更新了相应的created_atupdated_at列。

这样做的具体好处是非常容易,不需要创建额外的迁移文件,并且您学会了使用一个非常方便的命令;)


1

我遇到了同样的问题。我希望最终结果与在新数据库上运行add_timestamps :products严格等效。

我没有运行补偿查询,而是采取了三步骤的过程。

  • 添加允许空值并设置默认为当前时间的列以进行补偿
  • 更改约束条件为不为空
  • 删除默认值

这是可逆的。

    add_column :products, :created_at, :datetime, precision: 6, null: true, default: -> { "CURRENT_TIMESTAMP" }
    add_column :products, :updated_at, :datetime, precision: 6, null: true, default: -> { "CURRENT_TIMESTAMP" }

    change_column_null :products, :created_at, false
    change_column_null :products, :updated_at, false

    change_column_default :products, :created_at, from:  -> { "CURRENT_TIMESTAMP" }, to: nil
    change_column_default :products, :updated_at, from:  -> { "CURRENT_TIMESTAMP" }, to: nil

注意:这是使用Rails 6.1和PostgreSQL


0

我正在使用Rails 5.0,但这些选项都没有起作用。 rails:db:redo 可以工作,但对于大多数人来说并不是可行的解决方案。

唯一有效的方法是:

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

0

我和@jeremiemv做的类似,只是少了几个步骤。如果在添加带有默认值的时间戳后不更改列默认值,则默认值将显示为迁移运行时的时间。

我的解决方案的区别还在于,当您提供默认值时,无需管理null:true然后false

add_timestamps :products, null: false, default: Time.current
change_column_default :products, :created_at, nil
change_column_default :products, :updated_at, nil

这里是Rails 7和MySQL


请注意,此迁移是不可逆的。change_column_default 应该具有 fromto 选项。 - Al17

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