Newer versions of rails let you specify that tables should be created with a uuid primary key like so:
create_table :foos, id: :uuid do |t|
# ...
end
Which is great. And for a long time rails has supported creating join tables like so:
create_join_table :foos, :bars do |t|
# ...
end
Also great. Except my tables have uuid primary keys and that generates foreign key columns of type integer instead of type uuid.
Looking over the documentation for create_join_table, I can't find anything obvious to change the column type. Is it possible to use create_join_table with uuids?
Or do I have create the join table manually:
create_table :bars_foos, id: false do |t|
t.uuid :bar_id
t.uuid :foo_id
end
Within Rails 5.0 you can use an additional option column_options on the create_join_table method to specify the type of your id columns. Your migration would then look like:
create_join_table :foos, :bars, column_options: {type: :uuid} do |t|
t.index [:foo_id, :baar_id]
end
I should have looked at the code...
def create_join_table(table_1, table_2, options = {})
join_table_name = find_join_table_name(table_1, table_2, options)
column_options = options.delete(:column_options) || {}
column_options.reverse_merge!(null: false)
t1_column, t2_column = [table_1, table_2].map{ |t| t.to_s.singularize.foreign_key }
create_table(join_table_name, options.merge!(id: false)) do |td|
td.integer t1_column, column_options
td.integer t2_column, column_options
yield td if block_given?
end
end
Columns are explicitly created as integers with no means to change them. Too bad...
There is no way of creating join tables with uuids.
As pointed out in the question create_table is the only option. The best way of emulating create_join_tables with uuid is by using create_tables as follows:
Run: rails g migration CreateFoosBars bars:references foos:references
The command will produce the following output which you will need to modify
generate output
class CreateBarFoos < ActiveRecord::Migration
def change
create_table :bars_foos, id: :uuid do |t|
t.references :bars, foreign_key: true
t.references :foo, foreign_key: true
end
end
end
Change id: uuid => id: false
Add type: uuid, index: true to the end of the references
final migration
class CreateBarFoos < ActiveRecord::Migration
def change
create_table :bars_foos, id: false do |t|
t.references :bars, foreign_key: true, type: :uuid, index: true
t.references :foo, foreign_key: true, type: :uuid, index: true
end
end
end
It would be good if Rails could add extra support for different id types in create_join_table, this could even be inferred by an existing migration.
Until then hopefully these steps will achieve the same result.
Related
I am trying to add a unique index that gets created from the foreign keys of four associated tables:
add_index :studies,
["user_id", "university_id", "subject_name_id", "subject_type_id"],
:unique => true
The database’s limitation for the index name causes the migration to fail. Here’s the error message:
Index name 'index_studies_on_user_id_and_university_id_and_subject_name_id_and_subject_type_id' on table 'studies' is too long; the limit is 64 characters
How can I handle this? Can I specify a different index name?
Provide the :name option to add_index, e.g.:
add_index :studies,
["user_id", "university_id", "subject_name_id", "subject_type_id"],
unique: true,
name: 'my_index'
If using the :index option on references in a create_table block, it takes the same options hash as add_index as its value:
t.references :long_name, index: { name: :my_index }
You can change the index name in column definitions within a create_table block (such as you get from the migration generator).
create_table :studies do |t|
t.references :user, index: {:name => "index_my_shorter_name"}
end
In PostgreSQL, the default limit is 63 characters. Because index names must be unique it's nice to have a little convention. I use (I tweaked the example to explain more complex constructions):
def change
add_index :studies, [:professor_id, :user_id], name: :idx_study_professor_user
end
The normal index would have been:
:index_studies_on_professor_id_and_user_id
The logic would be:
index becomes idx
Singular table name
No joining words
No _id
Alphabetical order
Which usually does the job.
You can also do
t.index([:branch_id, :party_id], unique: true, name: 'by_branch_party')
as in the Ruby on Rails API.
Similar to the previous answer: Just use the 'name' key with your regular add_index line:
def change
add_index :studies, :user_id, name: 'my_index'
end
I'm afraid none of these solutions worked for me. Perhaps because I was using belongs_to in my create_table migration for a polymorphic association.
I'll add my code below and a link to the solution that helped me in case anyone else stumbles upon when searching for 'Index name is too long' in connection with polymorphic associations.
The following code did NOT work for me:
def change
create_table :item_references do |t|
t.text :item_unique_id
t.belongs_to :referenceable, polymorphic: true
t.timestamps
end
add_index :item_references, [:referenceable_id, :referenceable_type], name: 'idx_item_refs'
end
This code DID work for me:
def change
create_table :item_references do |t|
t.text :item_unique_id
t.belongs_to :referenceable, polymorphic: true, index: { name: 'idx_item_refs' }
t.timestamps
end
end
This is the SO Q&A that helped me out: https://stackoverflow.com/a/30366460/3258059
I have a project that uses generators a lot and needed this to be automatic, so I copied the index_name function from the rails source to override it. I added this in config/initializers/generated_index_name.rb:
# make indexes shorter for postgres
require "active_record/connection_adapters/abstract/schema_statements"
module ActiveRecord
module ConnectionAdapters # :nodoc:
module SchemaStatements
def index_name(table_name, options) #:nodoc:
if Hash === options
if options[:column]
"ix_#{table_name}_on_#{Array(options[:column]) * '__'}".slice(0,63)
elsif options[:name]
options[:name]
else
raise ArgumentError, "You must specify the index name"
end
else
index_name(table_name, index_name_options(options))
end
end
end
end
end
It creates indexes like ix_assignments_on_case_id__project_id and just truncates it to 63 characters if it's still too long. That's still going to be non-unique if the table name is very long, but you can add complications like shortening the table name separately from the column names or actually checking for uniqueness.
Note, this is from a Rails 5.2 project; if you decide to do this, copy the source from your version.
I had this issue, but with the timestamps function. It was autogenerating an index on updated_at that exceeded the 63 character limit:
def change
create_table :toooooooooo_loooooooooooooooooooooooooooooong do |t|
t.timestamps
end
end
Index name 'index_toooooooooo_loooooooooooooooooooooooooooooong_on_updated_at' on table 'toooooooooo_loooooooooooooooooooooooooooooong' is too long; the limit is 63 characters
I tried to use timestamps to specify the index name:
def change
create_table :toooooooooo_loooooooooooooooooooooooooooooong do |t|
t.timestamps index: { name: 'too_loooooooooooooooooooooooooooooong_updated_at' }
end
end
However, this tries to apply the index name to both the updated_at and created_at fields:
Index name 'too_long_updated_at' on table 'toooooooooo_loooooooooooooooooooooooooooooong' already exists
Finally I gave up on timestamps and just created the timestamps the long way:
def change
create_table :toooooooooo_loooooooooooooooooooooooooooooong do |t|
t.datetime :updated_at, index: { name: 'too_long_on_updated_at' }
t.datetime :created_at, index: { name: 'too_long_on_created_at' }
end
end
This works but I'd love to hear if it's possible with the timestamps method!
create_table :you_table_name do |t|
t.references :studant, index: { name: 'name_for_studant_index' }
t.references :teacher, index: { name: 'name_for_teacher_index' }
end
So I read this question, answer and the comments, but it doesn't answer my case, which is what to do when of the columns is a foreign key?
Here is my original migration to create the table in question:
class CreateTemplates < ActiveRecord::Migration[5.1]
def change
create_table :templates, id: :uuid do |t|
t.references :account, type: :uuid, foreign_key: true
t.string :name
t.text :info
t.string :title
t.timestamps
end
end
end
Since account_id is a foreign_key (and identifies the customer) it will appear in almost all (99%) of queries on this table.
Now it has been decided that name should be unique to account, so the model has been updated:
validates_uniqueness_of :name, scope: [:account]
So once I add the joint index:
add_index :templates, [:name, :account_id], unique: true
should I delete the index on account_id?
I ask because in SQLLite (see this), it seems the answer would be that I don't need the single index on account_id and to create my new index with account_id in the first position:
add_index :templates, [:account_id, :name], unique: true
I'm using postgres, so does the same idea apply?
You have to add extra index if it's not the first index.
So if you have this:
add_index :templates, [:name, :account_id], unique: true
then you should not delete the original :account_id foreign key index, since it is second index.
I recommend you to read about index implementations. It's pretty interesting and you can learn a lot from it.
What is the proper way to create a table in rails via a migration in which the primary key is a string instead of an int?
I've tried setting primary_key as #oldergod suggested in the answer below but baz seems to get set to an int still:
class CreateFoos < ActiveRecord::Migration
def change
create_table :foos, primary_key: 'baz' do |t|
end
end
end
UPDATE
I've since tried
class CreateFoos < ActiveRecord::Migration
def change
create_table :foos, primary_key: false do |t|
t.string :baz
end
end
end
which gets me a little closer but still missing the PRIMARY index on the column. I've tried add_index :foos, :baz, type: :primary but this generates the following error:
SQLite3::SQLException: near "primary": syntax error: CREATE primary INDEX "index_foos_on_baz" ON "foos" ("baz")/Users/kyledecot/.rvm/gems/ruby-1.9.3-p392/gems/sqlite3-1.3.8/lib/sqlite3/database.rb:91:in `initialize'
It seems like this should work after looking at http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_index_options
What's different if it's a string? See the create table doc.
create_table :foos, primary_key: 'baz' do |t|
t.column :baz, :string
end
Also note that this just sets the primary key in the table. You
additionally need to configure the primary key in the model via
self.primary_key=. Models do NOT auto-detect the primary key from
their table definition.
I am trying to add a unique index that gets created from the foreign keys of four associated tables:
add_index :studies,
["user_id", "university_id", "subject_name_id", "subject_type_id"],
:unique => true
The database’s limitation for the index name causes the migration to fail. Here’s the error message:
Index name 'index_studies_on_user_id_and_university_id_and_subject_name_id_and_subject_type_id' on table 'studies' is too long; the limit is 64 characters
How can I handle this? Can I specify a different index name?
Provide the :name option to add_index, e.g.:
add_index :studies,
["user_id", "university_id", "subject_name_id", "subject_type_id"],
unique: true,
name: 'my_index'
If using the :index option on references in a create_table block, it takes the same options hash as add_index as its value:
t.references :long_name, index: { name: :my_index }
You can change the index name in column definitions within a create_table block (such as you get from the migration generator).
create_table :studies do |t|
t.references :user, index: {:name => "index_my_shorter_name"}
end
In PostgreSQL, the default limit is 63 characters. Because index names must be unique it's nice to have a little convention. I use (I tweaked the example to explain more complex constructions):
def change
add_index :studies, [:professor_id, :user_id], name: :idx_study_professor_user
end
The normal index would have been:
:index_studies_on_professor_id_and_user_id
The logic would be:
index becomes idx
Singular table name
No joining words
No _id
Alphabetical order
Which usually does the job.
You can also do
t.index([:branch_id, :party_id], unique: true, name: 'by_branch_party')
as in the Ruby on Rails API.
Similar to the previous answer: Just use the 'name' key with your regular add_index line:
def change
add_index :studies, :user_id, name: 'my_index'
end
I'm afraid none of these solutions worked for me. Perhaps because I was using belongs_to in my create_table migration for a polymorphic association.
I'll add my code below and a link to the solution that helped me in case anyone else stumbles upon when searching for 'Index name is too long' in connection with polymorphic associations.
The following code did NOT work for me:
def change
create_table :item_references do |t|
t.text :item_unique_id
t.belongs_to :referenceable, polymorphic: true
t.timestamps
end
add_index :item_references, [:referenceable_id, :referenceable_type], name: 'idx_item_refs'
end
This code DID work for me:
def change
create_table :item_references do |t|
t.text :item_unique_id
t.belongs_to :referenceable, polymorphic: true, index: { name: 'idx_item_refs' }
t.timestamps
end
end
This is the SO Q&A that helped me out: https://stackoverflow.com/a/30366460/3258059
I have a project that uses generators a lot and needed this to be automatic, so I copied the index_name function from the rails source to override it. I added this in config/initializers/generated_index_name.rb:
# make indexes shorter for postgres
require "active_record/connection_adapters/abstract/schema_statements"
module ActiveRecord
module ConnectionAdapters # :nodoc:
module SchemaStatements
def index_name(table_name, options) #:nodoc:
if Hash === options
if options[:column]
"ix_#{table_name}_on_#{Array(options[:column]) * '__'}".slice(0,63)
elsif options[:name]
options[:name]
else
raise ArgumentError, "You must specify the index name"
end
else
index_name(table_name, index_name_options(options))
end
end
end
end
end
It creates indexes like ix_assignments_on_case_id__project_id and just truncates it to 63 characters if it's still too long. That's still going to be non-unique if the table name is very long, but you can add complications like shortening the table name separately from the column names or actually checking for uniqueness.
Note, this is from a Rails 5.2 project; if you decide to do this, copy the source from your version.
I had this issue, but with the timestamps function. It was autogenerating an index on updated_at that exceeded the 63 character limit:
def change
create_table :toooooooooo_loooooooooooooooooooooooooooooong do |t|
t.timestamps
end
end
Index name 'index_toooooooooo_loooooooooooooooooooooooooooooong_on_updated_at' on table 'toooooooooo_loooooooooooooooooooooooooooooong' is too long; the limit is 63 characters
I tried to use timestamps to specify the index name:
def change
create_table :toooooooooo_loooooooooooooooooooooooooooooong do |t|
t.timestamps index: { name: 'too_loooooooooooooooooooooooooooooong_updated_at' }
end
end
However, this tries to apply the index name to both the updated_at and created_at fields:
Index name 'too_long_updated_at' on table 'toooooooooo_loooooooooooooooooooooooooooooong' already exists
Finally I gave up on timestamps and just created the timestamps the long way:
def change
create_table :toooooooooo_loooooooooooooooooooooooooooooong do |t|
t.datetime :updated_at, index: { name: 'too_long_on_updated_at' }
t.datetime :created_at, index: { name: 'too_long_on_created_at' }
end
end
This works but I'd love to hear if it's possible with the timestamps method!
create_table :you_table_name do |t|
t.references :studant, index: { name: 'name_for_studant_index' }
t.references :teacher, index: { name: 'name_for_teacher_index' }
end
What I need is a migration to apply unique constraint to a combination of columns. i.e. for a people table, a combination of first_name, last_Name and Dob should be unique.
add_index :people, [:firstname, :lastname, :dob], unique: true
According to howmanyofme.com, "There are 46,427 people named John Smith" in the United States alone. That's about 127 years of days. As this is well over the average lifespan of a human being, this means that a DOB clash is mathematically certain.
All I'm saying is that that particular combination of unique fields could lead to extreme user/customer frustration in future.
Consider something that's actually unique, like a national identification number, if appropriate.
(I realise I'm very late to the party with this one, but it could help future readers.)
You may want to add a constraint without an index. This will depend on what database you're using. Below is sample migration code for Postgres. (tracking_number, carrier) is a list of the columns you want to use for the constraint.
class AddUniqeConstraintToShipments < ActiveRecord::Migration
def up
execute <<-SQL
alter table shipments
add constraint shipment_tracking_number unique (tracking_number, carrier);
SQL
end
def down
execute <<-SQL
alter table shipments
drop constraint if exists shipment_tracking_number;
SQL
end
end
There are different constraints you can add. Read the docs
For completeness sake, and to avoid confusion here are 3 ways of doing the same thing:
Adding a named unique constraint to a combination of columns in Rails 5.2+
Let's say we have Locations table that belongs to an advertiser and has column reference_code and you only want 1 reference code per advertiser. so you want to add a unique constraint to a combination of columns and name it.
Do:
rails g migration AddUniquenessConstraintToLocations
And make your migration look either something like this one liner:
class AddUniquenessConstraintToLocations < ActiveRecord::Migration[5.2]
def change
add_index :locations, [:reference_code, :advertiser_id], unique: true, name: 'uniq_reference_code_per_advertiser'
end
end
OR this block version.
class AddUniquenessConstraintToLocations < ActiveRecord::Migration[5.2]
def change
change_table :locations do |t|
t.index ['reference_code', 'advertiser_id'], name: 'uniq_reference_code_per_advertiser', unique: true
end
end
end
OR this raw SQL version
class AddUniquenessConstraintToLocations < ActiveRecord::Migration[5.2]
def change
execute <<-SQL
ALTER TABLE locations
ADD CONSTRAINT uniq_reference_code_per_advertiser UNIQUE (reference_code, advertiser_id);
SQL
end
end
Any of these will have the same result, check your schema.rb
Hi You may add unique index in your migration to the columns for example
add_index(:accounts, [:branch_id, :party_id], :unique => true)
or separate unique indexes for each column
In the typical example of a join table between users and posts:
create_table :users
create_table :posts
create_table :ownerships do |t|
t.belongs_to :user, foreign_key: true, null: false
t.belongs_to :post, foreign_key: true, null: false
end
add_index :ownerships, [:user_id, :post_id], unique: true
Trying to create two similar records will throw a database error (Postgres in my case):
ActiveRecord::RecordNotUnique: PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "index_ownerships_on_user_id_and_post_id"
DETAIL: Key (user_id, post_id)=(1, 1) already exists.
: INSERT INTO "ownerships" ("user_id", "post_id") VALUES ($1, $2) RETURNING "id"
e.g. doing that:
Ownership.create!(user_id: user_id, post_id: post_id)
Ownership.create!(user_id: user_id, post_id: post_id)
Fully runnable example: https://gist.github.com/Dorian/9d641ca78dad8eb64736173614d97ced
db/schema.rb generated: https://gist.github.com/Dorian/a8449287fa62b88463f48da986c1744a
If you are creating a new table just add unique: true
class CreatePosts < ActiveRecord::Migration[6.0]
def change
create_table :posts do |t|
t.string :title, unique: true
t.text :body
t.references :user, foreign_key: true
t.timestamps
end
add_index :posts, :user_id, unique: true
end
end