使用 CURRENT_TIMESTAMP 的 Rails 5.2 和 Active Record 迁移

Dow*_*ker 5 ruby-on-rails rails-activerecord ruby-on-rails-5

我有一些属性需要有默认值。我已经设置了迁移以在数据库中设置默认值,如下所示:

class AddDefaultsToModel < ActiveRecord::Migration[5.2]
  def change
    change_column :posts, :post_type, :string, default: 'draft'
    change_column :posts, :date_time, :datetime, default: -> { 'CURRENT_TIMESTAMP' }
  end
end
Run Code Online (Sandbox Code Playgroud)

直接添加到数据库时,默认值效果很好。但是,如果我在 Rails 中构建一个新模型,一个属性会按预期工作,而另一个则不会:

post = Post.new
post.post_type # draft (as expected)
post.date_time # nil (expecting the current date and time)
Run Code Online (Sandbox Code Playgroud)

这种行为是故意的吗?我是否也必须在模型中设置默认值?为什么Post#post_type有效但无效Post#date_time

mu *_*ort 4

ActiveRecord 不理解您的默认值的含义date_time,因此它根本不提供date_time默认值。然后,当您将该行插入数据库时​​(即post.save),数据库将使用当前时间戳作为值date_time(当然假设没有人接触过date_time)。Rails 不会知道date_time插入后有一个值,因此您会得到如下行为:

post = Post.new
post.date_time # `nil` because it hasn't been set at all
# set some other values on `post`...
post.save      # INSERT INTO posts (columns other than date_time...) values (...)
post.date_time # `nil` even thought there is a value in the database
post.reload    # Pull everything out of the database
post.date_time # Now we have a timestamp
Run Code Online (Sandbox Code Playgroud)

您有一些选择:

  1. post.reload保存后调用post以获取数据库使用的默认时间戳。

  2. 使用after_initialize钩子自行设置默认值:

    class Post < ApplicationRecord
      after_initialize if: :new_record? do
        self.date_time = Time.now
      end
    end
    
    Run Code Online (Sandbox Code Playgroud)
  3. 使用属性 API手动设置默认值:

    class Post < ApplicationRecord
      attribute :date_time, :datetime, default: ->{ Time.now }
    end
    
    Run Code Online (Sandbox Code Playgroud)

    您需要使用 lambda(或 Proc),以便Time.now在正确的时间执行。