Rails & SQLite: migration to add unique index and allow null values - ruby-on-rails

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.

Related

Rails 5.1: Foreign key mismatch for old rails schema

Context: On Rails 5.1 the default for Primary Keys changed from Int to BigInt (PR > https://github.com/rails/rails/pull/26266).
I am working on a new Rails 5.1 app which reuses some models from another Rails 4.0 app.
When I run the specs, I have a script that basically loads the Rails 4.0 app's schema.rb from its repo, to create a temp database, so I can use those "external" models (from that other DB) when running tests.
The issue is that given the other app created the schema.rb file on Rails 4.0, all Foreign Keys are integers.
The Rails 4.0 schema file looks like this:
create_table "companies", force: :cascade do |t|
t.string "name"
end
create_table "users", force: :cascade do |t|
t.string "name",
t.integer "company_id", limit: 4, null: false
end
add_foreign_key "users", "companies"
Therefore, when I run tests on the new Rails 5.1 app, it loads that other app schema (the one added above), but when creating the Companies table, it sets the Primary Key as BigInt instead of integer. But the Foreign Key is an integer.
That mismatch is messing up with MySQL:
ActiveRecord::MismatchedForeignKey: Column company_id on table
users has a type of int(11). This does not match column id on
companies, which has type bigint(20). To resolve this issue,
change the type of the company_id column on users to be :integer.
(For example t.integer company_id).
I know I could just change all Foreign Keys from that schema to be a BigInt. But I want to try avoiding that solution, given there are quite a lot of Foreign Keys, and in multiple DBs.
Any ideas on how to solve this issue? Or any thoughts?
You can specify an integer type for the primary key for the new table; see this answer. That would give you the opportunity to migrate to bigint primary keys without being rushed into it.
You can specify the type as integer, for example:
t.references :company, type: :integer, foreign_key: true, null: false

on heroku with rails / postgres, uid strings are converted to integers

See this answer for how I implemented string primary keys in my rails app: https://stackoverflow.com/a/1434819/4585520
In the migrations, id: false is specified and some custom postgres execution code is added (i.e. execute "ALTER TABLE users ADD PRIMARY KEY (uid);" where uid is a string). Also, in the models, self.primary_key = :uid is usedThe result is that I have no 'id' columns, just 'uid' columns which are all strings.
However, when I deploy this to Heroku and run my seed generation, I get errors because of duplicate uids. I notice that the added uid values are integers. Perhaps the string values I'm providing are being converted to integers (I have UIDs in a JSON file and am importing those, so no uid generation is occurring), which would explain why they're producing duplicate values.
Why are my uids integers on heroku when they're correctly running as strings in development?
I don't believe this is a Heroku issue, but rather an issue with how the migration tweaked the database.
I've got a Rails app that uses UUID's for primary keys as well. Here's a look at my DB on Heroku Postgres:
Column | Type | Modifiers
------------+-----------------------------+-------------------------------------------
id | uuid | not null default uuid_generate_v4()
data | json | not null
Here was my migration:
enable_extension 'uuid-ossp'
create_table :notifications, id: :uuid do |t|
t.json :data, null: false
end
I'm guessing that the column type may not have been set correctly in your migration. Take a look at your DB on heroku (heroku pg:psql) -- is the uid column an integer that's using a sequence? Do you see something like nextval('users_uid_seq'::regclass)? If so, this requires the column to be an integer. I'd try the following migration to modify your database:
enable_extension 'uuid-ossp'
remove_column :users, :uid
add_column :users, :uuid, :uuid, default: 'uuid_generate_v4()'
This will:
ensure that UUID generation for PG is enabled
remove the current uid column (just to get proper naming)
create a new uuid column, with the type set as uuid and the generation handled on the PG side.
If you're already using Rails to generate UUID's (not recommended, though), you could just go into your database and change the column type from integer to string.
I hope that helps!

Name of index migration too long

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.

Why postgres 9.3 table keeps losing the primary key and indexes?

In pg 9.3 database for rails 3.2 app, table engine_configs keeps losing primary key (id) and its indexes. Here is the table:
For the table shown above, we just added the primary key again and it is showing integer in column ID instead of serial. Last week, we had manually re-created key and indexes on the table and verified that those key and indexes did exist. There were only a few pg_dump and pg_restore on the table before we found that primary key and indexes are missing again. There was no db crash or something similar. Is there any explanation of why this happens and how to prevent it from happening again? This problem literally blows up our rails app.
UPDATE:
Here is the rails db create:
class CreateAuthentifyEngineConfigs < ActiveRecord::Migration
def change
create_table :authentify_engine_configs do |t|
#t.integer :client_id
t.string :engine_name
t.string :engine_version
t.string :argument_name
t.text :argument_value
t.integer :last_updated_by_id
t.timestamps
t.string :brief_note
t.boolean :global, :default => false
end
add_index :authentify_engine_configs, :engine_name
add_index :authentify_engine_configs, :argument_name
add_index :authentify_engine_configs, [:engine_name, :argument_name], :name => :authentify_engine_configs_names
end
end
This migration file has been used many times with sqlite3 and never had any problem.
UPDATE1:
After pg_dump -Fc --table=authentify_engine_configs mydb > mydb_ec.backup, then restore with:
pg_restore --clean --dbname=mydevdb --table=authentify_engine_configs --verbose c:\d\code\rails_proj\cis\db\mydb_ec.backup
The indexes were lost with the restored local copy. However when pg_dump the whole db, then restored copy is fine.
I had the same problem recently and used pg:reset to solve it. I found the solution from Getting "Unknown primary key for table" while the ID is there . I was using heroku pgbackups:restore and it was dropping the primary keys until I dropped the database using pg:reset.

Rails + UUID generated schema assumes UUID is an integer rather than a string

I’m trying to use a UUID as the primary key for a Rails app, and am running into problem after problem.
I am specifying in migration this:
create_table :users, :id => false do |t|
then this:
execute("ALTER TABLE users ADD PRIMARY KEY(uuid)")
In my user model:
set_primary_key "uuid"
Using the UUID tools to generate the UUID.
This is all working great, the problem I currently have is that the schema.rb that gets generated looks like this:
create_table "users", :primary_key => "uuid", :force => true do |t|
Which assumes that the primary key column is an 11 character integer rather than a 36 character string, so running migrations produces a correct database, but test database is generated incorrectly, and if I were to run rake db:schema:load, it would fail as well...
Need to figure out how to override the way that schema.rb assumes that if there’s a primary key column that it will be an integer....
I think the best approach is to switch from managing your schema in Ruby (schema.rb) to managing it in SQL (development_structure.sql).
To do this:
In application.rb set config.active_record.schema_format = :sql
Delete your schema.rb file.
Every time you run rake db:migrate, run rake db:dump:structure as well. That will dump your schema to db/development_structure.sql. When you run rake db:test:prepare it will now use the development_structure.sql file instead of the scheam.rb file.
You can read more about this in section 6.2 of the migrations guide (http://guides.rubyonrails.org/migrations.html).
We have been using UUID as the primary key for a long time (currently with Rails 3.0.0), but so far have not found a way to make Schema dumper understand that there is no integer id.
So our solution has been fixing the schema by hand, when needed. For example always after rake db:migrate. :D
We just change the line
create_table "people", :force => true do |t|
to
create_table "people", :id => false, :force => true do |t|
t.string "id", :limit => 22, :null => false
It's rather annoying but then everything works. So the problem is not in Rails not allowing UUID primary keys, but schema dumper not understanding those.
I ran into this problem. As far as i can tell, it's impossible to override rails' requirement that the PK be an integer. What i did to bypass this was add a key to unique on the database and then setup my default scopes on each model to search via my unique string instead of the regular integer

Resources