【发布时间】:2011-08-23 10:28:12
【问题描述】:
我在之前的迁移中创建了一个日期列并将其设置为可为空。现在我想将其更改为不可为空。假设该数据库中有空行,我该怎么做?如果这些列当前为空,我可以将这些列设置为 Time.now。
【问题讨论】:
我在之前的迁移中创建了一个日期列并将其设置为可为空。现在我想将其更改为不可为空。假设该数据库中有空行,我该怎么做?如果这些列当前为空,我可以将这些列设置为 Time.now。
【问题讨论】:
创建具有change_column 语句和:default => 值的迁移。
change_column :my_table, :my_column, :integer, :default => 0, :null => false
根据数据库引擎,您可能需要使用change_column_null
【讨论】:
change_column_null。
如果你在迁移中这样做,那么你可能会这样做:
# Make sure no null value exist
MyModel.where(date_column: nil).update_all(date_column: Time.now)
# Change the column to not allow null
change_column :my_models, :date_column, :datetime, null: false
【讨论】:
MyModel.update_all({:date_column => Time.now}, {:date_column => nil})。您原始表单中的查询刚刚使我所有的模型在该字段中都具有 nil 值。
change 方法不太适合这种情况,因为 (1) update_all 方法将在迁移和潜在还原时执行。这可能不是最糟糕的事情,但因为 (2) 迁移无法知道列在潜在还原中的更改内容。所以对于这种情况,我会坚持使用up 和down。
在 Rails 4 中,这是一个更好的(DRYer)解决方案:
change_column_null :my_models, :date_column, false
为确保该列中不存在具有NULL 值的记录,您可以传递第四个参数,这是用于具有NULL 值的记录的默认值:
change_column_null :my_models, :date_column, false, Time.now
【讨论】:
change_column_null 加 1。然而,Rick Smith 的上述评论指出了一个非常有效的案例。
导轨 4:
def change
change_column_null(:users, :admin, false )
end
【讨论】:
Rails 4(其他 Rails 4 答案有问题):
def change
change_column_null(:users, :admin, false, <put a default value here> )
# change_column(:users, :admin, :string, :default => "")
end
将其中包含 NULL 值的列更改为不允许 NULL 会导致问题。这正是可以在您的开发设置中正常工作的代码类型,然后在您尝试将其部署到 LIVE 生产环境时崩溃。您应该首先将 NULL 值更改为有效的值,然后 然后 不允许 NULL。 change_column_null 中的第 4 个值正是这样做的。详情请见documentation。
另外,我通常更喜欢为字段设置默认值,这样我就不需要在每次创建新对象时都指定字段的值。我也包含了注释掉的代码来执行此操作。
【讨论】:
change_column_null(:admin, :string, false, "new_value_for_existing_records")
在 Rails 4.02+ 中,根据 docs 没有像 update_all 这样的方法,带有 2 个参数。相反,可以使用以下代码:
# Make sure no null value exist
MyModel.where(date_column: nil).update_all(date_column: Time.now)
# Change the column to not allow null
change_column :my_models, :date_column, :datetime, null: false
【讨论】:
如果您有现有记录,则不能使用 add_timestamps 和 null:false,因此这是解决方案:
def change
add_timestamps(:buttons, null: true)
Button.find_each { |b| b.update(created_at: Time.zone.now, updated_at: Time.zone.now) }
change_column_null(:buttons, :created_at, false)
change_column_null(:buttons, :updated_at, false)
end
【讨论】:
根据Strong Migrations gem,在生产中使用change_column_null 是个坏主意,因为它会在检查所有记录时阻止读写。
处理这些迁移(特定于 Postgres)的推荐方法是将此过程分成两个迁移。
一个用约束改变表:
class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
def change
safety_assured do
execute 'ALTER TABLE "users" ADD CONSTRAINT "users_some_column_null" CHECK ("some_column" IS NOT NULL) NOT VALID'
end
end
end
还有一个单独的迁移来验证它:
class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0]
def change
safety_assured do
execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "users_some_column_null"'
end
end
end
以上示例是从链接文档中提取(并稍作更改)的。显然对于 Postgres 12+,您还可以将 NOT NULL 添加到架构中,然后在运行验证后删除约束:
class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0]
def change
safety_assured do
execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "users_some_column_null"'
end
# in Postgres 12+, you can then safely set NOT NULL on the column
change_column_null :users, :some_column, false
safety_assured do
execute 'ALTER TABLE "users" DROP CONSTRAINT "users_some_column_null"'
end
end
end
当然,这意味着对于 Postgres 的早期版本,您的架构不会显示该列是 NOT NULL,因此我还建议设置模型级别验证以要求该值存在(尽管我建议即使对于允许此步骤的 PG 版本也是如此)。
此外,在运行这些迁移之前,您需要使用 null 以外的值更新所有现有记录,并确保写入表的任何生产代码都没有为值写入 null。
【讨论】: