【问题标题】:How to change a reference column to be polymorphic?如何将参考列更改为多态?
【发布时间】:2014-08-29 07:21:00
【问题描述】:

我的模型(Bar)已经有一个引用列,我们称之为foo_id,现在我需要将foo_id更改为fooable_id并使其具有多态性。

我想我有两个选择:

  • 创建新的引用列fooable,它是多态的,并从foo_id 迁移ID(迁移这些的最好方法是什么?我可以做Bar.each { |b| b.fooable_id = b.foo_id }?
  • foo_id 重命名为fooable_id 并将polymorphic 添加到fooable_id。如何向现有列添加多态?

【问题讨论】:

  • 哇哦,你还在使用 Rails 2 吗?这是真的还是错字?

标签: ruby-on-rails activerecord migration ruby-on-rails-2


【解决方案1】:

1.foo_id 的名称更改为 fooable_id,方法是在如下迁移中重命名:

rename_column :bars, :foo_id, :fooable_id

2. 并通过在迁移中添加所需的 foo_type 列来为其添加多态性:

add_column :bars, :fooable_type, :string

3. 并在您的模型中:

class Bar < ActiveRecord::Base
  belongs_to :fooable, 
    polymorphic: true
end

4. 最后播种你已经关联的类型,例如:

Bar.update_all(fooable_type: 'Foo')

阅读Define polymorphic ActiveRecord model association!

【讨论】:

  • 最后一步可以改写成Bar.update_all(fooable_type: 'Foo')吧?
  • 如果您使用 foreign_key(默认生成的迁移以供参考),这将不起作用。请参阅下面的更新答案。
【解决方案2】:

Rails 更新 >= 4.2

当前 Rails 使用 index 和 foreign_key 生成引用,这是一件好事。
这意味着@Christian Rolle 的答案不再有效,因为在重命名foo_id 后,它会在bars.fooable_id 上留下一个foreign_key,引用foo.id,这对其他fooables 无效。

幸运的是,迁移也在不断发展,因此确实存在可撤消的引用迁移。

您需要创建一个新引用并删除旧引用,而不是重命名引用 ID。
新的是需要将 id 从旧引用迁移到新引用。
这可以通过

Bar.find_each { |bar| bar.update fooable_id: bar.foo_id }

但是当已经有很多关系时,这可能会很慢。 Bar.update_all 在数据库级别执行此操作,速度要快得多。

当然,你应该可以回滚迁移,所以当使用foreign_keys时,完整的迁移是:

def change
  add_reference :bars, :fooable, polymorphic: true
  reversible do |dir|
    dir.up { Bar.update_all("fooable_id = foo_id, fooable_type='Foo'") }
    dir.down { Bar.update_all('foo_id = fooable_id') }
  end
  remove_reference :bars, :foo, index: true, foreign_key: true
end

记住,在回滚期间,更改是从下到上处理的,所以 foo_id 在update_all 之前创建,一切都很好。

【讨论】:

  • 谢谢!这真的很有帮助。关于向下部分的问题 - 它不应该显式过滤 type='Foo'fooables,因为 Foo 另一个类之间可能存在重复 id,而自多态更改以来可能已经实现了 Fooable,对吧?
  • 清除对 Foo 以外的其他类型的所有多态引用可能很有用,因为当您关闭多态时,其他类应该不再存在。这应该在稍后添加其他类的迁移中完成(之前还原:-)。是的,但是...
  • 超级有用的迁移 - 刚刚在我的一个项目中使用了 5 次
【解决方案3】:

我要做的一个小改动是迁移:

#db/migrate/latest.rb
class Latest
  def change
    rename_column :bars, :foo_id, :fooable_id
    add_column :bars, :fooable_type, :string, after: :id, default: 'Foo'
  end
end

这将消除进行数据迁移的需要。

更新:这将适用于 Rails 3 及更高版本。根据问题,原始基类暗示为 Foo。

【讨论】:

  • 嗯,这在 Rails 2.3 中无效,这也消除了使用无类型基类的可能性
【解决方案4】:

具体参考您的问题,我会这样做:

Bar 模型中的所有数据都将参考Bar 模型进行存储。这意味着如果您更改模型中的 foo_id 属性,您将能够只填充您需要添加的 bar_type 属性(因为它们都可以引用相同的模型)

方法如下:

  1. foo_id > fooable_id 创建迁移
  2. 插入fooable_type
  3. 在 Rails 控制台中,循环遍历 Bar 的所有现有记录,填充 fooable_type

第一件事:

$ rails g migration ChangeFooID

#db/migrate/latest.rb
class Latest
   def change
      rename_column :bars, :foo_id, :fooable_id
      add_column :bars, :fooable_type, :string, after: :id
   end
end

这将为您创建各种列。然后您只需要能够循环浏览记录并更改type 列:

轨道c

Bar.find_each do |bar|
  bar.update(barable_type: "Foo")
end

这将允许您更改列的类型,使您能够将所有当前记录与各自的记录相关联。


多态性

您可以use the Rails docs as a reference 了解如何关联您的模型:

#app/models/foo.rb
class Foo < ActiveRecord::Base
   has_many :bars, as: :barable
end

#app/models/bar.rb
class Bar < ActiveRecord::Base
   belongs_to :foo, polymorphic: true
end

【讨论】:

  • bar.update(barable_type: "Foo") 不起作用,因为更新是私有的。我猜你的意思是 ActiveRecord#update_attribute。
猜你喜欢
  • 2014-01-20
  • 2018-10-30
  • 2018-05-24
  • 1970-01-01
  • 2015-10-03
  • 2017-03-30
  • 1970-01-01
  • 2022-11-03
  • 1970-01-01
相关资源
最近更新 更多