我在之前的迁移中创建了一个日期列,并将其设置为可为空。现在我想将其更改为不可为空。假设该数据库中有空行,我该怎么做?如果它们当前为空,我可以将这些列设置为 Time.now。
如果你在迁移中这样做,那么你可能会这样做:
# 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
在 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。然而,里克史密斯的上述评论指出了一个非常有效的案例。
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。
此外,我通常更喜欢为该字段设置一个默认值,这样我就不需要在每次创建新对象时都指定该字段的值。我也包含了注释掉的代码来做到这一点。
add_column :users, :admin, :string
然后 change_column_null(:admin, :string, false, "new_value_for_existing_records")
创建具有 :default =>
值的 change_column
语句的迁移。
change_column :my_table, :my_column, :integer, :default => 0, :null => false
请参阅:change_column
根据您可能需要使用的数据库引擎 change_column_null
change_column_null
。
导轨 4:
def change
change_column_null(:users, :admin, false )
end
根据 docs,在 Rails 4.02+ 中,没有像 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
。
MyModel.update_all({:date_column => Time.now}, {:date_column => nil})
。您原始形式的查询只是使我所有的模型在该字段中都具有 nil 值。change
方法不太适合这种情况,因为 (1)update_all
方法将在迁移和潜在还原上执行。这可能不是最糟糕的事情,但因为 (2) 迁移无法知道列在潜在还原中的更改内容。所以对于这种情况,我会坚持使用up
和down
。