RoR testing with instantiated fixtures while using multiple databases is more possible then ever. All you need to do is just implement your own test_helper method, that load fixtures into the databases and instatiate them. Here’s my code, which i use in my project(just copy it into test_helper.rb):
cattr_accessor :classes_cache
#class cache for storing already founded classes from models
@@classes_cache = {}
def load_user_fixtures(*table_names)
fixtures = {}
table_names = table_names.flatten.collect{|t| t.to_s}
table_names.each do |table_name|
unless @@classes_cache[table_name].nil?
klas = @@classes_cache[table_name]
else
begin
#try to find class name from table name
klas = eval(table_name.classify)
rescue
#go to model directory, run through all models and search for table name
classes = Dir.entries(RAILS_ROOT + "/app/models").select{|d| d.include?(".rb")}.collect{|f| File.basename(f, ".rb").classify}
klas_names = classes.select{|f| (eval("#{f}.table_name") rescue false)==table_name }
klas_name = klas_names.blank? ? table_name.classify : klas_names.first
klas = eval(klas_name)
end
@@classes_cache[table_name] = klas
end
#load fixture
fixtures[table_name] = Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures", table_name,
{table_name.to_sym => klas.name}){klas.connection}
end
#run through all fixtures and instantiate them
fixtures.each_pair do |table_name, fixs|
Fixtures.instantiate_fixtures(self, table_name, fixs)
end
end
self.use_transactional_fixtures = false
self.use_instantiated_fixtures = false
To load your fixtures in tests use implemented helper method in your unit or functional tests like this(notice, that load_user_fixtures is instance method, but regular fixtures method is class method)
require File.dirname(__FILE__) + '/../test_helper'
class EmployeeTest < Test::Unit::TestCase
def setup
load_user_fixtures :employees, :users
end
end
AR has a small bug when asking for table_name to class, which is inherited from abstract class. So we have to set table names even if they are the same as model name.
First set abstract class as usual to define connection.
class AnotherDatabaseConnector < ActiveRecord::Base
self.abstract_class = true
establish_connection "another_database_#{RAILS_ENV}"
# all subclasses use connection from here
end
class User < AnotherDatabaseConnector
set_table_name "users" # we need to set this explicitly
end
If you have any problems or suggestions, you can discuss email me robert.cigan@skvely.cz, or chat directly using ICQ 82645774
- rimmer
After two days of reading the wiki and experimenting I was able to access two databases (legacy: MS SQLServer and app: MySQL) and create unit tests and test fixtures for both. Here’s what i did:
Add an additional entry to the database configuration for the legacy database (one entry for each environment test, development, production).
legacy_test:
adapter: sqlserver
host: (local)
database: legacy_test
username: user
password: password
I created a base legacy class which extends ActiveRecord::Base and established the connection to the legacy database there.
class LegacyBase < ActiveRecord::Base
self.abstract_class = true
establish_connection("legacy_#{RAILS_ENV}")
end
All model classes that access the legacy database extend the legacy base class.
class LegacyWeb < LegacyBase
end
Note If you use ruby script/create model some model make sure you edit the generated class and replace the generated parent class (MyClass < ActiveRecord::Base) with the LegacyBase class as shown above.
For legacy classes, fixtures must be loaded using Fixture.create_fixtures, can’t use the standard fixture: some_fixture
class WebTest < Test::Unit::TestCase
# fixtures :webs, :lists # Can't use this when using multiple databases
def setup
ActiveRecord::Base.connection = LegacyBase.connection
Fixtures.create_fixtures(File.dirname(__FILE__) + "/../fixtures", "webs"){LegacyBase.connection}
@web = Web.find("abc")
end
# Replace this with your real tests.
def test_truth
assert_kind_of Web, @web
end
end
Note I found that the ActiveRecord::Base.connection attribute had to be set otherwise when the test fixtures are inserted during the unit test runs, the column names in the SQL were being formatted with the default test database adapter’s formatting. In my case the mysql_adapter was adding “’” around each column name in the insert statement causing an invalid sql error when trying to insert into the legacy MS SQLServer database.
Unit testing for model classes that are not derived from the LegacyBase class and connect to the default test database require the connection to be explicitly established and the fixtures loaded in a similar fashion to the legecy unit test classes. If the connection is not explicitly specified the loading of fixture data may use the connection specified in the legacy unit tests.
def setup
ActiveRecord::Base.establish_connection("#{RAILS_ENV}")
Fixtures.create_fixtures(File.dirname(__FILE__) + "/../fixtures", "scrum_foos")
end
Note The connection does not need to be specified for the create_fixtures call
Thanks to everyone who contributed to the multiple database topics on the wiki HowtoUseMultipleDatabases, couldn’t have figured this out without all of the good information.
- JSzod
I followed the previous solutions, but couldn’t figure out how to clone the structure from “legacy_development” to “legacy_test”.
I came up with a solution to use your legacy databases with your main test database. It has the benefit that fixtures will work as usual, but will break if you have tables with the same name in different lagacy databases. You need legacy.rake and base.rb below. Use at your own risk! I’m a newbie just hacking away. :)
Run with: rake db:test:prepare:legacy
# lib/tasks/legacy.rake
#
# - prepare all legacy databases with rake db:test:prepare:legacy
# - db:test:*:legacy_db to run any db:test: task on database legacy_db
#
# list your legacy dbs here:
legacy_dbs = %w(ciministry)
def prepend(prefix, array)
array.collect { |item| prefix + item }
end
namespace :db do
namespace :test do
namespace :prepare do
desc "like db:test:prepare plus copies all legacy database structure to the test database"
task :legacy => "db:test:prepare" do
prepend("db:test:clone_structure:", legacy_dbs).each do |task|
Rake::Task[task].invoke
end
end
end
commands = %w(clone clone_structure)
commands.each do |command|
legacy_dbs.each do |legacy_db|
namespace command do
desc "like db:test:#{command} but for #{legacy_db}"
task legacy_db do
config_path = "config/environments"
# db:test:clone will complain without this environments file
File.copy("#{config_path}/#{RAILS_ENV}.rb", "#{config_path}/#{legacy_db}.rb")
# fake the environment to get the legacy database's structure in the test db
backup_env = RAILS_ENV;
RAILS_ENV = legacy_db
Rake::Task["db:test:#{command}"].invoke
# restore
File.delete("#{config_path}/#{legacy_db}.rb")
RAILS_ENV = backup_env;
end
end
end
end
end
end
Use this as your abstract base instead of the one presented above
class LegacyBase < ActiveRecord::Base
self.abstract_class = true
if RAILS_ENV == "test"
establish_connection("test")
else
establish_connection("legacydb")
end
end
RoR testing with instantiated fixtures while using multiple databases is more possible then ever. All you need to do is just implement your own test_helper method, that load fixtures into the databases and instatiate them. Here’s my code, which i use in my project(just copy it into test_helper.rb):
cattr_accessor :classes_cache
#class cache for storing already founded classes from models
@@classes_cache = {}
def load_user_fixtures(*table_names)
fixtures = {}
table_names = table_names.flatten.collect{|t| t.to_s}
table_names.each do |table_name|
unless @@classes_cache[table_name].nil?
klas = @@classes_cache[table_name]
else
begin
#try to find class name from table name
klas = eval(table_name.classify)
rescue
#go to model directory, run through all models and search for table name
classes = Dir.entries(RAILS_ROOT + "/app/models").select{|d| d.include?(".rb")}.collect{|f| File.basename(f, ".rb").classify}
klas_names = classes.select{|f| (eval("#{f}.table_name") rescue false)==table_name }
klas_name = klas_names.blank? ? table_name.classify : klas_names.first
klas = eval(klas_name)
end
@@classes_cache[table_name] = klas
end
#load fixture
fixtures[table_name] = Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures", table_name,
{table_name.to_sym => klas.name}){klas.connection}
end
#run through all fixtures and instantiate them
fixtures.each_pair do |table_name, fixs|
Fixtures.instantiate_fixtures(self, table_name, fixs)
end
end
self.use_transactional_fixtures = false
self.use_instantiated_fixtures = false
To load your fixtures in tests use implemented helper method in your unit or functional tests like this(notice, that load_user_fixtures is instance method, but regular fixtures method is class method)
require File.dirname(__FILE__) + '/../test_helper'
class EmployeeTest < Test::Unit::TestCase
def setup
load_user_fixtures :employees, :users
end
end
AR has a small bug when asking for table_name to class, which is inherited from abstract class. So we have to set table names even if they are the same as model name.
First set abstract class as usual to define connection.
class AnotherDatabaseConnector < ActiveRecord::Base
self.abstract_class = true
establish_connection "another_database_#{RAILS_ENV}"
# all subclasses use connection from here
end
class User < AnotherDatabaseConnector
set_table_name "users" # we need to set this explicitly
end
If you have any problems or suggestions, you can discuss email me robert.cigan@skvely.cz, or chat directly using ICQ 82645774
- rimmer
After two days of reading the wiki and experimenting I was able to access two databases (legacy: MS SQLServer and app: MySQL) and create unit tests and test fixtures for both. Here’s what i did:
Add an additional entry to the database configuration for the legacy database (one entry for each environment test, development, production).
legacy_test:
adapter: sqlserver
host: (local)
database: legacy_test
username: user
password: password
I created a base legacy class which extends ActiveRecord::Base and established the connection to the legacy database there.
class LegacyBase < ActiveRecord::Base
self.abstract_class = true
establish_connection("legacy_#{RAILS_ENV}")
end
All model classes that access the legacy database extend the legacy base class.
class LegacyWeb < LegacyBase
end
Note If you use ruby script/create model some model make sure you edit the generated class and replace the generated parent class (MyClass < ActiveRecord::Base) with the LegacyBase class as shown above.
For legacy classes, fixtures must be loaded using Fixture.create_fixtures, can’t use the standard fixture: some_fixture
class WebTest < Test::Unit::TestCase
# fixtures :webs, :lists # Can't use this when using multiple databases
def setup
ActiveRecord::Base.connection = LegacyBase.connection
Fixtures.create_fixtures(File.dirname(__FILE__) + "/../fixtures", "webs"){LegacyBase.connection}
@web = Web.find("abc")
end
# Replace this with your real tests.
def test_truth
assert_kind_of Web, @web
end
end
Note I found that the ActiveRecord::Base.connection attribute had to be set otherwise when the test fixtures are inserted during the unit test runs, the column names in the SQL were being formatted with the default test database adapter’s formatting. In my case the mysql_adapter was adding “’” around each column name in the insert statement causing an invalid sql error when trying to insert into the legacy MS SQLServer database.
Unit testing for model classes that are not derived from the LegacyBase class and connect to the default test database require the connection to be explicitly established and the fixtures loaded in a similar fashion to the legecy unit test classes. If the connection is not explicitly specified the loading of fixture data may use the connection specified in the legacy unit tests.
def setup
ActiveRecord::Base.establish_connection("#{RAILS_ENV}")
Fixtures.create_fixtures(File.dirname(__FILE__) + "/../fixtures", "scrum_foos")
end
Note The connection does not need to be specified for the create_fixtures call
Thanks to everyone who contributed to the multiple database topics on the wiki HowtoUseMultipleDatabases, couldn’t have figured this out without all of the good information.
- JSzod
I followed the previous solutions, but couldn’t figure out how to clone the structure from “legacy_development” to “legacy_test”.
I came up with a solution to use your legacy databases with your main test database. It has the benefit that fixtures will work as usual, but will break if you have tables with the same name in different lagacy databases. You need legacy.rake and base.rb below. Use at your own risk! I’m a newbie just hacking away. :)
Run with: rake db:test:prepare:legacy
# lib/tasks/legacy.rake
#
# - prepare all legacy databases with rake db:test:prepare:legacy
# - db:test:*:legacy_db to run any db:test: task on database legacy_db
#
# list your legacy dbs here:
legacy_dbs = %w(ciministry)
def prepend(prefix, array)
array.collect { |item| prefix + item }
end
namespace :db do
namespace :test do
namespace :prepare do
desc "like db:test:prepare plus copies all legacy database structure to the test database"
task :legacy => "db:test:prepare" do
prepend("db:test:clone_structure:", legacy_dbs).each do |task|
Rake::Task[task].invoke
end
end
end
commands = %w(clone clone_structure)
commands.each do |command|
legacy_dbs.each do |legacy_db|
namespace command do
desc "like db:test:#{command} but for #{legacy_db}"
task legacy_db do
config_path = "config/environments"
# db:test:clone will complain without this environments file
File.copy("#{config_path}/#{RAILS_ENV}.rb", "#{config_path}/#{legacy_db}.rb")
# fake the environment to get the legacy database's structure in the test db
backup_env = RAILS_ENV;
RAILS_ENV = legacy_db
Rake::Task["db:test:#{command}"].invoke
# restore
File.delete("#{config_path}/#{legacy_db}.rb")
RAILS_ENV = backup_env;
end
end
end
end
end
end
Use this as your abstract base instead of the one presented above
class LegacyBase < ActiveRecord::Base
self.abstract_class = true
if RAILS_ENV == "test"
establish_connection("test")
else
establish_connection("legacydb")
end
end