将现有时间迁移到 Rails 中的 DateTime,同时保持数据完整

Migrating existing Time to DateTime in Rails while keeping data intact

请原谅我问了一个愚蠢的问题,但我 运行 在尝试将 time 列更新为 datetime 时遇到了一些麻烦。

我的 schema.rb 的相关部分如下所示:

  create_table "shop_hours", id: :serial, force: :cascade do |t|
    t.time "from_hours"
    t.time "to_hours"
    t.string "day"
    t.integer "repair_shop_id"
    t.boolean "is_shop_open"
    t.integer "chain_id"
    t.integer "regions", default: [], array: true
    t.index ["repair_shop_id"], name: "index_shop_hours_on_repair_shop_id"
  end

这是 运行dom ShopHour 对象的示例:

[67] pry(main)> ShopHour.find(439)
 #<ShopHour:0x00007ff05462d3a0
 id: 439,
 from_hours: Sat, 01 Jan 2000 15:00:00 UTC +00:00,
 to_hours: Sat, 01 Jan 2000 00:00:00 UTC +00:00,
 day: "Friday",
 repair_shop_id: 468,
 is_shop_open: true,
 chain_id: nil,
 regions: []>

最终,我想迁移所有 ShopHour 表上的属性 from_hoursto_hours,以便它们的类型为 datetime.

我还想更新每个 from_hoursto_hours 的日期。

我试过这个迁移,但是 运行 出错了:

class ChangeShopHoursToDateTime < ActiveRecord::Migration[6.0]
  def change
    change_column :shop_hours, :from_hours, 'timestamp USING CAST(from_hours AS timestamp)'
    change_column :shop_hours, :to_hours, 'timestamp USING CAST(to_hours AS timestamp)'
  end
end

这是我遇到的错误:

== 20201021083719 ChangeShopHoursToDateTime: migrating ========================
-- change_column(:shop_hours, :from_hours, "timestamp USING CAST(from_hours AS timestamp)")
rails aborted!
StandardError: An error has occurred, this and all later migrations canceled:

PG::CannotCoerce: ERROR:  cannot cast type time without time zone to timestamp without time zone
LINE 1: ...s" ALTER COLUMN "from_hours" TYPE timestamp USING CAST(from_...

如果我能提供更多信息,请告诉我。提前致谢!

您实际上无法自动将时间列转换为时间戳,因为时间没有日期部分。 Postgres 实际上正确地阻止了你这样做,因为结果会模棱两可——它应该真正将 12:45 转换为:

的日期
  • 公元前 0 年?
  • epoc时间的开始?
  • 今天几号?

Ruby 实际上没有 class 来表示没有日期部分的时间。主要区别在于 Time 是用 C 编写的简单包装器,它包装了 UNIX 时间戳,而 DateTime 在历史时间上更好。 Rails 只是将 time 数据库列转换为从 2000-01-01 开始的时间这一事实实际上只是一个奇怪但实用的问题解决方案,而不是创建类似 TimeWithoutDate 的东西class.

如果您想将数据库列从 time 迁移到 timestamp / timestampz,您需要告诉数据库您希望时间在哪个日期:

class AddDatetimeColumnsToHours < ActiveRecord::Migration[6.0]
  def up
    add_column :shop_hours, :opens_at, :datetime
    add_column :shop_hours, :closes_at, :datetime
    ShopHour.update_all(
      [ "closes_at = (timestamp '2000-01-01') + to_hour, opens_at = (timestamp '2000-01-01') + from_hour" ]
    )
  end

  def down
    remove_column :shop_hours, :opens_at
    remove_column :shop_hours, :closes_at
  end
end

这会添加两个新列,您真的应该考虑只删除现有列并使用此命名方案,因为以 to_ 开头的方法按照惯例是 Ruby 中的转换方法(例如to_sto_ato_h) - to_hour 因此是一个非常糟糕的名字。