我有一个模特A,
Class A < ActiveRecord::Base
has_many: names, class_name: 'B'
和模型B
class B < ActiveRecord::Base
belongs to :A
并且数据库中已经存在大量数据。
如何编写迁移以将它们从一对多迁移到多对多关系?我更喜欢使用
has_many: through
如果可能的话。
编写db迁移并不难,但如何迁移数据?
答案 0 :(得分:9)
这种情况在Rails项目中经常出现,我感到惊讶的是,由于其简单的数据演变,但在处理已经部署的系统时需要一些细微之处,但仍然没有很多方法。
我不确定你是否对多对多的多态行为感兴趣,但我把它扔进去,因为我觉得它对许多多对多的场景很有用(双关语!: - )。
在我开始之前我已经有了这个:
class Tag < ActiveRecord::Base
has_many :posts, inverse_of: :tag
class Post < ActiveRecord::Base
belongs_to :tag, inverse_of: :posts
我知道,我知道,为什么只有一个帖子的标签?事实证明,我希望我的帖子毕竟有多个标签。然后我想,等一下,我想要其他东西也有标签,比如某种东西。
您可以为每个帖子标签和事物标签使用:has_and_belongs_to_many,但这样就可以生成2个连接表,我们可能希望在添加正确的标签时标记更多实体?对于我们关联的一方,has_many :through
是一个很好的选择,并避免使用多个连接表。
我们将在 2 STEPS 中执行此操作,涉及 2部署:
第1步 - 不更改现有关联。一个新的Taggable模型/迁移,它将在Posts and Things方面具有多态性。 <强>部署。强>
第2步 - 更新关联。新迁移以从帖子中删除旧的:tag_id
foreign_key。 <强>部署。强>
为了能够使用您之前的关联定义在步骤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
您应该可以在dependent: :destroy
和has_many :posts
上添加has_many :things
,因为:tag
是{+ 1}}在Taggable上。
不要忘记丢弃旧的foreign_key:
belongs_to
更新您的规格!
<强>部署!强>