Monday, 18 August 2008

Rails: Work Around to Run Tests While Having Full-Text Index

While working in a Rails project, I was having some field in the DB in Text data type and I need to create a full-text index on this column. So, I created a migration that have the following line:

execute "ALTER TABLE transcriptions ADD FULLTEXT text_index (text)"

After running this migration, the full-text index is created and the application runs with no problems, but!

When I tried to run the unit tests, I got the following error:

Mysql::Error: #42000BLOB/TEXT column 'text' used in key specification without a key length: CREATE INDEX `text_index` ON 'transcriptions' ('text')


That is because when you run db:migrate, a schema file is created from the DB called schema.rb. This file contains the complete DB schema including the indices. The problem is that creating index in the schema.rb file is written using add_index method which is used for creating regular indices only, not the full-text index.

In this case when you are trying to run the unit tests, Rails try to prepare the test DB using this schema.rb file and fail due to it can't create index on text type column.

If we take a look on what test rake task do, we can see the following trace:


** Invoke test:units (first_time)
** Invoke db:test:prepare (first_time)
** Invoke environment (first_time)
** Execute environment
** Invoke db:abort_if_pending_migrations (first_time)
** Invoke environment
** Execute db:abort_if_pending_migrations
** Execute db:test:prepare
** Invoke db:test:clone (first_time)
** Invoke db:schema:dump (first_time)
** Invoke environment
** Execute db:schema:dump
** Invoke db:test:purge (first_time)
** Invoke environment
** Execute db:test:purge
** Execute db:test:clone
** Invoke db:schema:load (first_time)
** Invoke environment
** Execute db:schema:load


And the problem fall in the line "Execute db:schema:load" which creates the test DB using the schema.rb file. To work around this issue, we can replace this rake sub-task (thanks for Matthew Bass’s blog post) by test:clone_structure against the development environment. This will clone the development DB schema into the test DB schema without using the corrupted schema.rb file.

To replace this rake task by the clone one, open the Rakefile file in your project and write the following lines:

require 'tasks/rails'

Rake::TaskManager.class_eval do

def remove_task(task_name)

@tasks.delete(task_name.to_s)
end

end

def remove_task(task_name)
Rake.application.remove_task(task_name)

end

remove_task 'db:schema:load'
namespace :db do

namespace :schema do
task :load do
RAILS_ENV = 'development'
Rake::Task['db:test:clone_structure'].invoke
RAILS_ENV = 'test'

end
end

end


Then when you run the test rake task you will get different trace contains the db:test:clone_structure instead of db:schema:load and everything will go as expected.