如何将现有的一对多关系迁移到 Rails 和 ActiveRecord 中的多对多

Posted

技术标签:

【中文标题】如何将现有的一对多关系迁移到 Rails 和 ActiveRecord 中的多对多【英文标题】:How to migrate a existing one-to-many relationship to many-to-many in Rails and ActiveRecord 【发布时间】:2014-09-03 04:20:27 【问题描述】:

我有一个模型 A,

Class A < ActiveRecord::Base
    has_many: names, class_name: 'B'

和一个模型 B

class B < ActiveRecord::Base
    belongs to :A

而且数据库中已经有一堆数据了。

如何编写迁移以将它们从一对多关系迁移到多对多关系?我更喜欢使用

has_many: through

如果可能的话。

db迁移写起来不难,但是里面的数据怎么迁移呢?

【问题讨论】:

我建议编写一个 rake 任务将 id 复制到 through 表中。也就是说,当然,在您编写/运行新迁移并修改模型以定义新关系之后。 【参考方案1】:

这种情况在 Rails 项目中经常出现,我很惊讶那里仍然没有很多操作方法,因为它是一种简单的数据演变,但在处理已部署的系统时需要一些技巧。

我不确定您是否对多对多的多态行为感兴趣,但我认为它对许多多对多场景很有用(双关语!:-) .

在开始之前我有这个:

class Tag < ActiveRecord::Base
  has_many :posts, inverse_of: :tag

class Post < ActiveRecord::Base
  belongs_to :tag, inverse_of: :posts

我知道,我知道,为什么一个帖子只有一个标签?事实证明,我希望我的帖子毕竟有多个标签。然后我想,等一下,我想让其他东西也有标签,比如某种东西。

您可以为每个 Posts-Tags 和 Things-Tags 使用 :has_and_belongs_to_many 但这会产生 2 个连接表,我们可能希望在添加更多实体时标记它们,对吧? has_many :through 是我们关联的一方面的一个很好的选择,并且避免了多个连接表。

我们将在 2 个步骤 中执行此操作,涉及 2 次部署

第 1 步 - 不更改现有关联。一个新的可标记模型/迁移,它将在帖子和事物方面具有多态性。 部署。

第 2 步 - 更新关联。新迁移从帖子中删除旧的:tag_idforeign_key。 部署。

这两个步骤对于能够使用您之前的关联定义在步骤 1 中执行迁移是必要的,否则您的新关联将不起作用。

我认为两个步骤是最简单的方法,如果您的流量足够低,以至于在两个步骤之间在帖子/事物上创建额外标签的风险足够低,建议您这样做。如果您的流量非常高,您可以将这两个步骤合二为一,但您需要使用不同的关联名称,然后在工作推出后返回删除旧的未使用的名称。我将把一步法作为练习留给读者:-)

步骤 1

为新的多态连接表创建模型迁移。

rails g model Taggable tag_id:integer tagged_id:integer tagged_type:string --timestamps=false

编辑生成的迁移以恢复使用#up#down(而不是#change)并添加数据迁移:

class CreateTaggables < ActiveRecord::Migration
  def up
    create_table :taggables do |t|
      t.integer :tag_id
      t.integer :tagged_id
      t.string :tagged_type
    end

    # we pull Posts here as they have the foreign_key to tags...
    Posts.all.each do |p|
      Taggable.create(tag_id: p.tag_id, tagged_id: p.id, tagged_type: "Post")
    end
  end

  def down
    drop_table :taggables
  end
end

编辑您的新模型:

class Taggable < ActiveRecord::Base
  belongs_to :tag
  belongs_to :tagged, polymorphic: true
end

此时,部署您的新模型并进行迁移。太好了。

第二步

现在我们要更新我们的类定义:

class Tag < ActiveRecord::Base
  has_many :taggables
  has_many :posts, through: :taggables, source: :tagged, source_type: "Post"
  has_many :things, through: :taggables, source: :tagged, source_type: "Thing"

class Post < ActiveRecord::Base
  has_and_belongs_to_many :tags, join_table: 'taggables', foreign_key: :tagged_id

class Thing < ActiveRecord::Base
  has_and_belongs_to_many :tags, join_table: 'taggables', foreign_key: :tagged_id

您应该能够在has_many :postshas_many :things 上添加dependent: :destroy,因为:tag 是Taggable 上的belongs_to

不要忘记删除旧的外键:

class RemoveTagIdFromPosts < ActiveRecord::Migration
  def up
    remove_column :posts, :tag_id
  end

  def down
    add_column :posts, :tag_id, :integer
  end
end

更新您的规格!

部署!

【讨论】:

以上是关于如何将现有的一对多关系迁移到 Rails 和 ActiveRecord 中的多对多的主要内容,如果未能解决你的问题,请参考以下文章

如何将现有的 Web 应用程序从 Heroku 迁移到 AWS

如何将现有的 sqlite 表迁移到具有 VARCHAR、TIMESTAMP 等数据类型的 ROOM Db?

如何将现有的 20.04 ext4 安装迁移到不同磁盘上的 zfs root?

将现有的移动应用程序从 Android Native(Java) 和 IOS Native(Swift) 迁移到 React Native

涉及新实体和一对多关系的核心数据迁移

在空 NSSet 上迁移关系到一对多结果