【问题标题】:Add timestamps to existing table in db Rails 5+在 db Rails 5+ 中向现有表添加时间戳
【发布时间】:2017-10-02 07:10:55
【问题描述】:

尝试将时间戳添加到现有表。 根据Api documenation 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*

我也尝试了thread中的所有解决方案

同样的错误... 导轨 5.1.4 Ruby 2.4.0

【问题讨论】:

  • 实际上你可以在不传递options的情况下添加列,这就是Rails 5中add_timestamps所做的,null: false是默认值。如果我错了,请纠正我。
  • 我也尝试过不通过选项并得到同样的错误

标签: ruby-on-rails ruby migration


【解决方案1】:

您不能将具有非空约束的列添加到非空表中,因为表中的现有行将立即具有空值,因此条件失败。

改为分三步引入列:

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

【讨论】:

  • 在迁移中使用 ActiveRecord 模型存在风险——例如,如果您稍后重命名 Product,这将失败。最好只使用 SQL:update "UPDATE products SET created_at = NOW(), updated_at = NOW()",这将始终有效。
【解决方案2】:

在我看来,在迁移中使用 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。正如here 解释的那样,该命令是执行回滚然后再次迁移备份的快捷方式。

    现在您将看到您的架构已更新为相应的 created_atupdated_at 列。

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

    【讨论】:

      【解决方案4】:

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

      唯一有效的是

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

      【讨论】:

        【解决方案5】:

        我有同样的问题。我希望最终结果严格等同于新数据库上的add_timestamps :products

        我最终没有运行查询来回填,而是执行了 3 个步骤。

        • 允许添加空列并默认为当前时间回填
        • 将约束更改为不为空
        • 删除默认值

        而且它是可逆的。

            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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-11-24
          • 1970-01-01
          • 2017-09-20
          • 2012-01-05
          • 2019-10-27
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多