I have to create a migration to do a db level validation. The migration:
class DataBaseLevelValidation < ActiveRecord::Migration
def change
add_index :benefits_business_changes, [:benefit_id, :business_change_id], :unique => true
end
end
The problem I have is that when I try to run rake db:migration I have this error:
Index name 'index_benefits_business_changes_on_benefit_id_and_business_change_id' on table 'benefits_business_changes' is too long;
the limit is 62 characters/Users/mariocardoso/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.5/lib/active_record/connection_adapters/abstract/schema_statements.rb:797:in `add_index_options'
But if I change the name to a shorter version I get this:
SQLite3::SQLException: no such table: main.benefits_businessc: CREATE UNIQUE INDEX "index_benefits_businessc_on_benefit_id_and_business_change_id" ON "benefits_businessc"
How can I overcome this problem?
The only ways I see, is to change the 'business_change' model to a shorter name (model, views, migration, ... everything).
There is any way to run this migration without having the error caused by the long name?
You can do
add_index :benefits_business_changes, [:benefit_id, :business_change_id], :unique => true, :name => "a_shorter_name"
A common choice is to use just the first few letters of each column.
Related
In my Ruby on Rails app I have a model Message that has a created_at column and a status_updated_at column. Both of them are datetimes.
In order to speed up queries such as Message.where(status: 'delivered', account_id: 11).where('status_updated_at - created_at >= ?', "180 seconds"), I want to add index to the difference between columns status_updated_at and created_at.
I've tried this code:
class AddMessageIndicesForDashboard < ActiveRecord::Migration[5.1]
def change
add_index :messages, [:status, :account_id, :created_at, "(status_updated_at-created_at)"], name: 'dashboard_delivery_time_index'
end
end
which result in PG::UndefinedColumn: ERROR: column "status_updated_at-created_at" does not exist
Strangely, say if I only wanted to add index to status_udpated_at - created_at by doing add_index :messages, "(status_updated_at-created_at)", name: 'dashboard_delivery_time_index', it will work.
Any suggestions? Thanks!
Maybe try
add_index :messages, "(status_updated_at-created_at)", name: 'dashboard_delivery_time_index'
PostgreSQL(9.1) 11.7. Indexes on Expressions.
The syntax of the CREATE INDEX command normally requires writing parentheses around index expressions... The parentheses can be omitted when the expression is just a function call...
Since you want to use an expression rather than a function the extra parentheses should do it according to the docs.
I fixed my problem, you can do add_index :messages, "status, account_id, (status_updated_at-created_at)", name: 'dashboard_delivery_time_index'
In my Heroku postgres db, I had a column of type "string" with a limit of "50" characters.
I just made a migration that changed the limit to 80 characters.
class ChangeTagLineLimit < ActiveRecord::Migration
def up
change_column :blocks, :tag_line, :string, :limit => 80
end
def down
change_column :blocks, :tag_line, :string, :limit => 50
end
end
However when I try to save a record, I get this error:
PG::StringDataRightTruncation: ERROR: value too long for type character varying(50)
It sounds like PostGRES hasn't changed the size of the varchar column. How do I fix this?
That migration should work. The error is probably related to another table that didn't get migrated yet.
I want to force e-mails to be unique at the database level, but I also want to allow for null e-mail values. I get the following error with the migration.
Current Schema
create_table "users", force: true do |t|
t.string "email", default: ""
...
end
add_index "users", ["email"], name: "index_users_on_email"
Attempted Migration
remove_index :users, [:email]
change_column :users, :email, :string, :null => true
add_index :users, [:email], unique: true
Error with migration
== RemoveUniqueEmailFromUsers: reverting =====================================
-- remove_index(:users, [:email])
-> 0.0437s
-- change_column(:users, :email, :string, {:null=>true})
-> 0.2664s
-- add_index(:users, [:email], {:unique=>true})
rake aborted!
An error has occurred, this and all later migrations canceled:
SQLite3::ConstraintException: indexed columns are not unique: CREATE UNIQUE INDEX "index_users_on_email" ON "users" ("email")
Try to remove the default: "". I think you have contradicting configs.
Empty string is not nil.
The problem was that the last user I created had the email of ''. After destroying this user the migration proceeded.
If you see the SQLite::ConstraintException after you've run $ rake db:migrate:
SQLite::ConstraintException : indexed columns are not unique CREATE UNIQUE INDEX "index_users_on_email" ON "users"
... and you're working in the dev environment, and you do not need the data in the development database, you can fix this rather quickly.
You can find out if you have existing users in your database and delete them all like this:
$ rails console
> User.count
=> 4
> User.destroy_all
> ...
> User.count
=> 0
Then you can run $ rake db:migrate again, and the migrations should run successfully.
If you're not sure, you can run $ rake db:migrate:status. If all migrations are "up," then your migrations have run successfully.
Here's how to understand the problem:
You have existing users without email values (users table has no email attribute).
When you run the migration the first part adds the email column.
Now, all of your users have an email attribute, but with a null value in the database.
The last part of the migration tries to add an index, with the condition that the values are unique (that's what unique: true specifies).
The unique constraint is impossible to add to the database because you now have an email column for the users table, which all have null values, making them non-unique values. SQLite is throwing an error, stating that you cannot add a unique constraint to a column that has non-unique values.
This can be a good thing if you're trying to add this constraint to a database column in production mode! Thanks for protecting us, SQLite!
Once you understand the problem, you can see that there are other possible solutions, but if you're in the dev environment on a new application, this will probably fit your needs.
IMPORTANT - READ EDIT BELOW FOR UPDATE ON THE ISSUE
I'm getting what I think is a bogus error when I try to add a new record to a join table with a unique composite key index on SQLite3. Note that for all (manual) tests I've done, the database has been completely rebuilt through db:drop followed by db:migrate.
The error:
ActiveRecord::RecordNotUnique
SQLite3::ConstraintException: columns adventurer_id, item_id are not unique:
INSERT INTO "adventurers_items" ("adventurer_id", "item_id") VALUES (1, 68)
The code that generates the error:
class Adventurer < ActiveRecord::Base
after_create :set_starting_skills
after_create :set_starting_items
has_and_belongs_to_many :items
has_and_belongs_to_many :skills
# automatically add starting skills on creation
def set_starting_skills
self.skills = self.profession.starting_skills
end
# automatically add starting items on creation
def set_starting_items
self.items = self.profession.items
end
The migration creating the join table adventurers_skills:
class AdventurersItems < ActiveRecord::Migration
def change
create_table :adventurers_items do |t|
t.integer :item_id, :null => false
t.integer :adventurer_id, :null => false
end
add_index :adventurers_items, :item_id
add_index :adventurers_items, :adventurer_id
add_index :adventurers_items, [:adventurer_id, :item_id], :unique => true
The table exists and is completely empty. Why is my application failing to insert this record due to the uniqueness constraint? I also have the same error with an equivalent table "adventurers_skills" -- am I doing something wrong architecturally?
EDIT
The system is trying to add the same item/skill twice. When I change the private method to this:
def set_starting_skills
skills = profession.starting_skills
end
It doesn't attempt to create anything in the join table. But reverting the first line to self.skills as below attempts to create the same skill TWICE
def set_starting_skills
self.skills = profession.starting_skills
end
returns
(0.4ms) INSERT INTO "adventurers_skills" ("adventurer_id", "skill_id") VALUES (4, 54)
(4.9ms) INSERT INTO "adventurers_skills" ("adventurer_id", "skill_id") VALUES (4, 54)
SQLite3::ConstraintException: columns adventurer_id, skill_id are not unique:
INSERT INTO "adventurers_skills" ("adventurer_id", "skill_id") VALUES (4, 54)
(3.2ms) rollback transaction
There is only one skill returned for profession.starting_skills:
1.9.3-p194 :022 > Profession.find(7).starting_skills.each {|x| puts x.id}
54
So the real question has become: why is Rails trying to add this HABTM record twice?
You need to put the callback declaration (after_create :set_starting_skills) after the relation declaration (has_and_belongs_to_many :skills).
i.e. The ordering of the lines in the model is significant else you get this bug.
This is madness, and there is a GitHub issue for it.
This answer https://stackoverflow.com/a/973785/1297371 to the question: How to set Sqlite3 to be case insensitive when string comparing?
tells how to create table with "COLLATE NOCASE" column.
My question is how to create such column in a rails migration?
i.e. how from
create table Test
(
Text_Value text collate nocase
);
create index Test_Text_Value_Index
on Test (Text_Value collate nocase);
do:
create_table :tests do
#WHAT HERE?
end
add_index #... WHAT HERE?
I added new migration where I deleted and recreated the index like:
class AddIndexToWordVariations < ActiveRecord::Migration
def change
remove_index :word_variations, :variation_word
add_index :word_variations, :variation_word, :COLLATE => :NOCASE
end
end
where 'word_variations' is my table
For people searching, if 'collate' is still not available in your version of rails (you'll get "Unknown key: :COLLATE"), then you have to create the index manually:
execute("create index Test_Text_Value_Index on Test (Text_Value collate nocase);")
In your 'def up' (the 'drop_table' will drop the index as well in your 'def down')