【问题标题】:Rails 5 with PostgreSQL change table relation from polymorphic to standard has_one/belongs_to association带有 PostgreSQL 的 Rails 5 将表关系从多态更改为标准 has_one/belongs_to 关联
【发布时间】: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


【解决方案1】:

问题与多态性无关,而是与 STI 有关。因为 STI 在起作用,所以它需要并使用类型字段来识别记录。 Key 类,即父类,应该定义为:

self.abstract_class = true

而且,孩子们应该已经定义了没有类型字段的单个表。

【讨论】:

    【解决方案2】:

    关于查询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_id 和 @987654332 @ 这就是 Rails 要求您指定它的原因。

    运行上述步骤的结果:

    关于去除多态关系

    数据库本身不知道你的关系是否是多态的。如果没有用于确保数据完整性的 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. 重新启动服务器或控制台会话。

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

    运行上述步骤的结果:

    【讨论】:

    • 只是为了向您保证,我在使用多态和不使用多态的情况下运行每个测试。而且,我在每次更改时重新启动服务器。这需要一些时间,但如果我弄错了,则需要更长的时间。第二条评论如下。
    • 抱歉,来不及处理这个问题。明天早上会再试一次,头脑清醒。
    • 更新 2 个选项。仔细检查了它。在我开始处理这个问题之前,我从它的备份中恢复了数据库,所以它很干净。我按照您的建议更新了 Key 和 Car。我用你的迁移替换了我的迁移并运行了 rails db:migrate。启动了服务器。如果我提供有效的“key = car.key”。在这种情况下,“key.car”有效。如果我在没有多态性的情况下执行“key = Key.find(car.key_id)”,那就可以了。但是,然后 key.car 仍然失败,并出现“cars.key_type 不存在”的相同错误,它不会。如果我使用多态性执行“key = Key.find(car.key_id)”或“car.key”,则两者都返回 nil。
    • @Richard_G 让我尝试复制。
    • 好吧,你的第一个例子,具有多态性,适用于所有测试用例,给定一辆汽车,car.key,Key.find(car.key_id).car 都有效。我仍然想删除多态性,稍后可能会尝试您的第二个解决方案。
    【解决方案3】:

    奇怪了,能不能试试把新的key_id列修改为引用Key表的外键?

    虽然在我看来问题是 ActiveRecord 而不是 PostgreSQL,因为 ActiveRecord 正在生成一个假设多态关系的查询。话虽如此,如果您使用 spring 从模型中删除多态关系后,我建议在打开新控制台之前使用spring stop 停止它。我发现了一些像这样的奇怪问题,这些问题在停止 spring 后得到了修复。

    【讨论】:

    • 好吧,我真的不希望 key_type 列成为问题。当我更改模型时,我实际上重新启动了服务器。让我看看将 key_id 指定为外部。谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-02-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-16
    相关资源
    最近更新 更多