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_idkey_type 它仍然可以指代 Key 模型并被称为其他名称例如 source_idsource_type 这就是 Rails 要求您指定它的原因。

以上 运行ning 步骤的结果:

关于删除多态关系

数据库本身并不知道你的关系是否是多态的。没有用于确保数据完整性的 foreign_key,常规关系也能正常工作。

您可能收到错误 PG::UndefinedColumn: ERROR: column cars.key_type does not exist 的原因是 Rails 正在尝试使用迁移后不再存在的 key_type 列进行查询以将其删除。

我怀疑您可能没有在 运行 迁移后立即从 Car 模型中删除 polymorphic: true 或者 Rails 已经加载了考虑到的模型它是多态的,并且没有正确重新加载。以下步骤应该可以将迁移从多态切换到非多态:

  1. 从具有多态关系的原始状态的服务器开始 运行 以下迁移。请注意,此步骤是可选的,但建议:
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
  1. 现在将您的模型关系更改为以下内容:
class Key < ApplicationRecord
    has_one :car, dependent: :destroy
end

class Car < ApplicationRecord
    belongs_to :key
end
  1. 重新启动服务器或控制台会话。

让我知道这是否有效!我没有机会 运行 代码,因此可能存在一些语法错误。

以上 运行ning 步骤的结果:

问题与多态性无关,而是 STI。因为 STI 正在发挥作用,所以它需要并使用类型字段来标识记录。密钥 class、parent 应该定义为:

self.abstract_class = true

而且,children 应该定义了没有类型字段的单个表。