如何将现有的一对多关系迁移到 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_id
foreign_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 :posts
和has_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