Add data to a unique, required column - ruby-on-rails

I'm looking for the preferred way, and unique column to an existing table. I also want to add a unique index to the table. Before adding the index though, I obviously need to add data to the column to prevent the index creation from failing.
Here is the situation:
class AddUsernameToUsers < ActiveRecord::Migration
def change
add_column :users, :username, :string, null: false
# Need Data here! And don't want to do something like this:
# User.each { |u| u.update_attribute(:username, u.email }
add_index :users, :username, unique: true
end
end
I know using ruby code to populate the data is possible, there are lots of examples of that, but I keep reading that it isn't such a good idea. Are there any options other than something similar to the above?

would the following work for your situation?
class AddUsernameToUsers < ActiveRecord::Migration
def change
add_column :users, :username, :string, null: true
execute("UPDATE users SET username = email")
change_column :users, :username, :string, null: false
add_index :users, :username, unique: true
end
end

Related

Make table name unique for a user only and not across the entire database

I have here, for example, a user with many groups and the database is structured as follows:
class CreateGroups < ActiveRecord::Migration[6.0]
def change
create_table :groups, id: :uuid do |t|
t.uuid :user_id
t.string :name
t.timestamps
end
add_index :groups, :name, unique: true
end
end
The above will make name being unique across the entire database. I want name to be unique based on the user (user_id). Is that possible at database level? To have something like this, do I have to use find or create instead?
If I have two users, they should be able to create the same group names but should not be the same for each user:
User.first.groups.first.name #> Bold
User.first.groups.second.name #> Bold (should fail)
User.second.groups.first.name #> Bold (should be ok)
User.second.groups.second.name #> Bold (should fail)
Its called a multicolumn index (aka composite index, combined index, or concatenated index) and you can create them by passing an array:
class CreateGroups < ActiveRecord::Migration[6.0]
def change
create_table :groups, id: :uuid do |t|
t.uuid :user_id
t.string :name
t.timestamps
end
add_index :groups, [:user_id, :name], unique: true
end
end
On the application side you can validate this by using:
class Group < ApplicationRecord
validates :name, uniqueness: { scope: :user_id }
end
This provides user feedback and prevents a database driver error while the actual DB index safeguards against race conditions.
Just change
add_index :groups, :name, unique: true
to
add_index :groups, [:user_id, :name], unique: true

How to drop a fk constraint in production

I have the following situation in production:
My PersonalInfo model was created using t.references :user, foreign_key: true, index: true, unique: true to model Users. However, PersonalInfo became polymorfic, with this migration:
class AddInfoOwnerToPersonalInfos < ActiveRecord::Migration[5.1]
def up
rename_column :personal_infos, :user_id, :info_owner_id
add_column :personal_infos, :info_owner_type, :string
add_index :personal_infos, [ :info_owner_type, :info_owner_id]
PersonalInfo.update_all(info_owner_type: 'User')
change_column :personal_infos, :info_owner_type, :string, null: false
end
def down
rename_column :personal_infos, :info_owner_id, :user_id
remove_column :personal_infos, :info_owner_type
end
end
The problem:
Still remain a fk constraint in database:
ALTER TABLE ONLY public.personal_infos
ADD CONSTRAINT fk_rails_796da13f22 FOREIGN KEY (info_owner_id) REFERENCES public.users(id);
How could I build a migration to remove safely this constraint? (I believe no constraint is needed in polymorphic associations, only the index)
if foreign_key_exists?(:personal_infos, :users)
remove_foreign_key(:personal_infos, :users)
end
See:
Rails API: remove_foreign_key

Update database data inside a Rails migration

Is possible to update database data inside a migration?
I have this migration:
class FixTokenAuth < ActiveRecord::Migration
def change
unless column_exists? :users, :provider
add_column :users, :provider, :null => false
end
unless column_exists? :users, :uid
add_column :users, :uid, :null => false, :default => ""
end
unless column_exists? :users, :tokens
add_column :users, :tokens, :text
end
end
end
And I have also to add indexes
add_index :users, [:uid, :provider], :unique => true
Of course the migration fail: uid is "" per default and it can't be unique.
After the creation of the column I need to execute a block on my User model:
User.all.each{|u|
u.provider = "email"
u.save!
}
Doing this 'uid' field is filled by data.
Is possible to add something to a migration?
The migration is just plain ruby code, which gets executed, so the best thing you could do is change the change method into up and down. In these methods you can safely write your updating code after the add_columnstatements.

How to update reference column in Ruby

I have a migration that I want to make with a reference in my table. I create the reference using this:
create_table :user_events do |t|
t.references :user, :null => false
end
And in my migration, I want to be able to allow the reference to be NULL.
def self.up
change_column :user_events, :user, :integer, :null => true
end
However I keep getting PGError: ERROR: column "user" of relation "user_events" does not exist. Am I migrating wrong?
This should work:
def self.up
change_column :user_events, :user_id, :integer, :null => true
end
Note that the column you're trying to change is called user_id, not user
It's because your migration creates a column named user_id, referencing the User model.
try
def self.up
change_column :user_events do |c|
c.references :user, :integer, :null => true
end
end

Adding unique: true for add_column and add_index in Active Record

Even though my application isn't gonna allow a user to key in a location, I wanted to enforce a uniqueness on city in the database. Since my Rails app will be searching on the city column, I would like to add an index on the city column as well but was wondering if it matters adding unique: true on the index as well. Is this repetitive? If this doesn't make sense, I would really appreciate it if you could explain why.
class CreateLocations < ActiveRecord::Migration
def change
create_table :locations do |t|
t.string :city, unique: true
t.string :state
t.timestamps
end
add_index :locations, :city, unique: true
end
end
Using Rails 4, you can provide: index param a hash argument
class CreateLocations < ActiveRecord::Migration
def change
create_table :locations do |t|
t.string :city, index: {unique: true}
t.string :state
t.timestamps
end
end
end
As far as I know the unique option in the create_table block is actually not supported and doesn't do anything, see TableDefinition. To create the unique index, you need to call the method add_index the way you do now. Note that a unique index is both for uniqueness and for searching etc., there's no need to add two indexes on the same column.
You can specify unique index while scaffolding:
rails generate model Locations city:string:uniq state:string
This will create Model, Spec, Factory and this migration:
class CreateLocations < ActiveRecord::Migration
def change
create_table :locations do |t|
t.string :city
t.string :state
t.timestamps null: false
end
add_index :locations, :city, unique: true
end
end
Rails knows what it's doing - nothing more is required.
Add: index: { unique: true }
Example:
class AddUsernameToUsers < ActiveRecord::Migration[6.1]
def change
add_column :users, :username, :string, null: false, index: { unique: true }
end
end

Resources