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 DatabaseCleaner[:mongo_mapper].strategy = :truncation
#How to specify particular connections #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> </pre>
Usage beyond that remains the same with DatabaseCleaner.start calling any setup on the different configured connections, and DatabaseCleaner.clean executing afterwards. 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 Given /^I have setup database cleaner to clean multiple databases using activerecord$/ do
#DatabaseCleaner #DatabaseCleaner
# require "#{File.dirname(__FILE__)}/../../../lib/datamapper_models" # require "#{File.dirname(__FILE__)}/../../../lib/activerecord_models"
# #
# DatabaseCleaner[:datamapper, {:connection => :one} ].strategy = :truncation # DatabaseCleaner[:active_record, {:connection => 'ActiveRecordWidgetUsingDatabaseOne'} ].strategy = whatever
# DatabaseCleaner[:datamapper, {:connection => :two} ].strategy = :truncation # DatabaseCleaner[:active_record, {:connection => 'ActiveRecordWidgetUsingDatabaseTwo'} ].strategy = whatever
end end
When /^I create a widget using activerecord$/ do When /^I create a widget using activerecord$/ do

View File

@ -42,9 +42,13 @@ if orm && strategy
DatabaseCleaner.app_root = "#{File.dirname(__FILE__)}/../.." DatabaseCleaner.app_root = "#{File.dirname(__FILE__)}/../.."
orm_sym = orm.gsub(/(.)([A-Z]+)/,'\1_\2').downcase.to_sym orm_sym = orm.gsub(/(.)([A-Z]+)/,'\1_\2').downcase.to_sym
if orm_sym == :mongo_mapper case orm_sym
DatabaseCleaner[ orm_sym, {:connection => 'database_cleaner_test_one'} ].strategy = strategy.to_sym when :mongo_mapper
DatabaseCleaner[ orm_sym, {:connection => 'database_cleaner_test_two'} ].strategy = strategy.to_sym 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 else
DatabaseCleaner[ orm_sym, {:connection => :one} ].strategy = strategy.to_sym DatabaseCleaner[ orm_sym, {:connection => :one} ].strategy = strategy.to_sym
DatabaseCleaner[ orm_sym, {:connection => :two} ].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 | | DataMapper | truncation |
| MongoMapper | truncation | | MongoMapper | truncation |
| DataMapper | transaction | | DataMapper | transaction |
# Not working... | ActiveRecord | transaction |
#| ActiveRecord | transaction |

View File

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

View File

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

View File

@ -4,24 +4,24 @@ module DatabaseCleaner::ActiveRecord
include ::DatabaseCleaner::ActiveRecord::Base include ::DatabaseCleaner::ActiveRecord::Base
def start def start
if connection_klass.connection.respond_to?(:increment_open_transactions) if connection.respond_to?(:increment_open_transactions)
connection_klass.connection.increment_open_transactions connection.increment_open_transactions
else else
connection_klass.__send__(:increment_open_transactions) connection_class.__send__(:increment_open_transactions)
end end
connection_klass.connection.begin_db_transaction connection.begin_db_transaction
end end
def clean 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) if connection.respond_to?(:decrement_open_transactions)
connection_klass.connection.decrement_open_transactions connection.decrement_open_transactions
else else
connection_klass.__send__(:decrement_open_transactions) connection_class.__send__(:decrement_open_transactions)
end end
end end
end end

View File

@ -86,7 +86,7 @@ module ActiveRecord
def truncate_table(table_name) def truncate_table(table_name)
truncate_tables([table_name]) truncate_tables([table_name])
end end
def truncate_tables(table_names) def truncate_tables(table_names)
execute("TRUNCATE TABLE #{table_names.map{|name| quote_table_name(name)}.join(', ')} #{restart_identity} #{cascade};") execute("TRUNCATE TABLE #{table_names.map{|name| quote_table_name(name)}.join(', ')} #{restart_identity} #{cascade};")
end end
@ -119,7 +119,6 @@ module DatabaseCleaner::ActiveRecord
include ::DatabaseCleaner::Generic::Truncation include ::DatabaseCleaner::Generic::Truncation
def clean def clean
connection = connection_klass.connection
connection.disable_referential_integrity do connection.disable_referential_integrity do
connection.truncate_tables(tables_to_truncate(connection)) connection.truncate_tables(tables_to_truncate(connection))
end end
@ -138,6 +137,3 @@ module DatabaseCleaner::ActiveRecord
end end
end end

View File

@ -3,140 +3,37 @@ require 'active_record'
require 'database_cleaner/active_record/base' require 'database_cleaner/active_record/base'
require 'database_cleaner/shared_strategy_spec' require 'database_cleaner/shared_strategy_spec'
module DatabaseCleaner class FakeModel
describe ActiveRecord do def self.connection
it { should respond_to(:available_strategies) } :fake_connection
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 end
end
module DatabaseCleaner
module ActiveRecord module ActiveRecord
class ExampleStrategy class ExampleStrategy
include ::DatabaseCleaner::ActiveRecord::Base include ::DatabaseCleaner::ActiveRecord::Base
end end
describe ExampleStrategy do 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" it_should_behave_like "a generic strategy"
describe "db" do
it "should store my desired db" do describe "#connection" do
subject.stub(:load_config) it "returns the connection from ActiveRecord::Base by default" do
::ActiveRecord::Base.stub!(:connection).and_return(:fake_connection)
subject.db = :my_db subject.connection.should == :fake_connection
subject.db.should == :my_db
end end
it "should default to :default" do it "returns the connection of the model provided" do
subject.db.should == :default subject.db = FakeModel
subject.connection.should == :fake_connection
end end
it "should load_config when I set db" do it "allows for the model to be passed in as a string" do
subject.should_receive(:load_config) subject.db = "FakeModel"
subject.db = :my_db subject.connection.should == :fake_connection
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
end end
end end
end end