【发布时间】:2021-06-09 19:24:41
【问题描述】:
带有 PostgreSQL 的 Rails 5 将表关系从多态更改为标准 has_one/belongs_to 关联。
我有一个表 Car,它与表 Key 具有不必要的多态关联。由于多态为真,car.key 有效,而 key.car 失败。我正在尝试将这种关系改回 has_one/belongs_to,因为这种关系是不可变的并且不需要多态性。
我想我可以删除表并重新定义它,但接下来就是恢复数据和所有关系的问题。
我已经构建了一个运行良好的迁移,我认为应该可以解决问题。运行迁移,然后从 Car 模型中删除多态性。 Rails 似乎对此很满意。 PostgreSQL 不是那么多。它仍然认为关系是多态的,并抱怨缺少 key_type 字段。请参阅以下代码和简化示例。
必须存在导致此问题的架构外部的某些触发器、过程或约束。但是,我还没有找到它。
在这里,多态是不必要的复杂化。如何通过这项工作或您可能提出的其他建议来解决此问题?
迁移:
class ResolveCarKeyPolymorphism < ActiveRecord::Migration[5.2]
def self.up
add_column :cars, :will_be_key_id, :integer
self.copy_key_id_to_will_be_key_id
remove_reference :cars, :key, polymorphic: true
rename_column :cars, :will_be_key_id, :key_id
end
def self.down
rename_column :cars, :key_id, :will_be_key_id
add_reference :cars, :keys, polymorphic: true
self.copy_will_be_key_id_to_key_id
remove_column :cars, :will_be_key_id
end
def copy_key_id_to_will_be_key_id
Car.all.each do |car|
car.will_be_key_id = car.key_id
car.save!
puts "Car:#{car.id};Key:#{car.key_id}"
end
end
def copy_will_be_key_id_to_key_id
Car.all.each do |car|
car.key_id = car.will_be_key_id
car.key_type = "Key"
car.save!
puts "Car:#{car.id};Key:#{car.key_id}"
end
end
end
示例代码:
class Key < ApplicationRecord
has_one :car, dependent: :destroy
end
class Car < ApplicationRecord
belongs_to :key, dependent: :destroy #, polymorphic: true (commented out after the migration)
end
car = Car.find_by(stock_number: "PRT38880")
=> #<Car id: 56251, stock_number: "PRT38880", key_id: 25629>
car.key
=> #<Key id: 25629>
key = Key.find(25629)
=> #<Key id: 25629>
key.car
=> PG::UndefinedColumn: ERROR: column cars.key_type does not exist
其他示例:(我很惊讶这能奏效,但只是在迁移和回滚之后。)
car = Car.find_by(stock_number: "PRT38880")
=> #<Car id: 56251, stock_number: "PRT38880", key_id: 25629>
key = car.key
=> #<Key id: 25629>
key.car
=> #<Car id: 56251, stock_number: "PRT38880", key_id: 25629>
替代迁移用相同的结果替换整个表(更新):
class ResolveCarKeyPolymorphism < ActiveRecord::Migration[5.2]
def self.up
create_table "cars_news", id: :serial, force: :cascade do |t|
t.string "stock_number", limit: 255, default: "", null: false
... additional fields
t.integer "key_id"
end
add_foreign_key :cars, :keys
self.copy_cars_to_cars_news
drop_table :cars
rename_table :cars_news, :cars
change_table :cars do |t|
t.index ["company_id"], name: "index_cars_on_company_id"
t.index ["stock_number"], name: "index_cars_on_stock_number"
t.index ["key_id"], name: "index_cars_on_key_id"
end
end
def self.down
remove_foreign_key :cars, :keys if foreign_key_exists?(:cars, :keys)
remove_index :cars, :key_id if index_exists?(:keys, :key_id)
add_column :cars, :key_type, :string unless column_exists?(:cars, :key_type)
add_index :cars, ["key_type", "key_id"], name: "index_cars_on_key_type_and_key_id"
Car.update_all(key_type: "Key")
end
def copy_cars_to_cars_news
Car.all.each do |car|
cars_new = CarsNew.new
car.attributes.each do |key, value|
cars_new[key] = value unless key == "key_type"
end
cars_new.save!
puts "Car:#{cars_new[:stock_number]}:#{cars_new[:id]} created with key_id:#{cars_new[:key_id]};"
end
end
end
【问题讨论】:
-
在输入示例之前您是否重新加载了控制台?
-
@Maxence 是的。并且,检查数据库以确保 key_id 列是正确的,并且 key_type 列实际上已经消失了。我曾问过自己同样的问题,并在重新启动后重新运行测试以确保该过程完成。
-
确实很奇怪。您是否查看过 db 文件夹中的架构并检查它是否与您的更改一致? (我怀疑它会有所不同,但这个旧列必须仍然存在于应用程序的某个地方)
-
@Maxence 是的,我有。我从中拉出 Car 表,并正在考虑进行更多重大更改,在该更改中,我将包含 key_type 的旧表复制到没有 key_type 并进行重命名的新表中,就像此处指定的迁移一样,只是一个更全局的范围.但是,我还不相信我不会遇到同样的问题。我同意对 key_type 的一些引用必须存在,但它必须存在于 PG 中,因为我猜这是发出错误的人。
-
@Maxence 让我感到奇怪的是“remove_reference :cars, :key, polymorphic: true” 应该通过删除 key_type 引用来真正清理这个烂摊子,但事实并非如此。
标签: ruby-on-rails postgresql polymorphism rails-migrations