I would like to run the following migration to my Rails(4.x) app but would like to get some feedback from the Rails community first.
class AddUniqueIndexToSchemaMigrations < ActiveRecord::Migration
def change
add_column :schema_migrations, :id, :primary_key
add_index :schema_migrations, ["version"], :name => "unique_versions", :unique => true
end
end
The database in use is MySQL (5.x). Adding this index allows me execute SQL such as:
INSERT INTO schema_migrations
(version)
VALUES ( '2014xxxxxx' )
ON DUPLICATE KEY
UPDATE foo=bar; // or some other bit of cleverness
Is there any reason why this is going off the rails? I don't want to create a situation where I'm fighting against Rails internals.
Thanks in advance!
Related
Im using Rails 5.1 and SQLite. The below migration is not working as expected.
class AddJobTitleForeignKeyToTimeOffTypes < ActiveRecord::Migration[5.1]
def change
add_column :time_off_types, :job_title_id, :integer
add_foreign_key :time_off_types, :job_title, :column => :job_title_id
end
end
It creates the column "job_title_id" in the table "time_off_types" but it does not create the foreign key.
ActiveRecord's add_foreign_key method is used outside table creation, hence uses ALTER TABLE ... ADD CONSTRAINT ....
SQLite's ALTER TABLE does not support adding constraints (of any kind). (It is worth reviewing, as ALTER TABLE in SQLite might be more limited than you expect. For example, SQLite < 3.25.0 cannot rename columns either.)
However, SQLite CREATE TABLE does support foreign key constraints and the ActiveRecord migration create_table #references method can create them:
def change
create_table :pets do
t.references :owner, foreign_key: true
...
end
end
The Rails Migrations Guide does not mention this difference.
So how does this work?
ActiveRecord database adapters have two methods: supports_foreign_keys? and supports_foreign_keys_in_create? which are both false by default (see the Rails API documentation).
add_foreign_key returns immediately unless supports_foreign_keys? is true, but it is false for SQLite, so end of the trail for add_foreign_key.
On the other hand, supports_foreign_keys_in_create? is true for SQLite >= 3.6.19, which allows the #references method to create the foreign key using CREATE TABLE ....
(I've linked to Rails 5.1 code, as that's what you were using at the time of the question, but this all remains true as of Rails 5.2.1 today.)
I'm on a git branch and trying to rollback two migrations, before destroying the branch. The most recent migration adds a column to a table that I want to keep (and which is part of master, not the branch to be dropped), so dropping the whole table is not a solution (unless I have to recreate it again). Anyways, I must have done something wrong, because when I tried to remove the apple_id column from the scores table, I got this abort error.
This is the migration that I'm trying to rollback
add_column :scores, :apple_id, :integer
However, the error message (see below) is referring to the indexes that I created with the original migration (part of master branch) that created the table
add_index :scores, [:user_id, :created_at, :funded, :started]
Can you suggest what I might do?
== AddAppleidColumnToScores: reverting =======================================
-- remove_column("scores", :apple_id)
rake aborted!
An error has occurred, this and all later migrations canceled:
Index name 'temp_index_altered_scores_on_user_id_and_created_at_and_funded_and_started' on table 'altered_scores' is too long; the limit is 64 characters
Update: reading this SO question How do I handle too long index names in a Ruby on Rails migration with MySQL?, I got some more information about the source of the problem but don't know how to solve it. Both sql and postgres have 64 character limits
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
The accepted answer for the question I refer to says to give the index a name, although I'm not sure how I could do this now that I'm trying to rollback.
add_index :studies, ["user_id", "university_id", \
"subject_name_id", "subject_type_id"],
:unique => true, :name => 'my_index'
Update: in response to the comments, I'm using Rails 3.2.12. This is the migration that adds the column
class AddAppleidColumnToScores < ActiveRecord::Migration
def change
add_column :scores, :apple_id, :integer
end
end
Furthermore, the reason why I didn't want to drop the table was that I was unsure about what problems it might cause in recreating it since a) the main part was created on branch master, while a column added on a branch and b) I was unsure about what to do with the migration file for the dropped table? since it was the fourth (of about 10) tables I created, I don't know how to run it and only it again.
Can you copy/paste your migration?
Here's mine:
class AddAppleIdColumnToScores < ActiveRecord::Migration
def self.up
add_column :scores, :apple_id, :integer
add_index :scores, [:user_id, :created_at, :funded, :started]
end
def self.down
# delete index MySql way
# execute "DROP INDEX index_scores_on_user_id_and_created_at_and_funded_and_started ON scores"
# delete index Postgresql way
# execute "DROP INDEX index_scores_on_user_id_and_created_at_and_funded_and_started"
# delete index Rails way / not necessary if you remove the column
# remove_index :scores, [:user_id, :created_at, :funded, :started]
remove_column :scores, :apple_id
end
end
I have a Rails app, where one of the models does not have the id column. Doing some research I found the migration that created it:
create_table(:the_model, :id => false) do |t|
# columns
end
Now, on a new migration, I want to add the id column in a Rails standard way (not using database specific sql). How can I do that?
I already tried this without success:
change_table(:the_model, :id => true) do |t|
end
You can either manually add the id column:
add_column :table_name, :id, :primary_key
or clear (or backup) your data, rollback to this migration, get rid of the option :id => false, and re-migrate.
You don't need to mention :integer
rails g migration add_id_to_model id:primary_key worked for me.
You already got your answer, but here is a one liner that does all in this case
rails generate migration AddIdToModel id:integer
Look at the syntax of migration file name AddColumnNameToTableName followed the column description.
It will generate something like below
class AddIdToModel < ActiveRecord::Migration
def change
add_column :models, :id, :integer
end
end
Now you can change this line if you feel for anything else. and just run rake db:migrate.
Is there any difference between using t.references and executing SQL command to create foreign key relationship between products and category table as shown below? In other words, are the two different ways of doing the same thing or am I missing anything here?
class ExampleMigration < ActiveRecord::Migration
def up
create_table :products do |t|
t.references :category
end
#add a foreign key
execute <<-SQL
ALTER TABLE products
ADD CONSTRAINT fk_products_categories
FOREIGN KEY (category_id)
REFERENCES categories(id)
SQL
add_column :users, :home_page_url, :string
rename_column :users, :email, :email_address
end
def down
rename_column :users, :email_address, :email
remove_column :users, :home_page_url
execute <<-SQL
ALTER TABLE products
DROP FOREIGN KEY fk_products_categories
SQL
drop_table :products
end
end
They're not the same thing. Rails by default doesn't enforce foreign keys in the database. Instead, references when creating from the command line also creates a regular index, like this:
add_index :products, :category_id
Update:
Rails 5 actually does exactly the same thing now. So, to answer the original question: Nowadays, both are the same.
I found some thing intresting in this page.
http://railsforum.com/viewtopic.php?id=17318
From a comment :
Rails doesn't use foreign keys to perform his backend tasks. This
because some db like sqlite doesn't allow foreign keys on its tables.
So Rails doesn't provide an helper to build a foreign key
Also there is a gem foreigner for adding foreign keys to database table.
What creates the FOREIGN KEY constraint in Ruby on Rails 3?
When I created the table users, the resulting table had a column called id, defined as an integer.
I tried to modify it to bigint, unsigned as follows:
change_column :users, :id, :integer, :limit => 8, :unsigned => true
Which did change it to bigint, but it was no longer an auto-increment column (though it was still identified as the primary index, and it was not set to unsigned (even though rails told me that the migration executed fine)
I then tried doing:
change_column :users, :id, :integer, :limit => 8, :unsigned => true, :null => false, :auto_increment => true
Rails said that the migration executed fine, but nothing changed.
I could try something like:
change_column :users, :id, :primary_index
but that would put me right where I started
I could also try an "execute" statement with MySQL code, but I want to keep the migration file "clean". Has anyone run into this issue?
As an aside, I was also trying to set the default to NULL on another column, researched it here (and Google), with no success.
EDIT:
It seems as if there is no way to edit the column "id" after it is created as part of table creation through a generic migration. The only way to do this is through an "execute" statement with MySQL syntax.
Try creating and running this migration:
class ChangeColumnUserIdToAutoIncrement < ActiveRecord::Migration
def self.up
execute "ALTER TABLE users modify COLUMN id int(8) AUTO_INCREMENT"
end
def self.down
execute "ALTER TABLE users modify COLUMN id int(8)"
end
end
Rails is taking care for you the ID field of all the tables you're creating and it's hidden from your migrations and even from the schema.rb file.
So I would advice you to take a step back and think: Why would you want to mess with it? Are you sure that's really needed?
EDIT: This answer seems to be exactly what you're looking for.
Regarding "setting default to NULL", again, why would you do that? All the columns are defaulting to NULL anyway. However, if that's not the case you could do that in a migration:
change_table :users do |t|
t.string :description, :default => nil
end
Hope that answers your question.