Rails Postgres functional indexes - sql

How I should enter my multicolum indexes which contain functions into schema.rb ?
for example this DOESN'T work:
add_index "temporary_events", ["templateinfoid", "campaign", "date(gw_out_time)", "messagetype"], :name => "temporary_events_campaign_tinfoid_date_messagetype"
rake db:test:load
rake aborted!
PGError: ERROR: column "date(gw_out_time)" does not exist
: CREATE INDEX "temporary_events_campaign_tinfoid_date_messagetype" ON "temporary_events" ("templateinfoid", "campaign", "date(gw_out_time", "messagetype")

The built-in ActiveRecord method for creating indexes (add_index) does not support functions or any other more advanced features. Instead you can use execute to create the index with SQL:
execute <<-SQL
CREATE INDEX temporary_events_campaign_tinfoid_date_messagetype
ON temporary_events(templateinfoid, campaign, date(gw_out_time), messagetype);
SQL
Note that the use of execute in migrations can be problematic if you are not using the SQL schema format (config.active_record.schema_format = :sql). For more information, search for schema_format.

I was able to get functional indexes out of Rails (3.1.3) migrations by removing a couple guard-rails!
# lib/functional_indexes.rb
module ActiveRecord
module ConnectionAdapters
module SchemaStatements
#disable quoting of index columns to allow functional indexes (e.g lower(full_name) )
def quoted_columns_for_index(column_names, options = {})
column_names
end
def index_name_for_remove(table_name, options = {})
index_name = index_name(table_name, options)
# disable this error check -- it can't see functional indexes
#unless index_name_exists?(table_name, index_name, true)
# raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
#end
index_name
end
end
end
end
I had to make my own index names, though:
class AddLowerCaseIndexes < ActiveRecord::Migration
def up
add_index :people, 'lower(full_name)', :name => "index_people_on_lower_full_name"
add_index :people, 'lower(company)', :name => "index_people_on_lower_company"
end
def down
remove_index :people, :name => "index_people_on_lower_full_name"
remove_index :people, :name => "index_people_on_lower_company"
end
end
(You probably don't need quotes around your index column names unless you are doing something insane like putting spaces or weird characters in them.)
(You are probably fine with postgres error messages when attempting to rollback non-existent indexes.)

If you are using pg_power gem(https://github.com/TMXCredit/pg_power),
you can do it in the following way:
add_index(:comments, 'dmetaphone(author)', :using => 'gist')

Related

Convert Rails migration to raw SQL

How can I convert this migration to raw sql? or Can I convert?
class AddUnsubscribeTokenToUsers < ActiveRecord::Migration
def self.up
add_column :users, :unsubscribe_token, :string, :unique => true
User.all.each do |user|
user.unsubscribe_token = ActiveSupport::SecureRandom.hex(18)
end
end
def self.down
remove_column :users, :unsubscribe_token
end
end
AFAIK you can't convert a single migration into SQL, but you can have ActiveRecord output your schema in SQL instead of Ruby.
# in config/application.rb
config.active_record.schema_format = :sql
This will give you SQL output in your db/schema instead of the Ruby DSL. But neither format will include the snippet of code setting the user token.
Also, it's considered a Bad Idea to include DB modifying code like that in your migration. For example what happens if you get rid of or rename the model, when that migration runs it will fail. At least wrap it in a check for the model. or a begin/rescue/end
if defined? User
User.all.each do |user|
user.unsubscribe_token = ActiveSupport::SecureRandom.hex(18)
end
end
or
begin
User.all.each do |user|
user.unsubscribe_token = ActiveSupport::SecureRandom.hex(18)
end
rescue
end
And lastly, that snippet is not going to do what you intended since your not saving the model after setting the token, either use update_attributes or call user.save
I stumbled upon a very nice article, describing how this can be achieved via a custom rake task.

How to use hstore on Heroku

As per https://postgres.heroku.com/blog/past/2012/4/26/heroku_postgres_development_plan/ I did "heroku addons:add heroku-postgresql:dev". But when I do
class CreateUsers < ActiveRecord::Migration
def up
create_table :users do |t|
execute "CREATE EXTENSION hstore"
t.hstore :access
end
end
def down
drop_table :users
execute "DROP EXTENSION hstore"
end
end
end
and then "heroku run rake db:migrate" I get this error:
PG::Error: ERROR: syntax error at or near "EXTENSION"
LINE 1: CREATE EXTENSION hstore ^
: CREATE EXTENSION hstore
finally got it to work. turns out i needed to "promote" the database using heroku pg:promote as per https://devcenter.heroku.com/articles/heroku-postgres-dev-plan
I think you want to split out your migrations, one to add hstore, the other to use it;
class SetupHStore < ActiveRecord::Migration
def self.up
execute "CREATE EXTENSION hstore"
end
def self.down
execute "DROP EXTENSION hstore"
end
end
to enable the extension, and the your Users migration will just add any fields and then use hstore on which ever column you want.

Tried to create table without id column but facing 'unique index' error from sqlite

I followed the instruction on link below to "create table with no 'id' column", since i am using 'emp_id' instead.
Create an ActiveRecord database table with no :id column?
I am facing error "table users has no column named id: CREATE UNIQUE INDEX" from Sqlite. Just wondering if you could provide some suggestions to me.
Thank you so much for your kind input.
Below is the original migration file:
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users, :id => false do |t|
t.integer :emp_id
t.string :name
t.integer :dept_id
t.timestamps
end
end
def self.down
drop_table :users
end
end
Below is the result after running "rake db:migrate"
-- add_index(:users, :id, {:unique=>true})
rake aborted!
An error has occurred, this and all later migrations canceled:
SQLite3::SQLException: table users has no column named id: CREATE UNIQUE INDEX "index_users_on_id" ON "users" ("id")
Sincerely,
Kevin H
ActiveRecord expects a primary key for all tables that back a model (not a relationship, like a HTBTM–join table). What you're trying to do appears to be STI (single-table inheritance), and it's supported in another fashion (see Single Table Inheritance in The Rails 3 Way).

How to add a Hash object to an ActiveRecord class? Tried but migration fails

I want my ActiveRecord class User to contain options (a bunch of string key-values), so I wrote:
rails generate migration AddOptionsToUser options:Hash
It generated:
class AddOptionsToUser < ActiveRecord::Migration
def self.up
add_column :users, :options, :Hash
end
def self.down
remove_column :users, :options
end
end
I also added this line to my class User:
serialize :options, Hash
But the migration fails:
Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'Hash' at line 1: ALTER TABLE `users` ADD `options` Hash
I am new to Rails, what is the usual way to store a bunch of string key-values in an ActiveRecord class?
Rails serializes things in to a (YAML) string. So in your database, the type should be string (or text).
class AddOptionsToUser < ActiveRecord::Migration
def self.up
add_column :assessments, :options, :string
end
def self.down
remove_column :assessments, :options
end
end
To have ruby object as an attribute of the ActiveRecord model you should use serialize method inside your class for that attribute link

Why am I getting an error during a migration coupled with default data?

rails g migration CreateStates
Then add the following code to the migration:
===========================
class CreateStates < ActiveRecord::Migration
def self.up
create_table :states do |t|
t.column :name, :string
t.column :abbreviation, :string
end
State.create :name => 'Alabama', :abbreviation => 'AL'
State.create :name => 'Alaska', :abbreviation => 'AK'
State.create :name => 'Arizona', :abbreviation => 'AZ'
end
def self.down
drop_table :states
end
end
============================
I get an error:
** Invoke db:migrate (first_time)
** Invoke environment (first_time)
** Execute environment
** Execute db:migrate
== CreateStates: migrating ===================================================
-- create_table(:states)
-> 0.0010s
rake aborted!
An error has occurred, this and all later migrations canceled:
uninitialized constant CreateStates::State
/Users/jondoe/.rvm/rubies/ruby-1.8.7-p330/lib/ruby/gems/1.8/gems/rspec-core-2.5.1/lib/rspec/core/backward_compatibility.rb:20:in `const_missing'
========
It seems like this should be able to do this:
http://api.rubyonrails.org/classes/ActiveRecord/Migration.html
I have also tried to create a model instead of a just a migration file. Still same error. I have also tried creating 2 migrations (one to create table, then one to add data) and that didn't work either. Any ideas?
Try doing:
State.reset_column_information
before your State.create.
documentation
Your code would work perfectly, except that you don't actually have a State class. The only way Rails would know about this class is if you defined it in app/models/state.rb as Class State < ActiveRecord::Base...
Rather than running a custom migration, I'd recommend running this line of code:
rails g model State name:string abbreviation:string
This will:
Create your model (and unit test files)
Create a migration named something like 20110508223913_create_states.rb, which will look nearly identical to your attempted migration above.
Then all you need to do is add your State.create... lines and you should be good to go.