mirror of
https://github.com/github/rails.git
synced 2026-04-04 03:00:58 -04:00
Added transactional fixtures that uses rollback to undo changes to fixtures instead of DELETE/INSERT -- it's much faster. See documentation under Fixtures #760 [bitsweat]
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@846 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
*SVN*
|
||||
|
||||
* Added transactional fixtures that uses rollback to undo changes to fixtures instead of DELETE/INSERT -- it's much faster. See documentation under Fixtures #760 [bitsweat]
|
||||
|
||||
* Added destruction of dependent objects in has_one associations when a new assignment happens #742 [mindel]. Example:
|
||||
|
||||
class Account < ActiveRecord::Base
|
||||
|
||||
@@ -138,16 +138,46 @@ require 'csv'
|
||||
# This is however a feature to be used with some caution. The point of fixtures are that they're stable units of predictable
|
||||
# sample data. If you feel that you need to inject dynamic values, then perhaps you should reexamine whether your application
|
||||
# is properly testable. Hence, dynamic values in fixtures are to be considered a code smell.
|
||||
#
|
||||
# = Transactional fixtures
|
||||
#
|
||||
# TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case.
|
||||
# They can also turn off auto-instantiation of fixture data since the feature is costly and often unused.
|
||||
#
|
||||
# class FooTest < Test::Unit::TestCase
|
||||
# self.use_transactional_fixtures = true
|
||||
# self.use_instantiated_fixtures = false
|
||||
#
|
||||
# fixtures :foos
|
||||
#
|
||||
# def test_godzilla
|
||||
# assert !Foo.find_all.emtpy?
|
||||
# Foo.destroy_all
|
||||
# assert Foo.find_all.emtpy?
|
||||
# end
|
||||
#
|
||||
# def test_godzilla_aftermath
|
||||
# assert !Foo.find_all.emtpy?
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures,
|
||||
# then you may omit all fixtures declarations in your test cases since all the data's already there and every case rolls back its changes.
|
||||
#
|
||||
# When *not* to use transactional fixtures:
|
||||
# 1. You're testing whether a transaction works correctly. Nested transactions don't commit until all parent transactions commit,
|
||||
# particularly, the fixtures transaction which is begun in setup and rolled back in teardown. Thus, you won't be able to verify
|
||||
# the results of your transaction until Active Record supports nested transactions or savepoints (in progress.)
|
||||
# 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
|
||||
# Use InnoDB, MaxDB, or NDB instead.
|
||||
class Fixtures < Hash
|
||||
DEFAULT_FILTER_RE = /\.ya?ml$/
|
||||
|
||||
def self.instantiate_fixtures(object, fixtures_directory, *table_names)
|
||||
[ create_fixtures(fixtures_directory, *table_names) ].flatten.each_with_index do |fixtures, idx|
|
||||
object.instance_variable_set "@#{table_names[idx]}", fixtures
|
||||
fixtures.each do |name, fixture|
|
||||
if model = fixture.find
|
||||
object.instance_variable_set "@#{name}", model
|
||||
end
|
||||
def self.instantiate_fixtures(object, table_name, fixtures)
|
||||
object.instance_variable_set "@#{table_name}", fixtures
|
||||
fixtures.each do |name, fixture|
|
||||
if model = fixture.find
|
||||
object.instance_variable_set "@#{name}", model
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -322,51 +352,100 @@ class Fixture #:nodoc:
|
||||
end
|
||||
end
|
||||
|
||||
module Test#:nodoc:
|
||||
module Unit#:nodoc:
|
||||
module Test #:nodoc:
|
||||
module Unit #:nodoc:
|
||||
class TestCase #:nodoc:
|
||||
include ClassInheritableAttributes
|
||||
|
||||
cattr_accessor :fixture_path
|
||||
cattr_accessor :fixture_table_names
|
||||
class_inheritable_accessor :fixture_table_names
|
||||
class_inheritable_accessor :use_transactional_fixtures
|
||||
class_inheritable_accessor :use_instantiated_fixtures
|
||||
|
||||
self.fixture_table_names = []
|
||||
self.use_transactional_fixtures = false
|
||||
self.use_instantiated_fixtures = true
|
||||
|
||||
def self.fixtures(*table_names)
|
||||
require_fixture_classes(table_names)
|
||||
write_inheritable_attribute("fixture_table_names", table_names)
|
||||
self.fixture_table_names = table_names.flatten
|
||||
require_fixture_classes
|
||||
end
|
||||
|
||||
def self.require_fixture_classes(table_names)
|
||||
table_names.each do |table_name|
|
||||
def self.require_fixture_classes
|
||||
fixture_table_names.each do |table_name|
|
||||
begin
|
||||
require(Inflector.singularize(table_name.to_s))
|
||||
require Inflector.singularize(table_name.to_s)
|
||||
rescue LoadError
|
||||
# Let's hope the developer is included it himself
|
||||
# Let's hope the developer has included it himself
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
instantiate_fixtures(*fixture_table_names) if fixture_table_names
|
||||
def setup_with_fixtures
|
||||
# Load fixtures once and begin transaction.
|
||||
if use_transactional_fixtures
|
||||
load_fixtures unless @already_loaded_fixtures
|
||||
@already_loaded_fixtures = true
|
||||
ActiveRecord::Base.lock_mutex
|
||||
ActiveRecord::Base.connection.begin_db_transaction
|
||||
|
||||
# Load fixtures for every test.
|
||||
else
|
||||
load_fixtures
|
||||
end
|
||||
|
||||
# Instantiate fixtures for every test if requested.
|
||||
instantiate_fixtures if use_instantiated_fixtures
|
||||
end
|
||||
|
||||
def self.method_added(method_symbol)
|
||||
if method_symbol == :setup && !method_defined?(:setup_without_fixtures)
|
||||
alias_method :setup_without_fixtures, :setup
|
||||
define_method(:setup) do
|
||||
instantiate_fixtures(*fixture_table_names) if fixture_table_names
|
||||
setup_without_fixtures
|
||||
alias_method :setup, :setup_with_fixtures
|
||||
|
||||
def teardown_with_fixtures
|
||||
# Rollback changes.
|
||||
if use_transactional_fixtures
|
||||
ActiveRecord::Base.connection.rollback_db_transaction
|
||||
ActiveRecord::Base.unlock_mutex
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :teardown, :teardown_with_fixtures
|
||||
|
||||
def self.method_added(method)
|
||||
case method.to_s
|
||||
when 'setup'
|
||||
unless method_defined?(:setup_without_fixtures)
|
||||
alias_method :setup_without_fixtures, :setup
|
||||
define_method(:setup) do
|
||||
setup_with_fixtures
|
||||
setup_without_fixtures
|
||||
end
|
||||
end
|
||||
when 'teardown'
|
||||
unless method_defined?(:teardown_without_fixtures)
|
||||
alias_method :teardown_without_fixtures, :teardown
|
||||
define_method(:teardown) do
|
||||
teardown_without_fixtures
|
||||
teardown_with_fixtures
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def instantiate_fixtures(*table_names)
|
||||
Fixtures.instantiate_fixtures(self, fixture_path, *table_names)
|
||||
def load_fixtures
|
||||
@loaded_fixtures = {}
|
||||
fixture_table_names.each do |table_name|
|
||||
@loaded_fixtures[table_name] = Fixtures.create_fixtures(fixture_path, table_name)
|
||||
end
|
||||
end
|
||||
|
||||
def fixture_table_names
|
||||
self.class.read_inheritable_attribute("fixture_table_names")
|
||||
def instantiate_fixtures
|
||||
raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
|
||||
@loaded_fixtures.each do |table_name, fixtures|
|
||||
Fixtures.instantiate_fixtures(self, table_name, fixtures)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -105,5 +105,34 @@ class FixturesTest < Test::Unit::TestCase
|
||||
def test_empty_csv_fixtures
|
||||
assert_not_nil Fixtures.new( Account.connection, "accounts", File.dirname(__FILE__) + "/fixtures/naked/csv/accounts")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class FixturesWithoutInstantiationTest < Test::Unit::TestCase
|
||||
self.use_instantiated_fixtures = false
|
||||
fixtures :topics, :developers, :accounts
|
||||
|
||||
def test_without_complete_instantiation
|
||||
assert_nil @topics
|
||||
assert_nil @first
|
||||
end
|
||||
|
||||
def test_fixtures_from_root_yml_without_instantiation
|
||||
assert_nil @unknown
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class TransactionalFixturesTest < Test::Unit::TestCase
|
||||
self.use_transactional_fixtures = true
|
||||
fixtures :topics
|
||||
|
||||
def test_destroy
|
||||
assert_not_nil @first
|
||||
@first.destroy
|
||||
end
|
||||
|
||||
def test_destroy_just_kidding
|
||||
assert_not_nil @first
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user