在 Rails 中的每个模型上实现多个 UUID 和 ID 的最佳方法

Mat*_*man 8 ruby-on-rails ruby-on-rails-4

将大型遗留代码库从 UUID 转换为 ID。这需要分阶段完成,以保持许多设备之间的向后兼容性。

当前的解决方案是维护 UUID 和 ID 字段,直到我们可以完全过渡。

这样做的最佳方法是什么,以便所有belongs_to模型在每次创建/更新时都更新 ID 和 UUID?

示例:评论模型属于BlogPost,需要在创建/更新时同时设置blogpost_id& blogpost_uuid。

bli*_*iof 6

只需通过数据库即可:

假设您有这样的遗留表

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
Run Code Online (Sandbox Code Playgroud)

有了上面的代码,你有:

Legacy.create.id        # => "fb360410-0403-4388-9eac-c35f676f8368"
AnotherLegacy.create.id # => "dd45b2db-13c2-4ff1-bcad-3718cd119440"
Run Code Online (Sandbox Code Playgroud)

现在添加新的 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
Run Code Online (Sandbox Code Playgroud)

创建新列后会添加默认值,因为这会阻止数据库尝试更新所有记录。=> 默认值将是新记录的默认值。

您可以根据需要回填旧的。

例如一一

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) }
Run Code Online (Sandbox Code Playgroud)

如果需要,您可以先回填,然后添加默认值,然后再次回填。

当您对这些值感到满意时,只需更改主键即可:

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
Run Code Online (Sandbox Code Playgroud)
Legacy.first.id   # => 1
Legacy.first.uuid # => "fb360410-0403-4388-9eac-c35f676f8368"

AnotherLegacy.first.id   # => 1
AnotherLegacy.first.uuid # => "dd45b2db-13c2-4ff1-bcad-3718cd119440"
Run Code Online (Sandbox Code Playgroud)

最后,您需要再进行一次迁移以将主键更改为新 ID。

最重要的是避免停机:

  • 创建一个列
  • 确保以某种方式默认填充新记录(默认或触发器)
  • 回填旧记录
  • 添加约束
  • 切换到新列
  • 然后你可以放下旧的(如果你确定它没有被使用)

附:不确定为什么要完全从 uuid 切换,如果要从外部应用程序引用记录,它们会更好

ps.2.0。如果您需要能够做到Legacy.find("fb360410-0403-4388-9eac-c35f676f8368"),Legacy.find(123)也许可以尝试https://github.com/norman/friendly_id

friendly_id :uuid, use: [:slugged, :finders]
Run Code Online (Sandbox Code Playgroud)