将一个大型遗留代码库从UUID转换为ID。这需要分阶段进行,以保持对许多设备的向后兼容性。
目前的解决方案是保留UUID和ID字段,直到我们可以完全过渡为止。
最佳方法是什么,以便所有的belongs_to
模型在每次创建/更新时都更新ID和UUID?
例如:评论模型属于BlogPost并且需要在创建/更新时设置blogpost_id
和blogpost_uuid
。
将一个大型遗留代码库从UUID转换为ID。这需要分阶段进行,以保持对许多设备的向后兼容性。
目前的解决方案是保留UUID和ID字段,直到我们可以完全过渡为止。
最佳方法是什么,以便所有的belongs_to
模型在每次创建/更新时都更新ID和UUID?
例如:评论模型属于BlogPost并且需要在创建/更新时设置blogpost_id
和blogpost_uuid
。
只需通过数据库完成:
假设您有这样的遗留表格
class CreateLegacy < ActiveRecord::Migration
def change
enable_extension 'uuid-ossp'
create_table :legacies, id: :uuid do |t|
t.timestamps
end
create_table :another_legacies, id: false do |t|
t.uuid :uuid, default: 'uuid_generate_v4()', primary_key: true
t.timestamps
end
end
end
class Legacy < ActiveRecord::Base
end
class AnotherLegacy < ActiveRecord::Base
self.primary_key = 'uuid'
end
使用上述代码,您可以获得:
Legacy.create.id # => "fb360410-0403-4388-9eac-c35f676f8368"
AnotherLegacy.create.id # => "dd45b2db-13c2-4ff1-bcad-3718cd119440"
现在添加新的id列
class AddIds < ActiveRecord::Migration
def up
add_column :legacies, :new_id, :bigint
add_index :legacies, :new_id, unique: true
add_column :another_legacies, :id, :bigint
add_index :another_legacies, :id, unique: true
execute <<-SQL
CREATE SEQUENCE legacies_new_id_seq;
ALTER SEQUENCE legacies_new_id_seq OWNED BY legacies.new_id;
ALTER TABLE legacies ALTER new_id SET DEFAULT nextval('legacies_new_id_seq');
CREATE SEQUENCE another_legacies_id_seq;
ALTER SEQUENCE another_legacies_id_seq OWNED BY another_legacies.id;
ALTER TABLE another_legacies ALTER id SET DEFAULT nextval('another_legacies_id_seq');
SQL
end
def down
remove_column :legacies, :new_id
remove_column :another_legacies, :id
end
end
Legacy.where(new_id: nil).find_each { |l| l.update_column(:new_id, ActiveRecord::Base.connection.execute("SELECT nextval('legacies_new_id_seq')")[0]['nextval'].to_i) }
AnotherLegacy.where(id: nil).find_each { |l| l.update_column(:id, ActiveRecord::Base.connection.execute("SELECT nextval('another_legacies_id_seq')")[0]['nextval'].to_i) }
class Legacy < ActiveRecord::Base
self.primary_key = 'new_id'
def uuid
attributes['id']
end
end
class AnotherLegacy < ActiveRecord::Base
self.primary_key = 'id' # needed as we have not switched the PK in the db
end
Legacy.first.id # => 1
Legacy.first.uuid # => "fb360410-0403-4388-9eac-c35f676f8368"
AnotherLegacy.first.id # => 1
AnotherLegacy.first.uuid # => "dd45b2db-13c2-4ff1-bcad-3718cd119440"
最后,您需要进行另一个迁移以将主键更改为新的ID。
最重要的是避免停机时间:
注:不确定为什么您要完全从UUID切换,如果您想要从外部应用程序引用记录,则UUID更好。
注2.0:如果您需要能够执行Legacy.find("fb360410-0403-4388-9eac-c35f676f8368")
和Legacy.find(123)
,也许可以尝试https://github.com/norman/friendly_id
friendly_id :uuid, use: [:slugged, :finders]
ALTER TABLE Test MODIFY COLUMN id INT UNIQUE NOT NULL AUTO_INCREMENT;
,也可以使用CREATE TRIGGER test_uuid_generator BEFORE INSERT ON Test FOR EACH ROW SET new.uuid = IF(new.uuid IS NOT NULL, new.uuid, UUID());
为uuid创建触发器。 - bliofclass Blogpost
self.primary_keys = :uuid, :id
has_many :comments, foreign_key: [:uuid, :id]
end
class Comment
belongs_to :blogpost, foreign_key: [:blogpost_uuid, :blogpost_id]
end
如果您已经为BlogPost生成了UUID和ID并与Comment的blogpost_uuid
,blogpost_id
同步,则此方法可行。
如果您还没有同步blogpost_uuid
和blogpost_id
,我建议您执行以下操作进行迁移:
uuid
复制到Comment的blogpost_uuid
,您可以执行以下操作:Comment.preload(:blogpost).find_each do |comment|
comment.update_column(blogpost_uuid: blogpost.uuid)
end
希望这能帮助您顺利过渡。如果有不清楚的地方,请告诉我。
before_save
回调函数,该函数在模型创建和更新时被调用。在回调方法中,您可以引用关联并确保在评论中更新必要的字段。# app/models/comment.rb
belongs_to :blogpost
# Add callback, gets called before create and update
before_save :save_blogpost_id_and_uuid
# At the bottom of your model
private
def save_blogpost_id_and_uuid
# You usually don't have to explicitly set the blogpost_id
# because Rails usually handles it. But you might have to
# depending on your app's implementation of UUIDs. Although it's
# probably safer to explicitly set them just in case.
self.blogpost_uuid = blogpost.uuid
self.blogpost_id = blogpost.id
end
然后,对于其他模型及其关联,重复上述方法。
如果需要,您可以添加一些条件逻辑,仅在博客文章ID或UUID更改时更新blogpost_id
和blogpost_uuid
。
comments.blogpost_id
和blogposts.id
以及comments.blogpost_uuid
和blogposts.uuid
之间建立外键。这个设置将确保在删除整数列后,你的数据库内容保持一致。self.blogpost_id ||= BlogPost.find_by(uuid: blogpost_uuid)&.id if blogpost_uuid.present?
self.blogpost_uuid ||= BlogPost.find_by(id: blogpost_id)&.uuid if blogpost_id.present?
或者你可以再次让你的DBMS完成它的工作,并设置触发器来处理INSERT
/UPDATE
。这是我会采取的方式,因为它可以增加一致性并将偶然的临时复杂性排除在应用程序代码之外(如果您想要,仍然可以编写单元测试)。
第三步是回填任何现有数据,并在涉及外键关系的所有列上设置NOT NULL
约束,以确保完全一致性。
希望这有意义。如果您有任何后续问题,请告诉我。