Skip to content

Commit

Permalink
fixes #22, AR code doesn't create new connections like it is going ou…
Browse files Browse the repository at this point in the history
…t of style

The code that switches ActiveRecord adapters to take a model class
instead of a connection hash or name.
  • Loading branch information
bmabey committed Dec 20, 2011
1 parent 501025b commit 604c9cf
Show file tree
Hide file tree
Showing 9 changed files with 51 additions and 170 deletions.
8 changes: 7 additions & 1 deletion README.textile
Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions examples/features/step_definitions/activerecord_steps.rb
@@ -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
Expand Down
10 changes: 7 additions & 3 deletions examples/features/support/env.rb
Expand Up @@ -42,9 +42,13 @@
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
Expand Down
3 changes: 1 addition & 2 deletions features/cleaning_multiple_dbs.feature
Expand Up @@ -17,5 +17,4 @@ Feature: multiple database cleaning
| DataMapper | truncation |
| MongoMapper | truncation |
| DataMapper | transaction |
# Not working...
#| ActiveRecord | transaction |
| ActiveRecord | transaction |
36 changes: 8 additions & 28 deletions lib/database_cleaner/active_record/base.rb
Expand Up @@ -9,44 +9,24 @@ def self.available_strategies
%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
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
@model_class || ::ActiveRecord::Base
end

def create_connection_klass
Class.new(::ActiveRecord::Base)
def connection
connection_class.connection
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
Expand Down
1 change: 0 additions & 1 deletion lib/database_cleaner/active_record/deletion.rb
Expand Up @@ -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
Expand Down
18 changes: 9 additions & 9 deletions lib/database_cleaner/active_record/transaction.rb
Expand Up @@ -4,24 +4,24 @@ class Transaction
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
Expand Down
6 changes: 1 addition & 5 deletions lib/database_cleaner/active_record/truncation.rb
Expand Up @@ -86,7 +86,7 @@ def restart_identity
def truncate_table(table_name)
truncate_tables([table_name])
end

def truncate_tables(table_names)
execute("TRUNCATE TABLE #{table_names.map{|name| quote_table_name(name)}.join(', ')} #{restart_identity} #{cascade};")
end
Expand Down Expand Up @@ -119,7 +119,6 @@ class Truncation
include ::DatabaseCleaner::Generic::Truncation

def clean
connection = connection_klass.connection
connection.disable_referential_integrity do
connection.truncate_tables(tables_to_truncate(connection))
end
Expand All @@ -138,6 +137,3 @@ def migration_storage_name

end
end



133 changes: 15 additions & 118 deletions spec/database_cleaner/active_record/base_spec.rb
Expand Up @@ -3,140 +3,37 @@
require 'database_cleaner/active_record/base'
require 'database_cleaner/shared_strategy_spec'

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

class FakeModel
def self.connection
:fake_connection
end
end

module DatabaseCleaner
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
end

it "should default to :default" do
subject.db.should == :default
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
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 "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
it "returns the connection of the model provided" do
subject.db = FakeModel
subject.connection.should == :fake_connection
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
Expand Down

0 comments on commit 604c9cf

Please sign in to comment.