How to create a "collate nocase" column in a table in migration? - ruby-on-rails

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')

Related

Data type for characters in another language

I have a Location table in activerecord which contains the string type column address. I want to store this Garden Village Restaurant-B مطعم حديقة فيلاج sub-B in address column, which datatype should I use?
I have already tried text datatype but whenever i try to store it gives me an error:
ActiveRecord::StatementInvalid: Mysql2::Error: Incorrect string value: '\xD9\x85\xD8\xB7\xD8\xB9...' for column 'address'
Or if there is another way, I'm open to suggestions.
Generate migration: (Why? - Do not run custom queries on your database, as they are not recorded in your schema)
rails g migration change_collation
Add code as below in migration generated. This will change the character set of database(so next migrations will automatically respect the new collation) and will change the character set of existing tables.
class ChangeCollation < ActiveRecord::Migration[5.0]
def change_encoding(encoding,collation)
connection = ActiveRecord::Base.connection
tables = connection.tables
dbname =connection.current_database
execute <<-SQL
ALTER DATABASE #{dbname} CHARACTER SET #{encoding} COLLATE #{collation};
SQL
tables.each do |tablename|
execute <<-SQL
ALTER TABLE #{dbname}.#{tablename} CONVERT TO CHARACTER SET #{encoding} COLLATE #{collation};
SQL
end
end
def change
reversible do |dir|
dir.up do
change_encoding('utf8', 'utf8_unicode_ci')
end
dir.down do
change_encoding('latin1', 'latin1_swedish_ci')
end
end
end
end
Also, I think utf8_general_ci will also support storing the urdu characters. But based on this post, better to go ahead with utf8_unicode_ci
Another way: save address in encrypted manner:
config/initializers/encrypter.rb
encrypter_key = ActiveSupport::KeyGenerator.new('mypassword').generate_key('a..z', 32)
ENCRYPTER_CRYPT = ActiveSupport::MessageEncryptor.new(encrypter_key)
in model:
class Location < ApplicationRecord
before_save :encrypt_address
def encrypt_address
self.address = ENCRYPTER_CRYPT.encrypt_and_sign(self[:address]) if self[:address].present?
end
def address
# override getter to decrypt and give original urdu string.
ENCRYPTER_CRYPT.decrypt_and_verify(self[:address]) if self[:address].present?
end
end
You could run the following command on your desired table(s):
ALTER TABLE <table_name> CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;
Also delete all the data first from that table
Hope it helps!

Rails Migration, converting from string to enum

If I've got a production database that has "types" stored as string, but I want to convert that column to an integer for enum.
I've googled/SO'd, and I see that I can CAST, but not sure what that does exactly.
If it's not hard, I'd love ot use rails enum, but otherwise, maybe I should stick with my string schema...
Please advise!
You can rename existing column, create a new one called "types" (integer), then write a script that stores appropriate integer value in the new column, and then drop the old column.
The migration will look like this:
class FixTypes < ActiveRecord::Migration
def change
rename_column :table_name, :types, :old_types
add_column :table_name, :types, :integer
end
end
Then write a script that sets the value of "types" based on "old_types":
Model.all.each do |entry|
entry.types = %w(status1 status2 status3 status4).index(entry.old_types)
entry.save!
end
And then drop the "old_types" column.

Transforming an array of strings column to an array of integer column in rails

I have a table, called resource_schedules which contains a :
t.string :active_patient_ids, array: true, default: []
I want to transform it to :
t.integer :active_patient_ids, array: true, default: []
I've created a migration file and here is the code I've put inside it:
def up
change_column :resource_schedules, :active_patient_ids, :integer
end
def down
change_column :resource_schedules, :active_patient_ids, :string
end
Then I ran this command:
rake db:migrate
The point is my active_patient_ids is still an array of strings.
At no point in the migrations above are you actually changing the data-type of what is in the table. If there's data already in the table you're going to have a hard time converting between data types via a migration. I recommend making a new, empty int table and then manually migrating the data from the string table to the int table using string.to_i.
You need to migrate the data as well. Since you want to use the same column name, you should be able to rename the existing column, add a new column using the previous name, then migrate the data.
I haven't tested this, but I've done used something similar before. PLEASE backup your db before trying it.
def up
rename_column :resource_schedules, :active_patient_ids, :active_patient_id_strings
add_column :resource_schedules, :active_patient_ids, :integer
ResourceSchedule.each do |schedule|
schedule.active_patient_ids = schedule.active_patient_id_strings.map { |s| s.to_i }
schedule.save
end
remove_column :resource_schedules, :active_patient_id_strings
end

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.

How would I add an index on a key in a hstore column?

Watching http://railscasts.com/episodes/345-hstore
I've decided to implement some hstore columns.
From my understanding
execute "CREATE INDEX products_gin_properties ON products USING GIN(properties)"
or better written as (Rails 4):
add_index :products, :properties, using: :gin
Both only creates an index on the hstore column.
How would I add an index on a key in a hstore column? Lookin around, I could do something similar to:
execute "CREATE INDEX products_properties_name ON products (properties -> 'name')"
However, is there a Rails 4 approach to doing this?
just looking at the source code here: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb#L418
def add_index(table_name, column_name, options = {}) #:nodoc:
index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
end
index_columns is a comma separated list of columns.
it does not seem like it is supported.

Resources