我正在对Rails中的应用程序进行全面的重建。旧版本(我们称之为“Legacy”)允许存储数据以便解释为虚假和真实。我不再希望以这种方式解释这些领域。
我为新应用程序编写了一个自定义Rake任务,用于从旧版数据库中虹吸数据并将该数据插入到新的数据库模型中。
问题源于布尔列我从PG中的Legacy DB模型迁移到新PG DB中的新模型。
假设这两个模型如下所示:
OLD MODEL
create_table "table_name", force: :cascade do |t|
t.string "name"
t.boolean "is_alive"
end
新模型
create_table "new_table_name", force: :cascade do |t|
t.string "new_name"
t.boolean "is_dead", default: false, null: false
end
让我们说我的自定义rake任务看起来类似于以下内容:
namespace :db do
desc "Migrate old records to new model."
task :migrate_old_records_to_new_model, [] => :environment do
Model.find_each do |oldness|
NewModel.find_or_initialize_by(oldness.name) do |newness|
puts "\n\tCreating new updated record for #{oldness.name}"
newness.new_name = oldness.name
newness.is_dead = oldness.is_alive
end
end
当我尝试从Legacy DB进行转换时,我在布尔列上得到PG::NotNullViolation
。
答案 0 :(得分:1)
最简单的方法是:
newness.is_dead = !oldness.is_alive
表定义中的null: false
告诉PG该字段永远不能为空。遗留数据似乎包含空值。只需将它们分配给新模型就会破坏数据库约束。
我知道!你在考虑"但是我告诉PG,默认值是false
!"是。真正。但是,ActiveRecord 显式告诉PG将该字段设置为NULL
并且PG符合,忽略默认值...但不是约束!如果要从insert语句中省略字段/值,则将使用隐含的默认值而不是将字段保留为NULL
。
更好的解决方案是在新模型中添加一些before_save
验证,以设置这些默认值以避免将来出现类似情况。
你会这样做:
class NewModel < ActiveRecord::Base
before_save :ensure_is_dead_is_not_null
def ensure_is_dead_is_not_null
is_dead = false if is_dead.nil?
true
end
end
我们需要让此before_save
回调(ensure_is_dead_is_not_null
)始终返回true
,因为:
如果before_ *回调返回false,则取消所有后续回调和相关操作。
参考:http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
此外,根据您处理价值分配和模型实施的方式,您可能希望在after_initialize
而不是before_save
上设置默认值。如果这样做,请记住每次Ruby初始化和实例(每次从数据库加载记录时)after_initialize
,因此您需要确保不要盲目地破坏从DB加载的有效值。