【问题标题】:how to change rails migration t.timestamps to use `timestamp(0) without timezone` in postgres如何在postgres中更改rails迁移t.timestamps以使用没有时区的`timestamp(0)`
【发布时间】:2019-03-27 23:27:01
【问题描述】:

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

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

我需要timestamp(0) without timezone,因为我的 rails 应用程序与 laravel 应用程序共享其数据库,并且两个应用程序都可以插入数据。由于 rails 不使用毫秒/laravel 的事实,并且 laravel 似乎没有办法(截至 2018-10-23)支持拥有一个包含不同格式时间戳的表(Y-m-d H:i:s.u vs Y-m-d H:i:s) 无需关闭模型中的时间戳,基本上禁用它们的自动管理,我希望数据库强制使用单一格式 (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?

所以我想使用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

和类似的迁移

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

但这没有用。

我还尝试更改时间戳以使用与上述迁移相同的 timestampz 作为健全性检查,但仍然没有运气...

require "active_record/connection_adapters/postgresql_adapter"

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

【问题讨论】:

    标签: ruby-on-rails postgresql datetime timestamp


    【解决方案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) 在 PostgreSQL 数据库中创建一个表 articles,其中包含两个时间戳列 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);
    

    注意

    我已确认迁移以在 Rails 控制台中创建表和数据更新工作。它也适用于更新表的迁移,但我还没有测试过。

    稍加调整后,它也可以在其他数据库中使用。

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

    【讨论】:

      【解决方案2】:

      我相信我已经弄明白了!

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

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

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

      【讨论】:

      • 注意:我刚刚注意到这个答案。看来这个答案和我的答案之间的区别是我的答案保留了 Rails 中的默认 timestamp 属性,并仅更改了 created_atupdated_at 的那些。这个答案总体上对datetiime 进行了全局更改。我想哪个更合适取决于用户的需要。
      猜你喜欢
      • 2016-05-14
      • 1970-01-01
      • 1970-01-01
      • 2020-12-06
      • 2010-12-12
      • 2021-05-08
      • 2014-05-24
      • 2018-09-17
      • 2017-04-01
      相关资源
      最近更新 更多