<div class="ui text container spacer-top-20 spacer-bottom-10"><h1 class="ui header centered">Migrating to UUIDs as Primary Keys</h1><h3 class="ui header centered">Loren Burton<span class="spacer-left-2 spacer-right-2">&bull;</span>July 17, 2016</h3><div class="spacer-bottom-10"></div><div class="ui divider"></div></div><div class="ui text blog-post-text container spacer-bottom-20"><p>I&#39;m working on a project and at the point where I want to add UUIDs.  Typically I maintain IDs for internal use and UUIDs for anything that may be exposed to the end user.  For this particular project, I was curious about using UUIDs as primary keys (instead of IDs), thus dropping IDs altogether.  </p>

<p>There&#39;s plenty of information out there for using primary key UUIDs with new projects, but not so much for migrating existing data.</p>

<h2 id="uuids-as-primary-keys-in-rails-5-with-postgresql">UUIDs as Primary keys in Rails 5 with PostgreSQL</h2>

<p>First, enable UUIDs for our database:</p>

<pre><code>rails g migration enable_uuid_extension
</code></pre>

<pre><code>class EnableUuidExtension &lt; ActiveRecord::Migration
  def change
    enable_extension &#39;uuid-ossp&#39;
  end
end
</code></pre>

<p>And for new tables (going forward):</p>

<pre><code>create_table :users, id: :uuid do |t|
  t.string :name
end
</code></pre>

<p>Rails 5 tip -  <code>application.rb</code> config to generate this tag by default:</p>

<pre><code>config.generators do |generator|
  generator.orm :active_record, primary_key_type: :uuid
end
</code></pre>

<h2 id="add-uuids-to-existing-tables">Add UUIDs to existing tables</h2>

<p>We want to keep <code>id</code> as our primary key, but with UUIDs instead of sequential integers.  For now we&#39;ll create temporary <code>uuid</code> columns:</p>

<pre><code>class AddUuidToEveryTable &lt; ActiveRecord::Migration
  def up
    tables = [
      &quot;categories&quot;,
      &quot;products&quot;,
      &quot;users&quot;
    ]

    tables.each do |table|
      add_column table, :uuid, :uuid, default: &quot;uuid_generate_v4()&quot;, null: false
    end
  end
end
</code></pre>

<h2 id="migrate-foreign-keys-to-uuids">Migrate Foreign Keys to UUIDs</h2>

<p>Associations between records have to be accounted for as well.  This can be a bit tricky, particularly with non-standard association names names.  There are probably gems to help with this, but I just hacked together a quick and dirty helper method.</p>

<p>The strategy here has three steps.  For each association: 1) write the association&#39;s <code>uuid</code> to a temporary foreign key <code>_uuid</code> column, 2) remove the <code>_id</code> column and 3) rename the <code>_uuid</code> column, effectively migrating our foreign keys to UUIDs while sticking with the <code>_id</code> convention.</p>

<pre><code>class ChangeForeignKeysToUuid &lt; ActiveRecord::Migration
  def up
    id_to_uuid(&quot;products&quot;, &quot;primary_category&quot;, &quot;category&quot;)
    id_to_uuid(&quot;products&quot;, &quot;secondary_category&quot;, &quot;category&quot;)
    id_to_uuid(&quot;products&quot;, &quot;user&quot;, &quot;user&quot;)
  end

  def id_to_uuid(table_name, relation_name, relation_class)
    table_name = table_name.to_sym
    klass = table_name.to_s.classify.constantize
    relation_klass = relation_class.to_s.classify.constantize
    foreign_key = &quot;#{relation_name}_id&quot;.to_sym
    new_foreign_key = &quot;#{relation_name}_uuid&quot;.to_sym

    add_column table_name, new_foreign_key, :uuid

    klass.where.not(foreign_key =&gt; nil).each do |record|
      if associated_record = relation_klass.find_by(id: record.send(foreign_key))
        record.update_column(new_foreign_key, associated_record.uuid)
      end
    end

    remove_column table_name, foreign_key
    rename_column table_name, new_foreign_key, foreign_key
  end
end
</code></pre>

<h2 id="migrate-primary-keys-to-uuids">Migrate Primary Keys to UUIDs</h2>

<p>Now that we have UUIDs and foreign key references to UUIDs, we can safely transition our primary keys to UUIDs:</p>

<pre><code>class ChangePrimaryKeysToUuid &lt; ActiveRecord::Migration
  def up
    tables = [
      &quot;categories&quot;,
      &quot;products&quot;,
      &quot;users&quot;
    ]

    tables.each do |table|
      remove_column table, :id
      rename_column table, :uuid, :id
      execute &quot;ALTER TABLE #{table} ADD PRIMARY KEY (id);&quot;
    end
  end
end
</code></pre>

<p>And lets not forget about our association indexes:</p>

<pre><code>class AddAssociationIndexes &lt; ActiveRecord::Migration
  def up
    add_index :products, :user_id
  end
end
</code></pre>

<h2 id="sorting">Sorting</h2>

<p>One thing to note is that sequential IDs are used for default sorting, but since we no longer have sequential IDs, we need to fix that.  A good solution is to use <code>created_at</code>:</p>

<pre><code>default_scope -&gt; { order(&quot;created_at ASC&quot;) }
</code></pre>

<p>And of course, indexes on <code>created_at</code> as well:</p>

<pre><code>class AddCreatedAtIndexes &lt; ActiveRecord::Migration
  def up
    add_index :categories, :created_at
    add_index :products, :created_at
    add_index :users, :created_at
  end
end
</code></pre>

<p>Now <code>User.first</code> and <code>User.last</code> query on <code>created_at</code> instead of <code>id</code>.</p>

<h2 id="done">Done!</h2>

<p>And there we go!  We&#39;ve completed our migration to UUIDs as primary keys and foreign keys, with no more sequential IDs.  Happy coding!</p>
</div><div class="ui text blog-post-text container spacer-bottom-10"><a href="posts/migrating-to-uuids-as-primary-keys#disqus_thread">Discuss this post</a></div><div class="ui text blog-post-text container spacer-bottom-10"><hr class="ui divider" /></div><div class="ui text blog-post-text container spacer-bottom-20"><h1 class="ui header">Read more:</h1><h3 class="ui header"><a href="/posts/migrating-to-uuids-as-primary-keys">Migrating to UUIDs as Primary Keys</a></h3><h3 class="ui header"><a href="/posts/kanye-west-startup-idol">Kanye West, Startup Idol</a></h3><h3 class="ui header"><a href="/posts/end-the-week-with-something">End the week with something</a></h3><h3 class="ui header"><a href="/posts/too-many-features">Too many features</a></h3><h3 class="ui header"><a href="/posts/failure">Failure</a></h3><h3 class="ui header"><a href="/posts/i-get-it-this-is-why-people-have-cofounders">I get it.  This is why people have cofounders.</a></h3><h3 class="ui header"><a href="/posts/migrating-a-production-postgressql-db-to-amazon-rds">Migrating a Production PostgresSQL DB to Amazon RDS</a></h3><h3 class="ui header"><a href="/posts/from-idea-to-validation-to-3-500-users">From Idea to Validation to 3,500 Users</a></h3><h3 class="ui header"><a href="/posts/github-for-writers">Github for Writers</a></h3><h3 class="ui header"><a href="/posts/build-a-life">Build A Life</a></h3><h3 class="ui header"><a href="/posts/interesting-experience-with-the-tsa">Interesting Experience With the TSA...</a></h3><h3 class="ui header"><a href="/posts/every-first-draft">Every First Draft</a></h3><h3 class="ui header"><a href="/posts/i-quit-my-job">I Quit My Job</a></h3><h3 class="ui header"><a href="/posts/do-things-tell-people">Do things, tell people.</a></h3><h3 class="ui header"><a href="/posts/i-can-t-spell-textmate">I can&#39;t spell &#39;Textmate&#39;</a></h3><h3 class="ui header"><a href="/posts/shapeoko-resources">Shapeoko Resources</a></h3><h3 class="ui header"><a href="/posts/elon-musk-s-hyperloop">Elon Musk&#39;s Hyperloop</a></h3><h3 class="ui header"><a href="/posts/rocketr-don-t-tell-a-story-answer-questions">Rocketr, don’t tell a story.  Answer questions. </a></h3><h3 class="ui header"><a href="/posts/why-instagram-is-worth-1-billion-or-more">Why Instagram Is Worth $1 Billion (Or More) </a></h3><h3 class="ui header"><a href="/posts/startup-founders-it-s-okay-to-take-a-break">Startup Founders: It&#39;s Okay To Take A Break</a></h3><h3 class="ui header"><a href="/posts/knowledge-empowers-people">Knowledge empowers people</a></h3><h3 class="ui header"><a href="/posts/sean-parker-solve-problems">Sean Parker - Solve Problems</a></h3><h3 class="ui header"><a href="/posts/every-app-should-be-this-easy">Every app should be this easy.</a></h3><h3 class="ui header"><a href="/posts/dane-maxwell-do-more-stuff">Dane Maxwell - Do More Stuff</a></h3><h3 class="ui header"><a href="/posts/the-4-hour-startup-marketing-it">The 4-Hour Startup: Marketing It</a></h3><h3 class="ui header"><a href="/posts/developers-free-project-idea-please-make-this">Developers: Free Project Idea. Please Make This.</a></h3><h3 class="ui header"><a href="/posts/the-4-hour-startup-building-it">The 4-Hour Startup: Building It</a></h3><h3 class="ui header"><a href="/posts/rss-is-live">RSS is live!</a></h3><h3 class="ui header"><a href="/posts/the-4-hour-startup-how-i-did-it">The 4-Hour Startup: How I Did It</a></h3></div>