如何在Postgres中更改rails迁移t.timestamps以使用不带时区的timestamp(0)

时间:2018-10-23 16:33:01

标签: ruby-on-rails postgresql datetime timestamp

我试图弄清楚如何更改t.timestamps在Rails迁移中使用的本机数据类型。以postgres结尾的默认类型为timestamp without timezone。相反,我想要的是timestamp(0) without timezone

我想更改本机数据类型,以便在创建新表并在迁移中使用t.timestamps时,它会自动创建正确的时间戳数据类型。

我需要timestamp(0) without timezone是因为我的rails应用程序与laravel应用程序共享其数据库,并且两个应用程序都可以插入数据。由于rails使用毫秒/ laravel的事实,并且似乎没有一种方法(截至2018-10-23)使laravel支持具有包含不同时间戳格式的表({{1} } vs Y-m-d H:i:s.u),而不必关闭模型中的时间戳,本质上是禁用它们的自动管理,我想让数据库强制使用单一格式(Y-m-d H:i:s)。

有关更多详细信息,请问我另一个问题:Is there a way to change Rails default timestamps to Y-m-d H:i:s (instead of Y-m-d H:i:s.u) or have laravel ignore decimal portion of Y-m-d H:i:s.u?

因此,我想使用Y-m-d H:i:s截断毫秒数,而不必在创建新表时考虑正确设置表的时间戳类型,因为本机类型已经是timestamp(0)

我已经尝试过了

timestamp(0)

和类似的迁移

./config/environments/initializers

require "active_record/connection_adapters/postgresql_adapter"

module ActiveRecord
 module ConnectionAdapters
   class PostgreSQLAdapter
     NATIVE_DATABASE_TYPES.merge!(
      timestamp: { name: "timestamp(0) without timezone" }
     )
   end
 end
end

但是没有用。

我还尝试将时间戳更改为使用timestampz,并将其与正常检查相同地进行迁移,但还是没有运气...

class ChangeTimestampTypesToTimestamp0 < ActiveRecord::Migration[5.2]
  def change
    create_table :test, id: :uuid, default: -> { "gen_random_uuid()" } do|t|
      t.string :name, null: false

      t.timestamps
    end
  end
end

enter image description here

2 个答案:

答案 0 :(得分:1)

我相信我已经弄清楚了!

我开始研究通过从控制台打印出变量来设置哪些NATIVE_DATABASE_TYPES

Rails c
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES

结果: {:primary_key=>"bigserial primary key", :string=>{:name=>"character varying"}, :text=>{:name=>"text"}, :integer=>{:name=>"integer", :limit=>4}, :float=>{:name=>"float"}, :decimal=>{:name=>"decimal"}, :datetime=>{:name=>"timestamp"}, :time=>{:name=>"time"}, :date=>{:name=>"date"}, :daterange=>{:name=>"daterange"}, :numrange=>{:name=>"numrange"}, :tsrange=>{:name=>"tsrange"}, :tstzrange=>{:name=>"tstzrange"}, :int4range=>{:name=>"int4range"}, :int8range=>{:name=>"int8range"}, :binary=>{:name=>"bytea"}, :boolean=>{:name=>"boolean"}, :xml=>{:name=>"xml"}, :tsvector=>{:name=>"tsvector"}, :hstore=>{:name=>"hstore"}, :inet=>{:name=>"inet"}, :cidr=>{:name=>"cidr"}, :macaddr=>{:name=>"macaddr"}, :uuid=>{:name=>"uuid"}, :json=>{:name=>"json"}, :jsonb=>{:name=>"jsonb"}, :ltree=>{:name=>"ltree"}, :citext=>{:name=>"citext"}, :point=>{:name=>"point"}, :line=>{:name=>"line"}, :lseg=>{:name=>"lseg"}, :box=>{:name=>"box"}, :path=>{:name=>"path"}, :polygon=>{:name=>"polygon"}, :circle=>{:name=>"circle"}, :bit=>{:name=>"bit"}, :bit_varying=>{:name=>"bit varying"}, :money=>{:name=>"money"}, :interval=>{:name=>"interval"}, :oid=>{:name=>"oid"}

事实证明,在我开始将timestamp包括在内之前

module ActiveRecord
 module ConnectionAdapters
   class PostgreSQLAdapter
     NATIVE_DATABASE_TYPES.merge!(
      timestamp: { name: "timestamp", limit:0 }
     )
   end
 end
end

被认为是datetime被包含在内,我意识到timestampdatetime的别名。

我更改了NATIVE_DATABASE_TYPES合并,使其看起来像这样...

require "active_record/connection_adapters/postgresql_adapter"

module ActiveRecord
 module ConnectionAdapters
   class PostgreSQLAdapter
     NATIVE_DATABASE_TYPES.merge!(
       datetime: { name: "timestamp", limit:0 }
     )
   end
 end
end

我进行了迁移,列已成功设置为timestamp(0) without timezone

答案 1 :(得分:1)

这是一个解决方案。它将Rails中的默认时间戳记精度(包括迁移在内)更改为两个时间戳记,在PostgreSQL中更改为一秒。它既不容易也不简单,但是适用于带有PostgreSQL的Rails 5.2。

我认为初始化器应放在config/initializers/中(而不是environments中)。
编写以下文件。

# ./config/initializers/arbitrary.rb

require "active_record/connection_adapters/abstract/schema_definitions.rb"
require "active_record/connection_adapters/abstract_adapter"
require "active_record/connection_adapters/abstract/schema_statements"
require "active_record/connection_adapters/postgresql/schema_statements"
require "active_record/connection_adapters/postgresql_adapter"

module ActiveRecord
  module ConnectionAdapters
    # Overwrites a method in /abstract/schema_definitions.rb
    class TableDefinition
      def timestamps(**options)
        options[:null] = false if options[:null].nil?

        column(:created_at, :datetime0, options)
        column(:updated_at, :datetime0, options)
      end
    end

    # Overwrites a method in /abstract/schema_statements.rb
    module SchemaStatements
      def add_timestamps(table_name, options = {})
        options[:null] = false if options[:null].nil?

        add_column table_name, :created_at, :datetime0, options
        add_column table_name, :updated_at, :datetime0, options
      end
    end

    # Overwrites a method in /postgresql/schema_statements.rb
    module PostgreSQL
      module SchemaStatements
        def add_timestamps_for_alter(table_name, options = {})
          [add_column_for_alter(table_name, :created_at, :datetime0, options), add_column_for_alter(table_name, :updated_at, :datetime0, options)]
        end
      end
    end

    # Modifies a constant and methods in /postgresql_adapter.rb
    class PostgreSQLAdapter
      alias_method :initialize_type_map_orig, :initialize_type_map if ! self.method_defined?(:initialize_type_map_orig)
      NATIVE_DATABASE_TYPES[:datetime0] = { name: "timestamp(0)" }

      private    
        def initialize_type_map(m = type_map)
          register_class_with_precision_t0 m, "timestamp0", OID::DateTime
          initialize_type_map_orig(m)
        end

        def register_class_with_precision_t0(mapping, key, klass)
          mapping.register_type(key) do |*args|
            klass.new(precision: 0)
          end
        end
    end
  end
end

这是示例迁移文件。

# db/migrate/20181023182238_create_articles.rb
class CreateArticles < ActiveRecord::Migration[5.2]
  def change
    create_table :articles do |t|
      t.string :title
      t.timestamps
    end
  end
end

迁移(bin/rails db:migrate)创建一个表articles,该表具有PostgreSQL数据库中timestamp(0)的两个时间戳列(无时区)。

执行的SQL是这样的:

CREATE TABLE "articles" (
  "id" bigserial primary key, 
  "title" character varying, 
  "created_at" timestamp(0) NOT NULL,
  "updated_at" timestamp(0) NOT NULL);

注意

我已确认在Raisl控制台中进行迁移以创建表和进行数据更新的工作。它也意在更新表,但我尚未对其进行测试。

稍作调整,它也可以在其他数据库中工作。

基本上,以上代码定义了一个新的Rails类型timestamp0,并为其分配了timestamps()(分别为created_atupdated_at)。 如果您希望其他任何时间戳记列都相同(即,数据库中没有亚秒级精度),请在迁移中指定timestamp0,它应该可以工作(尽管我尚未测试)。