fixes #22, AR code doesn't create new connections like it is going out of style

The code that switches ActiveRecord adapters to take a model class
instead of a connection hash or name.
This commit is contained in:
Ben Mabey 2011-12-19 21:10:09 -07:00
parent 501025b9de
commit 604c9cf6ad
9 changed files with 51 additions and 170 deletions

View file

@ -148,7 +148,13 @@ Sometimes you need to use multiple ORMs in your application. You can use Databas
DatabaseCleaner[:mongo_mapper].strategy = :truncation
#How to specify particular connections
DatabaseCleaner[:active_record,{:connection => :two}]
# with DataMapper you pass in the name of the repository
DatabaseCleaner[:data_mapper,{:connection => :my_other_repository}]
# With ActiveRecord you pass in the model whose #connection DataCleaner should use
DatabaseCleaner[:active_record,{:connection => Widget}]
DatabaseCleaner[:active_record,{:connection => "Widget"}] # You may pass in the model as a String in case you need/want to delay loading.
</pre>
Usage beyond that remains the same with DatabaseCleaner.start calling any setup on the different configured connections, and DatabaseCleaner.clean executing afterwards.

View file

@ -1,9 +1,9 @@
Given /^I have setup database cleaner to clean multiple databases using activerecord$/ do
#DatabaseCleaner
# require "#{File.dirname(__FILE__)}/../../../lib/datamapper_models"
# require "#{File.dirname(__FILE__)}/../../../lib/activerecord_models"
#
# DatabaseCleaner[:datamapper, {:connection => :one} ].strategy = :truncation
# DatabaseCleaner[:datamapper, {:connection => :two} ].strategy = :truncation
# DatabaseCleaner[:active_record, {:connection => 'ActiveRecordWidgetUsingDatabaseOne'} ].strategy = whatever
# DatabaseCleaner[:active_record, {:connection => 'ActiveRecordWidgetUsingDatabaseTwo'} ].strategy = whatever
end
When /^I create a widget using activerecord$/ do

View file

@ -42,9 +42,13 @@ if orm && strategy
DatabaseCleaner.app_root = "#{File.dirname(__FILE__)}/../.."
orm_sym = orm.gsub(/(.)([A-Z]+)/,'\1_\2').downcase.to_sym
if orm_sym == :mongo_mapper
DatabaseCleaner[ orm_sym, {:connection => 'database_cleaner_test_one'} ].strategy = strategy.to_sym
DatabaseCleaner[ orm_sym, {:connection => 'database_cleaner_test_two'} ].strategy = strategy.to_sym
case orm_sym
when :mongo_mapper
DatabaseCleaner[:mongo_mapper, {:connection => 'database_cleaner_test_one'} ].strategy = strategy.to_sym
DatabaseCleaner[:mongo_mapper, {:connection => 'database_cleaner_test_two'} ].strategy = strategy.to_sym
when :active_record
DatabaseCleaner[:active_record, {:connection => 'ActiveRecordWidgetUsingDatabaseOne'} ].strategy = strategy.to_sym
DatabaseCleaner[:active_record, {:connection => 'ActiveRecordWidgetUsingDatabaseTwo'} ].strategy = strategy.to_sym
else
DatabaseCleaner[ orm_sym, {:connection => :one} ].strategy = strategy.to_sym
DatabaseCleaner[ orm_sym, {:connection => :two} ].strategy = strategy.to_sym

View file

@ -17,5 +17,4 @@ Feature: multiple database cleaning
| DataMapper | truncation |
| MongoMapper | truncation |
| DataMapper | transaction |
# Not working...
#| ActiveRecord | transaction |
| ActiveRecord | transaction |

View file

@ -9,44 +9,24 @@ module DatabaseCleaner
%w[truncation transaction deletion]
end
def self.config_file_location=(path)
@config_file_location = path
end
def self.config_file_location
@config_file_location ||= "#{DatabaseCleaner.app_root}/config/database.yml"
end
module Base
include ::DatabaseCleaner::Generic::Base
attr_accessor :connection_hash
def db=(desired_db)
@db = desired_db
load_config
def db=(model_class)
@model_class = model_class unless model_class == :default # hack. this design sucks.
@connection_class = nil
end
def db
@db || super
@model_class || ::ActiveRecord::Base
end
def load_config
if self.db != :default && File.file?(ActiveRecord.config_file_location)
connection_details = YAML::load(ERB.new(IO.read(ActiveRecord.config_file_location)).result)
@connection_hash = connection_details[self.db.to_s]
end
def connection
connection_class.connection
end
def create_connection_klass
Class.new(::ActiveRecord::Base)
end
def connection_klass
return ::ActiveRecord::Base unless connection_hash
klass = create_connection_klass
klass.send :establish_connection, connection_hash
klass
def connection_class
@connection_class ||= db.is_a?(String) ? Module.const_get(db) : db
end
end
end

View file

@ -53,7 +53,6 @@ module DatabaseCleaner::ActiveRecord
class Deletion < Truncation
def clean
connection = connection_klass.connection
tables_to_truncate(connection).each do |table_name|
connection.delete_table table_name
end

View file

@ -4,24 +4,24 @@ module DatabaseCleaner::ActiveRecord
include ::DatabaseCleaner::ActiveRecord::Base
def start
if connection_klass.connection.respond_to?(:increment_open_transactions)
connection_klass.connection.increment_open_transactions
if connection.respond_to?(:increment_open_transactions)
connection.increment_open_transactions
else
connection_klass.__send__(:increment_open_transactions)
connection_class.__send__(:increment_open_transactions)
end
connection_klass.connection.begin_db_transaction
connection.begin_db_transaction
end
def clean
return unless connection_klass.connection.open_transactions > 0
return unless connection.open_transactions > 0
connection_klass.connection.rollback_db_transaction
connection.rollback_db_transaction
if connection_klass.connection.respond_to?(:decrement_open_transactions)
connection_klass.connection.decrement_open_transactions
if connection.respond_to?(:decrement_open_transactions)
connection.decrement_open_transactions
else
connection_klass.__send__(:decrement_open_transactions)
connection_class.__send__(:decrement_open_transactions)
end
end
end

View file

@ -119,7 +119,6 @@ module DatabaseCleaner::ActiveRecord
include ::DatabaseCleaner::Generic::Truncation
def clean
connection = connection_klass.connection
connection.disable_referential_integrity do
connection.truncate_tables(tables_to_truncate(connection))
end
@ -138,6 +137,3 @@ module DatabaseCleaner::ActiveRecord
end
end

View file

@ -3,140 +3,37 @@ require 'active_record'
require 'database_cleaner/active_record/base'
require 'database_cleaner/shared_strategy_spec'
class FakeModel
def self.connection
:fake_connection
end
end
module DatabaseCleaner
describe ActiveRecord do
it { should respond_to(:available_strategies) }
describe "config_file_location" do
subject { ActiveRecord.config_file_location }
it "should default to DatabaseCleaner.root / config / database.yml" do
ActiveRecord.config_file_location=nil
DatabaseCleaner.should_receive(:app_root).and_return("/path/to")
subject.should == '/path/to/config/database.yml'
end
end
end
module ActiveRecord
class ExampleStrategy
include ::DatabaseCleaner::ActiveRecord::Base
end
describe ExampleStrategy do
let :config_location do
'/path/to/config/database.yml'
end
before { ::DatabaseCleaner::ActiveRecord.stub(:config_file_location).and_return(config_location) }
it_should_behave_like "a generic strategy"
describe "db" do
it "should store my desired db" do
subject.stub(:load_config)
subject.db = :my_db
subject.db.should == :my_db
describe "#connection" do
it "returns the connection from ActiveRecord::Base by default" do
::ActiveRecord::Base.stub!(:connection).and_return(:fake_connection)
subject.connection.should == :fake_connection
end
it "should default to :default" do
subject.db.should == :default
it "returns the connection of the model provided" do
subject.db = FakeModel
subject.connection.should == :fake_connection
end
it "should load_config when I set db" do
subject.should_receive(:load_config)
subject.db = :my_db
end
end
describe "load_config" do
before do
subject.db = :my_db
yaml = <<-Y
my_db:
database: <%= "ONE".downcase %>
Y
File.stub(:file?).with(config_location).and_return(true)
IO.stub(:read).with(config_location).and_return(yaml)
end
it "should parse the config" do
YAML.should_receive(:load).and_return( {:nil => nil} )
subject.load_config
end
it "should process erb in the config" do
transformed = <<-Y
my_db:
database: one
Y
YAML.should_receive(:load).with(transformed).and_return({ "my_db" => {"database" => "one"} })
subject.load_config
end
it "should store the relevant config in connection_hash" do
subject.load_config
subject.connection_hash.should == {"database" => "one"}
end
it "should skip config if config file is not available" do
File.should_receive(:file?).with(config_location).and_return(false)
subject.load_config
subject.connection_hash.should be_blank
end
it "skips the file when the db is set to :default" do
# to avoid https://github.com/bmabey/database_cleaner/issues/72
subject.db = :default
YAML.should_not_receive(:load)
subject.load_config
end
end
describe "connection_hash" do
it "should store connection_hash" do
subject.connection_hash = { :key => "value" }
subject.connection_hash.should == { :key => "value" }
end
end
describe "create_connection_klass" do
it "should return a class" do
subject.create_connection_klass.should be_a(Class)
end
it "should return a class extending ::ActiveRecord::Base" do
subject.create_connection_klass.ancestors.should include(::ActiveRecord::Base)
end
end
describe "connection_klass" do
it { expect{ subject.connection_klass }.to_not raise_error }
it "should default to ActiveRecord::Base" do
subject.connection_klass.should == ::ActiveRecord::Base
end
context "when connection_hash is set" do
let(:hash) { mock("hash") }
before { subject.stub(:connection_hash).and_return(hash) }
it "should create connection_klass if it doesnt exist if connection_hash is set" do
subject.should_receive(:create_connection_klass).and_return(mock('class').as_null_object)
subject.connection_klass
end
it "should configure the class from create_connection_klass if connection_hash is set" do
klass = mock('klass')
klass.should_receive(:establish_connection).with(hash)
subject.should_receive(:create_connection_klass).and_return(klass)
subject.connection_klass
end
it "allows for the model to be passed in as a string" do
subject.db = "FakeModel"
subject.connection.should == :fake_connection
end
end
end