【问题标题】:How to set a default value for a datetime column to record creation time in a migration?如何为日期时间列设置默认值以记录迁移中的创建时间?
【发布时间】:2009-10-16 22:54:32
【问题描述】:

考虑下面的表创建脚本:

create_table :foo do |t|
  t.datetime :starts_at, :null => false
end

是否可以将默认值设置为当前时间?

我正在尝试在 rails 中为下面给出的 SQL 列定义找到一个独立于数据库的等效项:

Oracle 语法

start_at DATE DEFAULT SYSDATE() 

MySQL 语法

start_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP

start_at DATETIME DEFAULT NOW()

【问题讨论】:

    标签: ruby-on-rails


    【解决方案1】:

    现在 Rails 5 支持此功能。

    这是一个示例迁移:

    class CreatePosts < ActiveRecord::Migration[5.0]
      def change
        create_table :posts do |t|
          t.datetime :modified_at, default: -> { 'CURRENT_TIMESTAMP' }
          t.timestamps
        end
      end 
    end
    

    请参阅https://github.com/rails/rails/issues/27077 的讨论并由 prathamesh-sonpatki 在那里回答

    【讨论】:

    • 很好的答案。顺便说一句,请注意在 Postgres 中 CURRENT_TIMESTAMP 将是当前事务的开始时间,因此在同一事务中创建的多个记录将获得相同的值。如果您想要语句执行的实际当前时间(忽略事务上下文),请查看CLOCK_TIMESTAMP
    • 我添加了类似这样的内容:add_column :table_name, :start_date, :datetime, default: -&gt; { 'CURRENT_TIMESTAMP' } 这就是它在 schema.rb 中的样子 t.datetime "start_date", default: -&gt; { "now()" } 但是当我创建新记录时,它并没有被填充。知道为什么吗?
    • @SandipSubedi 对我来说也一样。在轨道上 5.2.3。
    • @courtsimas 您需要执行post.reload 才能获得这些值。这里解释一下:stackoverflow.com/questions/53804787/…
    • @SandipSubedi 我在发表评论 5 分钟后意识到这一点。就像 uuid 生成一样。谢谢!
    【解决方案2】:

    你可以像这样在模型中添加一个函数:

      before_create :set_foo_to_now
      def set_foo_to_now
        self.foo = Time.now
      end
    

    这样模型会在模型中设置当前时间。

    您还可以在迁移中放置一些 sql 代码,用于在数据库级别设置默认值,例如:

    execute 'alter table foo alter column starts_at set default now()'
    

    设置如下:

    create_table :foo do |t|
      t.datetime :starts_at, :null => false, :default => Time.now
    end
    

    导致在迁移期间执行 Time.now 函数,因此数据库中的表创建如下:

    create table foo ( starts_at timestamp not null default '2009-01-01 00:00:00');
    

    但我认为这不是你想要的。

    【讨论】:

    • 目前我正在 :before_create 回调中设置值。
      我在这里寻找某种类型的 AR 魔法。我花了一些时间查看 Rails 代码,但没有找到任何解决方案。我想我会四处打听,看看是否有其他选择。
    • 我建议在 before_create 上使用回调。
    • 我不想更改数据库表,因为我想让我的代码数据库保持中立。我希望 AR 有一些机制来设置 Datetime 字段的默认值,类似于 created_at 字段。
    • 请注意,before_create 回调在插入数据库之前但在您实例化对象之后运行(如使用new())。所以self.foo = Time.now 将覆盖你可能给new() 的值。我建议改为self.foo = Time.current unless self.foo.present?
    • 不是这样,当使用执行时,这会在 schema.rb 中添加一行:starts_at, :default =&gt; 'now()'。但是,这在使用 rake db:schema:dump 时不起作用,它将用 :starts_at, :default =&gt; '2015-05-29 09:46:33' 覆盖 schema.rb (或启动脚本时的任何日期)......悲伤......
    【解决方案3】:

    如果您需要在 Rails 5 中更改 现有的 DateTime 列(而不是创建其他答案中指定的新表),以便它可以利用默认日期功能,您可以像这样创建迁移:

    class MakeStartsAtDefaultDateForFoo < ActiveRecord::Migration[5.0]
      def change
        change_column :foos, :starts_at, :datetime, default: -> { 'CURRENT_TIMESTAMP' }
      end
    end
    

    【讨论】:

    • 对某事投反对票并且不解释答案存在什么问题的错误形式。有人知道为什么这被否决了吗?如果您想更改列而不是创建列,它会显示语法。
    • 我不是投反对票的人,但我很难看出这个答案与你之前一年的遗嘱答案有何不同。问题是关于设置默认值,而您的答案具有相同的 lambda 子句。
    • @nurettin 我明白你的意思,但在我的辩护中,创建新列的语法与更改现有列的语法略有不同。为那些通过网络搜索找到这个问题的人提供这种语法,同时尝试向他们当前的数据模型添加默认值,而不是完全创建一个新的模型/表,这可能非常有帮助。不?您假设每个人都知道他们可以对 change_column 使用相同的 lambda。也许他们应该意识到这一点,但这就是我在这里回答的原因——所以他们不必去其他任何地方就能弄清楚这一点。干杯!
    • FWIW 我刚刚使用了这种语法,并感谢我的谷歌搜索和特定问题,这个答案对我帮助最大。
    • 我认为这是一个答案而不是评论有什么问题。赞成。我认为投票者只是粗心地阅读(就像大多数问题接近者一样!;p)。
    【解决方案4】:

    如果表中有名为created_at/created_onupdated_at/updated_on 的字段,Active Record 会自动为创建和更新操作添加时间戳。 Source - api.rubyonrails.org

    除了拥有该列之外,您无需执行任何其他操作。

    【讨论】:

    • 我的表中已经有了这些字段。我需要一个额外的字段供我的调度程序保存开始日期。目前,我正在使用 :before_create 回调来设置当前日期。如果我经常遇到这种情况,我不得不求助于编写一个插件来改变 ColumnDefinition 类的 'to_sql' 方法中的默认值处理。
    【解决方案5】:

    我正在寻找类似的解决方案,但我最终使用了https://github.com/FooBarWidget/default_value_for

    default_value_for 插件允许以声明的方式定义 ActiveRecord 模型的默认值。例如:

    class User < ActiveRecord::Base
      default_value_for :name, "(no name)"
      default_value_for :last_seen do
        Time.now
      end
    end
    
    u = User.new
    u.name       # => "(no name)"
    u.last_seen  # => Mon Sep 22 17:28:38 +0200 2008
    

    【讨论】:

      【解决方案6】:

      我通常这样做:

      def change
        execute("
          ALTER TABLE your_table
          ALTER COLUMN your_column
          SET DEFAULT CURRENT_TIMESTAMP
        ")
      end
      

      所以,你的schema.rb 将会有类似的东西:

      create_table "your_table", force: :cascade do |t|
        t.datetime "your_column", default: "now()"
      end
      

      【讨论】:

      • 缺点:此解决方案仅在您运行 rake db:migrate 时有效,而不是在您使用 rake db:schema:load 之类的内容加载架构文件时有效。
      • 这会使您的架构列更改为每次迁移运行的评估日期时间
      【解决方案7】:

      你知道upserts fail unless you have a default updated_at/created_at????

      没有自动执行此操作的迁移标志,您必须手动包含带有 default 键的选项对象

      create_table :table_foos do |t|
        #...
        # date with timestamp
        t.datetime :last_something_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
        
        # standard timestamps
        t.timestamps({default: -> { "CURRENT_TIMESTAMP" }})
      end
      

      【讨论】:

        【解决方案8】:

        在@szymon-lipiński (Szymon Lipiński) 给出的答案中,execute 方法对我不起作用。它抛出了一个 MySQL 语法错误。

        对我有用的 MySQL 语法是这样的。

        execute "ALTER TABLE mytable CHANGE `column_name` `column_name` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP"
        

        因此,在迁移脚本中设置日期时间列的默认值可以如下完成:

        def up
          create_table :foo do |t|
            t.datetime :starts_at, :null => false
          end
        
          execute "ALTER TABLE `foo` CHANGE `starts_at` `starts_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP"
        end
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-09-29
          • 2017-01-01
          • 2010-09-15
          • 2018-07-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-08-06
          相关资源
          最近更新 更多