Tests failing due to foreign key constraints? How to contro

I am having some problems with tests failing, and it seems to be related
to
the foreign key constraints I have in the database.

Listing A shows what appears in development.log, when I run “rake
test”,
before any output appears in test.log (which I find strange because I
would
have thought that all output would go to test.log when running in test
mode).
Lising B shows the corresponding output of the “rake test --trace”
command.

IIf I remove the foreign key constraints from the database, the test
runs
fine, and Listing C appears in the development log instead. The
corresponding “rake test --trace” output is shown in Listing D.

So it seems that as part of the testing process, rails is attempting
to drop
and recreate all of the tables in the database, but it is doing it in
the
wrong order so that the foreign key constraints get in they way. In
fact the
ordering is based on the order that the tables are created in schema.rb
(LISTING E) (which is different to the order that I programmatically
create
the tables in the migration file (LISTING F)). So has anyone else had
this
problem? Is the solution a matter of controlling which order schema.rb
creates the table? Or is there something more fundamental wrong with my
setup?

Thanks.

============= LISTING A ========================

SQL (0.000450) SELECT * FROM schema_info
SQL (0.000502) SHOW TABLES
SQL (0.001142) SHOW FIELDS FROM functional_rights
SQL (0.001021) SHOW KEYS FROM functional_rights
SQL (0.001017) SHOW FIELDS FROM functional_rights_functional_roles
SQL (0.000922) SHOW KEYS FROM functional_rights_functional_roles
SQL (0.001036) SHOW FIELDS FROM functional_roles
SQL (0.000856) SHOW KEYS FROM functional_roles
SQL (0.000990) SHOW FIELDS FROM functional_roles_users
SQL (0.000903) SHOW KEYS FROM functional_roles_users
SQL (0.001211) SHOW FIELDS FROM users
SQL (0.001118) SHOW KEYS FROM users
SQL (0.000000) Mysql::Error: Cannot delete or update a parent row: a
foreign key constraint fails: DROP TABLE functional_rights
SQL (0.000000) Mysql::Error: Table ‘functional_rights’ already
exists:
CREATE TABLEfunctional_rights (id int(11) DEFAULT NULL auto_increment
PRIMARY KEY, name varchar(255) DEFAULT ‘’ NOT NULL, controller
varchar(255) DEFAULT ‘’ NOT NULL, action varchar(255) DEFAULT ‘’ NOT
NULL)
ENGINE=InnoDB

============= LISTING B ========================

(in /home/michael/code/rails/work/railsproject)
** Invoke test (first_time)
** Execute test
** Invoke test:units (first_time)
** Invoke db:test:prepare (first_time)
** Invoke environment (first_time)
** Execute environment
** Execute db:test:prepare
** Invoke db:test:clone (first_time)
** Invoke db:schema:dump (first_time)
** Invoke environment
** Execute db:schema:dump
** Execute db:test:clone
** Invoke db:schema:load (first_time)
** Invoke environment
** Execute db:schema:load
** Invoke test:functionals (first_time)
** Invoke db:test:prepare
** Execute test:functionals
/usr/bin/ruby18 -Ilib:test
“/usr/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake/rake_test_loader.rb”
“test/functional/functional_role_controller_test.rb”
“test/functional/user_controller_test.rb”
“test/functional/functional_right_controller_test.rb”
“test/functional/session_controller_test.rb”
Loaded suite
/usr/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake/rake_test_loader
Started

Finished in 0.181921 seconds.

14 tests, 80 assertions, 0 failures, 0 errors
** Invoke test:integration (first_time)
** Invoke db:test:prepare
** Execute test:integration
/usr/bin/ruby18 -Ilib:test
“/usr/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake/rake_test_loader.rb”
rake aborted!
Test failures
/usr/lib/ruby/gems/1.8/gems/rails-1.1.6/lib/tasks/testing.rake:35
/usr/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake.rb:387:in execute' /usr/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake.rb:387:inexecute’
/usr/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake.rb:357:in invoke' /usr/lib/ruby/1.8/thread.rb:135:insynchronize’
/usr/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake.rb:350:in invoke' /usr/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake.rb:1906:inrun’
/usr/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake.rb:1906:in `run’
/usr/lib/ruby/gems/1.8/gems/rake-0.7.1/bin/rake:7
/usr/bin/rake:18

============= LISTING C ========================

SQL (0.000450) SELECT * FROM schema_info
SQL (0.000485) SHOW TABLES
SQL (0.001146) SHOW FIELDS FROM functional_rights
SQL (0.001023) SHOW KEYS FROM functional_rights
SQL (0.001011) SHOW FIELDS FROM functional_rights_functional_roles
SQL (0.000823) SHOW KEYS FROM functional_rights_functional_roles
SQL (0.001001) SHOW FIELDS FROM functional_roles
SQL (0.000853) SHOW KEYS FROM functional_roles
SQL (0.000991) SHOW FIELDS FROM functional_roles_users
SQL (0.000838) SHOW KEYS FROM functional_roles_users
SQL (0.001170) SHOW FIELDS FROM users
SQL (0.001298) SHOW KEYS FROM users
SQL (0.001402) DROP TABLE functional_rights
SQL (0.006160) CREATE TABLE functional_rights (id int(11) DEFAULT
NULL
auto_increment PRIMARY KEY, name varchar(255) DEFAULT ‘’ NOT NULL,
controller varchar(255) DEFAULT ‘’ NOT NULL, action varchar(255)
DEFAULT
‘’ NOT NULL) ENGINE=InnoDB
SQL (0.001514) DROP TABLE functional_rights_functional_roles
SQL (0.010817) CREATE TABLE functional_rights_functional_roles
(functional_right_id int(11), functional_role_id int(11))
ENGINE=InnoDB
SQL (0.001557) DROP TABLE functional_roles
SQL (0.004841) CREATE TABLE functional_roles (id int(11) DEFAULT
NULL
auto_increment PRIMARY KEY, name varchar(255) DEFAULT ‘’ NOT NULL)
ENGINE=InnoDB
SQL (0.001718) DROP TABLE functional_roles_users
SQL (0.005080) CREATE TABLE functional_roles_users
(functional_role_id
int(11), user_id int(11)) ENGINE=InnoDB
SQL (0.001777) DROP TABLE users
SQL (0.005176) CREATE TABLE users (id int(11) DEFAULT NULL
auto_increment PRIMARY KEY, username varchar(255) DEFAULT ‘’ NOT NULL,
password_salt varchar(255) DEFAULT ‘’ NOT NULL, password_hash
varchar(255) DEFAULT ‘’ NOT NULL, email_address varchar(255) DEFAULT
‘’ NOT
NULL, created_at datetime NOT NULL) ENGINE=InnoDB
SQL (0.000000) Mysql::Error: Table ‘schema_info’ already exists:
CREATE
TABLE schema_info (version int(11))
SQL (0.000974) SHOW FIELDS FROM schema_info
SQL (0.000680) UPDATE schema_info SET version = 2

============= LISTING D ========================

(in /home/michael/code/rails/work/railsproject)
** Invoke test (first_time)
** Execute test
** Invoke test:units (first_time)
** Invoke db:test:prepare (first_time)
** Invoke environment (first_time)
** Execute environment
** Execute db:test:prepare
** Invoke db:test:clone (first_time)
** Invoke db:schema:dump (first_time)
** Invoke environment
** Execute db:schema:dump
** Execute db:test:clone
** Invoke db:schema:load (first_time)
** Invoke environment
** Execute db:schema:load
** Execute test:units
/usr/bin/ruby18 -Ilib:test
“/usr/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake/rake_test_loader.rb”
“test/unit/functional_role_test.rb” “test/unit/overlay_test.rb”
“test/unit/password_test.rb” “test/unit/user_test.rb”
“test/unit/functional_right_test.rb”
Loaded suite
/usr/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake/rake_test_loader
Started

Finished in 1.309497 seconds.

47 tests, 262 assertions, 0 failures, 0 errors
** Invoke test:functionals (first_time)
** Invoke db:test:prepare
** Execute test:functionals
/usr/bin/ruby18 -Ilib:test
“/usr/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake/rake_test_loader.rb”
“test/functional/functional_role_controller_test.rb”
“test/functional/user_controller_test.rb”
“test/functional/functional_right_controller_test.rb”
“test/functional/session_controller_test.rb”
Loaded suite
/usr/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake/rake_test_loader
Started

Finished in 0.15657 seconds.

14 tests, 80 assertions, 0 failures, 0 errors
** Invoke test:integration (first_time)
** Invoke db:test:prepare
** Execute test:integration
/usr/bin/ruby18 -Ilib:test
“/usr/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake/rake_test_loader.rb”

===========================LISTING E ==========================

ActiveRecord::Schema.define(:version => 2) do

create_table “functional_rights”, :force => true do |t|
t.column “name”, :string, :default => “”, :null => false
t.column “controller”, :string, :default => “”, :null => false
t.column “action”, :string, :default => “”, :null => false
end

create_table “functional_rights_functional_roles”, :id => false,
:force =>
true do |t|
t.column “functional_right_id”, :integer
t.column “functional_role_id”, :integer
end

add_index “functional_rights_functional_roles”,
[“functional_right_id”], :name => “fk_frfr_functional_right”
add_index “functional_rights_functional_roles”,
[“functional_role_id”], :name => “fk_frfr_functional_role”

create_table “functional_roles”, :force => true do |t|
t.column “name”, :string, :default => “”, :null => false
end

create_table “functional_roles_users”, :id => false, :force => true do
|t|
t.column “functional_role_id”, :integer
t.column “user_id”, :integer
end

add_index “functional_roles_users”, [“functional_role_id”], :name =>
“fk_fru_functional_role”
add_index “functional_roles_users”, [“user_id”], :name =>
“fk_fru_user”

create_table “users”, :force => true do |t|
t.column “username”, :string, :default => “”, :null => false
t.column “password_salt”, :string, :default => “”, :null => false
t.column “password_hash”, :string, :default => “”, :null => false
t.column “email_address”, :string, :default => “”, :null => false
t.column “created_at”, :datetime, :null => false
end

end

========================== LISTING F ===========================

class AddFunctionalRightsAndFunctionalRolesTables <
ActiveRecord::Migration
def self.up
create_table(:functional_rights, :force => true) { |t| # A table
storing
the actions on a given controller that a given right allows a user to
perform.
t.column :name, :string, :null => false
t.column :controller, :string, :null => false
t.column :action, :string, :null => false
}

create_table(:functional_roles, :force => true) { |t| # *functional* 

roles,
referring to the fact that we are talking about actions that a user may
perform, rather than data they can access.
t.column :name, :string, :null => false
}

# Join
create_table(:functional_rights_functional_roles, :id => false, :force 

=>
true) { |t|
t.column :functional_right_id, :integer
t.column :functional_role_id, :integer
}

# Join
create_table(:functional_roles_users, :id => false, :force => true) { 

|t|
t.column :functional_role_id, :integer
t.column :user_id, :integer
}

# Add foreign key constraints.
execute 'ALTER TABLE functional_rights_functional_roles ADD CONSTRAINT

fk_frfr_functional_right FOREIGN KEY ( functional_right_id ) REFERENCES
functional_rights( id ) ’

execute 'ALTER TABLE functional_rights_functional_roles ADD CONSTRAINT

fk_frfr_functional_role FOREIGN KEY ( functional_role_id ) REFERENCES
functional_roles( id ) ’

execute 'ALTER TABLE functional_roles_users ADD CONSTRAINT

fk_fru_functional_role FOREIGN KEY ( functional_role_id ) REFERENCES
functional_roles( id ) ’

execute 'ALTER TABLE functional_roles_users ADD CONSTRAINT fk_fru_user

FOREIGN KEY ( user_id ) REFERENCES users( id ) ’

end

def self.down
drop_table :functional_rights_functional_roles
drop_table :functional_roles_users
drop_table :functional_rights
drop_table :functional_roles
end
end

Have you had a look at the plug-ins from Red Hill Consulting, notably
schema_defining and foreign_key_migrations?
(http://www.redhillconsulting.com.au/rails_plugins.html). They
automatically add foreign keys to the schema based on key and table
name matches (can be overridden).

I use these and my tests run fine - I think the schema_defining plugin
(on which the foreign_key_migrations plug-in relies) means that in a
test environment the foreign keys are ignored allowing the database to
quickly create and drop content…

It’s worth a look.

Have you had a look at the plug-ins from Red Hill Consulting, notably
schema_defining and foreign_key_migrations?

I use these and my tests run fine - I think the schema_defining plugin
(on which the foreign_key_migrations plug-in relies) means that in a
test environment the foreign keys are ignored allowing the database to
quickly create and drop content…

Thanks for the info Ian. I think for the time being I’ll just drop the
foreign key constraints, since according to [email protected] 's email
(rubyonrails-talk 21.Aug.06 12:28), the “rails way” is to implement all
constraints in the ruby model. I’ve since found a blog post by DHH
stating
the same… it does make me feel a bit uneasy, but I’m going to see
where
“going with the rails flow” takes me…