Rails—Migrate a table (with data) to have UUID instead of ID - ruby-on-rails

I have a Project model, which has dependents, and there are already records in the db.
I want to change Project.id (it's primary key) to be a UUID, instead of just an incrementing integer.
How do I write a migration that will change the id, update existing records to have UUID, and update their references in other tables' foreign keys?

To migrate from default id to use uuid, try writing migration like this:
class ChangeProjectsPrimaryKey < ActiveRecord::Migration
def change
add_column :projects, :uuid, :uuid, default: "uuid_generate_v4()", null: false
change_table :projects do |t|
t.remove :id
t.rename :uuid, :id
end
execute "ALTER TABLE projects ADD PRIMARY KEY (id);"
end
end

Related

generate a model with a string field as primary key

I want to create a User model inside my Ruby on Rails application.
I use following command:
rails generate model User email:string name:string role:string
It is possible to define the email as primary key with this command? Or I must modify the database migration file that I create with this command? And how?
No, you can't. By default the primary key is an auto-increment integer.
However, you can open the migration that was generated from the command, and change it (before running the rake db:migrate command). The migration will likely have a create_table command:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
# ...
end
end
end
If you read the create_table documentation, you will notice you can pass two options. Specifically, you need to set :id to false to not generate an id field, and you will need to specify the name of the primary key field.
create_table :users, id: false, primary_key: :email do |t|
To add to #Simone Carletti's answer, you may need to use execute to set the primary key (if it's obscure). This would be especially true if you're modifying an existing table, which you're obviously not doing:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users, id: false do |t|
t.string :email, null: false
t.timestamps
end
execute "ALTER TABLE users ADD PRIMARY KEY (email);"
end
end
We use uuid's in some of our apps, and that's what we had to do (primary_key: :uuid didn't work)...
In Migration 6.0 or Rails 6
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users, id: false do |t|
t.string :email, null: false, primary_key: true
t.timestamps
end
end
end
In recent versions of ActiveRecord you can use --primary-key-type to specify the primary key type in the generate command:
rails generate model User email:string --primary-key-type=string
produces the migration:
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users, id: :string do |t|
t.string :email
t.timestamps
end
end
end
However, as far as I can tell there's still no way to change the name of the primary key column, except by hand.

Changing model id to uuid in Rails using uuid-ossp

I need to change the id of the existing model to uuid. I'm using this guide:
http://rny.io/rails/postgresql/2013/07/27/use-uuids-in-rails-4-with-postgresql.html
But no idea how to adapt the migration below to changing (not creating a new one):
class CreateDocuments < ActiveRecord::Migration
def change
create_table :documents, id: :uuid do |t|
t.string :title
t.string :author
t.timestamps
end
end
end
Try This.
class documents < ActiveRecord::Migration
def change
add_column :documents, :uuid, :uuid, default: "uuid_generate_v4()", null: false
change_table :documents do |t|
t.remove :id
t.rename :uuid, :id
end
execute "ALTER TABLE documents ADD PRIMARY KEY (id);"
end
end
source
Rails automatically handles uuid, so you just need to change id to uuid, set the column type (uuid for PGSQL, string for MYSQL) and repopulate the table with the new uuid.
In doing this myself, I've only ever changed the id column to uuid for Rails to populate it automatically.
$ rails g migration ChangeID
#db/migrate/change_id______.rb
class ChangeId < ActiveRecord::Migration
def change
rename_column :documents, :id, :uuid
change_column :documents, :uuid, :uuid #-> will only work for PGQL, will have to make it string for MYSQL
end
end
$ rake db:migrate
This will rename your :id column to uuid, assigning the respective column type to it.
And, yes, I've used :uuid before...
--
A good ref:
http://labria.github.io/2013/04/28/rails-4-postgres-uuid-pk-guide/

Adding a reference to a non-integer column in rails migration

I have studied several questions on this topic today. I know that, I can use t.references in migration to add a reference. But, If a table has non-integer primary key, how do I add reference to that column?
I have a table with this definition
create_table :sessions, id: false do |t|
t.string :session, primary_key: true, limit: 10
t.timestamps null: false
end
How do I add a reference to the session column (the name doesn't matter here), which is a string from another table migration. I tested with t.references but that just add an integer column. I know that I can use add column. But how to do that without from create_table method directly?
Clarification for duplicate flag
This question is flagged as duplication of this question, but it is actually not. Because I am not asking about setting up table with non-default non-integer primary key, because I already set up that table. I am asking about referencing this type of table from another table.
Rather than using the references helper, you would need to use the string type, and add an index for faster lookups. If your other model is "Record":
rails generate migration AddSessionIdToRecords session:string:index
Which should generate a migration like so:
def change
add_column :records, :session_id, :string
add_index :records, :session_id
end
Since you're using unconventional naming, you'd need to specify the primary key in the model and the relation definition:
class Session < ActiveRecord::Base
self.primary_key = "session"
has_many :records, primary_key: "session"
end
class Record < ActiveRecord::Base
belongs_to :session, primary_key: "session"
end
Since rails version 4.2.7 You can specify type into the add_reference helper.
Suppose you have table records and you want to have reference of sessions inside records. Then you can do the following :
add_reference :records, :sessions, type: :string, foreign_key: {to_table: :sessions, primary_key: :session}
Actually, you don't have to add primary_key in class Session & Record
def change
add_column :records, :session_id, :string
add_index :records, :session_id
add_foreign_key :records, :sessions, column: :session_id, primary_key: "your_primary_key_name_in_sessions_table"
end
and that will be all.
I've arrived at the conclusion that, without using foreign_key which is introduced in rails 4.2, I can't truly reference another column, only creating index as suggested by this answer from eirikir.
From rails 4.2, I can use foreign key like this
In session Table migration
create table :sessions, id: false do |t|
t.string "session", primary_key: true
t.timestamp null: false
end
For a record table to reference the session primary key
create table :records do |t|
t.string "session", null: false
..
.. # other attributes
end
add_index :records, :session
add_foreign_key :records, :sessions, column: :session, primary_key: :session
Here, the column: option fixes, on which column i want to create the foreign key and the primary_key: option fixes, the primary key of the referenced table, which is here session.

How to migrate from using one foreign key to another in ActiveRecord?

Using Postgres as the backing store, I have a table which (at least for the time being) has both an integer primary key and a uuid with a unique index.
It looks something like this in my schema.rb (simplified for example):
create_table "regions", force: cascade do |t|
t.integer "region_id"
t.uuid "uuid", default: "uuid_generate_v4()"
t.string "name"
end
add_index "regions", ["uuid"], name "index_regions_on_uuid", unique: true, using :btree
I then have a table which has a reference to the integer id, something like this:
create_table "sites", force:cascade do
t.integer "site_id"
t.integer "region_id"
t.string "name"
end
What I want to do is to switch from region_id to uuid as the foreign key in the second table. How should I god about writing this migration?
Just create a migration, and inhale some SQL magic into it:
def up
# Create and fill in region_uuid column,
# joining records via still existing region_id column
add_column :sites, :region_uuid
if Site.reflect_on_association(:region).foreign_key == 'region_id'
# We won't use 'joins(:regions)' in case we will need
# to re-run migration later, when we already changed association
# code as suggested below. Specifying join manually instead.
Site.joins("INNER JOIN regions ON site.region_id = regions.id").update_all("region_uuid = regions.uuid")
end
drop_column :sites, :region_id
end
Then you just need to fix your association:
class Site < ActiveRecord::Base
belongs_to :region, primary_key: :uuid, foreign_key: :region_uuid
end
class Region < ActiveRecord::Base
has_many :sites, primary_key: :uuid, foreign_key: :region_uuid
end
From your comment, its seems that you want to modify the primary key referenced by the association, not the foreign key. You actually don't need a migration to do this. Instead, just specify the primary key on the association definitions in each model:
Class Region << ActiveRecord::Base
has_many :sites, primary_key: :uuid
end
Class Site << ActiveRecord::Base
belongs_to :region, primary_key: :uuid
end
The foreign key, since it follows rails convention of being named as the belongs_to relation with an appended "_id" (in this case, region_id), does not need to be specified here.
ETA: You will also need to ensure that the type of sites.region_id matches the type of regions.uuid, which I assume is uuid. I'm also going to assume that this field was previously indexed (under ActiveRecord convention) and that you still want it indexed. You can change all this in a migration like so:
def up
remove_index :sites, :region_id
change_column :sites, :region_id, :uuid
add_index :sites, :region_id
end
def down
remove_index :sites, :region_id
change_column :sites, :region_id, :integer
add_index :sites, :region_id
end

Rails reference a model with a non integer primary key

I have a table whose migration is this:
class CreateClient < ActiveRecord::Migration
def change
create_table :clients, :primary_key => :tag do |t|
t.string :name
end
change_column :clients, :tag, :string, limit: 4
end
end
so the primary key as you can see is that tag field which is a varchar(4), now I need to reference that field from another table.
I tried in the migration to create the reference in this way:
t.references :client, index: true
but the resulting table has a field called client_id which is int, should I pass any parameter to the references method so that it will create the field as varchar?
Thank you in advance
I believe you can use execute to force rails into setting the primary key in the way you want:
class CreateClient < ActiveRecord::Migration
def change
create_table :clients, :id => false do |t|
t.string :name
end
execute "ALTER TABLE clients ADD PRIMARY KEY (name);"
And then set do set_primary_key :name in your Client model

Resources