Rails 5 与 PostgreSQL 变化 table 关系从多态到标准 has_one/belongs_to 关联
Rails 5 with PostgreSQL change table relation from polymorphic to standard has_one/belongs_to association
Rails 5 与 PostgreSQL 变化 table 关系从多态到标准 has_one/belongs_to 关联。
我有一辆 table 汽车,它与 table 钥匙有不必要的多态关联。由于多态性为真,car.key 有效而 key.car 失败。我正在尝试将该关系改回 has_one/belongs_to,因为该关系是 immutable 并且不需要多态性。
我想我可以删除 table 并重新定义它,但接下来就是恢复数据和所有关系的问题。
我已经构建了一个 运行 很好的迁移,并且我认为应该可以解决问题。迁移是 运行 然后从 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>
替代迁移用相同的结果替换整个 table(更新):
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
这很奇怪,你可以尝试将新的 key_id
列修改为引用键 table 的外键吗?
尽管在我看来问题出在 ActiveRecord 而不是 PostgreSQL,因为 ActiveRecord 正在生成一个假定多态关系的查询。也就是说,如果您使用 spring 从模型中删除多态关系,我建议在打开新控制台之前使用 spring stop
停止它。我发现了一些像这样的奇怪问题,这些问题在停止 spring.
后得到解决
关于查询key.car
应该可以使用多态关联建立双向关系。我相信,在您的情况下,语法如下:
class Key < ApplicationRecord
has_one :car, as: :key, dependent: :destroy
end
class Car < ApplicationRecord
belongs_to :key, polymorphic: true
end
注意 as: :key
。在这种情况下,:key
指的是您为多态列选择的名称,也就是 key_id
和 key_type
它仍然可以指代 Key
模型并被称为其他名称例如 source_id
和 source_type
这就是 Rails 要求您指定它的原因。
以上 运行ning 步骤的结果:
关于删除多态关系
数据库本身并不知道你的关系是否是多态的。没有用于确保数据完整性的 foreign_key,常规关系也能正常工作。
您可能收到错误 PG::UndefinedColumn: ERROR: column cars.key_type does not exist
的原因是 Rails 正在尝试使用迁移后不再存在的 key_type
列进行查询以将其删除。
我怀疑您可能没有在 运行 迁移后立即从 Car
模型中删除 polymorphic: true
或者 Rails 已经加载了考虑到的模型它是多态的,并且没有正确重新加载。以下步骤应该可以将迁移从多态切换到非多态:
- 从具有多态关系的原始状态的服务器开始 运行 以下迁移。请注意,此步骤是可选的,但建议:
class ResolveCarKeyPolymorphism < ActiveRecord::Migration[5.2]
def change
remove_index :cars, column: [:key_type, :key_id]
remove_column :cars, :key_type, :string
add_index :cars, :key_id
end
end
- 现在将您的模型关系更改为以下内容:
class Key < ApplicationRecord
has_one :car, dependent: :destroy
end
class Car < ApplicationRecord
belongs_to :key
end
- 重新启动服务器或控制台会话。
让我知道这是否有效!我没有机会 运行 代码,因此可能存在一些语法错误。
以上 运行ning 步骤的结果:
问题与多态性无关,而是 STI。因为 STI 正在发挥作用,所以它需要并使用类型字段来标识记录。密钥 class、parent 应该定义为:
self.abstract_class = true
而且,children 应该定义了没有类型字段的单个表。
Rails 5 与 PostgreSQL 变化 table 关系从多态到标准 has_one/belongs_to 关联。
我有一辆 table 汽车,它与 table 钥匙有不必要的多态关联。由于多态性为真,car.key 有效而 key.car 失败。我正在尝试将该关系改回 has_one/belongs_to,因为该关系是 immutable 并且不需要多态性。
我想我可以删除 table 并重新定义它,但接下来就是恢复数据和所有关系的问题。
我已经构建了一个 运行 很好的迁移,并且我认为应该可以解决问题。迁移是 运行 然后从 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>
替代迁移用相同的结果替换整个 table(更新):
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
这很奇怪,你可以尝试将新的 key_id
列修改为引用键 table 的外键吗?
尽管在我看来问题出在 ActiveRecord 而不是 PostgreSQL,因为 ActiveRecord 正在生成一个假定多态关系的查询。也就是说,如果您使用 spring 从模型中删除多态关系,我建议在打开新控制台之前使用 spring stop
停止它。我发现了一些像这样的奇怪问题,这些问题在停止 spring.
关于查询key.car
应该可以使用多态关联建立双向关系。我相信,在您的情况下,语法如下:
class Key < ApplicationRecord
has_one :car, as: :key, dependent: :destroy
end
class Car < ApplicationRecord
belongs_to :key, polymorphic: true
end
注意 as: :key
。在这种情况下,:key
指的是您为多态列选择的名称,也就是 key_id
和 key_type
它仍然可以指代 Key
模型并被称为其他名称例如 source_id
和 source_type
这就是 Rails 要求您指定它的原因。
以上 运行ning 步骤的结果:
关于删除多态关系
数据库本身并不知道你的关系是否是多态的。没有用于确保数据完整性的 foreign_key,常规关系也能正常工作。
您可能收到错误 PG::UndefinedColumn: ERROR: column cars.key_type does not exist
的原因是 Rails 正在尝试使用迁移后不再存在的 key_type
列进行查询以将其删除。
我怀疑您可能没有在 运行 迁移后立即从 Car
模型中删除 polymorphic: true
或者 Rails 已经加载了考虑到的模型它是多态的,并且没有正确重新加载。以下步骤应该可以将迁移从多态切换到非多态:
- 从具有多态关系的原始状态的服务器开始 运行 以下迁移。请注意,此步骤是可选的,但建议:
class ResolveCarKeyPolymorphism < ActiveRecord::Migration[5.2]
def change
remove_index :cars, column: [:key_type, :key_id]
remove_column :cars, :key_type, :string
add_index :cars, :key_id
end
end
- 现在将您的模型关系更改为以下内容:
class Key < ApplicationRecord
has_one :car, dependent: :destroy
end
class Car < ApplicationRecord
belongs_to :key
end
- 重新启动服务器或控制台会话。
让我知道这是否有效!我没有机会 运行 代码,因此可能存在一些语法错误。
以上 运行ning 步骤的结果:
问题与多态性无关,而是 STI。因为 STI 正在发挥作用,所以它需要并使用类型字段来标识记录。密钥 class、parent 应该定义为:
self.abstract_class = true
而且,children 应该定义了没有类型字段的单个表。