database_cleaner 0.9.1 → 1.0.0.RC1

Sign up to get free protection for your applications and to get access to all the features.
@@ -32,9 +32,9 @@ module DatabaseCleaner
32
32
  end
33
33
 
34
34
  def load_config
35
- if self.db != :default && File.file?(ActiveRecord.config_file_location)
36
- connection_details = YAML::load(ERB.new(IO.read(ActiveRecord.config_file_location)).result)
37
- @connection_hash = connection_details[self.db.to_s]
35
+ if self.db != :default && self.db.is_a?(Symbol) && File.file?(ActiveRecord.config_file_location)
36
+ connection_details = YAML::load(ERB.new(IO.read(ActiveRecord.config_file_location)).result)
37
+ @connection_hash = connection_details[self.db.to_s]
38
38
  end
39
39
  end
40
40
 
@@ -43,12 +43,12 @@ module DatabaseCleaner
43
43
  end
44
44
 
45
45
  def connection_class
46
- @connection_class ||= if @db == :default || (@db.nil? && connection_hash.nil?)
47
- ::ActiveRecord::Base
46
+ @connection_class ||= if @db && !@db.is_a?(Symbol)
47
+ @db
48
48
  elsif connection_hash
49
49
  lookup_from_connection_pool || establish_connection
50
50
  else
51
- @db # allows for an actual class to be passed in
51
+ ::ActiveRecord::Base
52
52
  end
53
53
  end
54
54
 
@@ -57,8 +57,8 @@ module DatabaseCleaner
57
57
  def lookup_from_connection_pool
58
58
  if ::ActiveRecord::Base.respond_to?(:descendants)
59
59
  database_name = connection_hash["database"] || connection_hash[:database]
60
- models = ::ActiveRecord::Base.descendants
61
- models.detect {|m| m.connection_pool.spec.config[:database] == database_name}
60
+ models = ::ActiveRecord::Base.descendants
61
+ models.detect { |m| m.connection_pool.spec.config[:database] == database_name }
62
62
  end
63
63
  end
64
64
 
@@ -67,7 +67,7 @@ module DatabaseCleaner
67
67
  strategy_class.send :establish_connection, connection_hash
68
68
  strategy_class
69
69
  end
70
-
70
+
71
71
  end
72
72
  end
73
73
  end
@@ -3,51 +3,6 @@ require 'active_record/connection_adapters/abstract_adapter'
3
3
  require "database_cleaner/generic/truncation"
4
4
  require 'database_cleaner/active_record/base'
5
5
  require 'database_cleaner/active_record/truncation'
6
- # This file may seem to have duplication with that of truncation, but by keeping them separate
7
- # we avoiding loading this code when it is not being used (which is the common case).
8
-
9
- module ActiveRecord
10
- module ConnectionAdapters
11
-
12
- class MysqlAdapter < MYSQL_ADAPTER_PARENT
13
- def delete_table(table_name)
14
- execute("DELETE FROM #{quote_table_name(table_name)};")
15
- end
16
- end
17
-
18
- class Mysql2Adapter < MYSQL2_ADAPTER_PARENT
19
- def delete_table(table_name)
20
- execute("DELETE FROM #{quote_table_name(table_name)};")
21
- end
22
- end
23
-
24
- class JdbcAdapter < AbstractAdapter
25
- def delete_table(table_name)
26
- execute("DELETE FROM #{quote_table_name(table_name)};")
27
- end
28
- end
29
-
30
- class PostgreSQLAdapter < POSTGRE_ADAPTER_PARENT
31
- def delete_table(table_name)
32
- execute("DELETE FROM #{quote_table_name(table_name)};")
33
- end
34
- end
35
-
36
- class SQLServerAdapter < AbstractAdapter
37
- def delete_table(table_name)
38
- execute("DELETE FROM #{quote_table_name(table_name)};")
39
- end
40
- end
41
-
42
- class OracleEnhancedAdapter < AbstractAdapter
43
- def delete_table(table_name)
44
- execute("DELETE FROM #{quote_table_name(table_name)}")
45
- end
46
- end
47
-
48
- end
49
- end
50
-
51
6
 
52
7
  module DatabaseCleaner::ActiveRecord
53
8
  class Deletion < Truncation
@@ -55,9 +10,11 @@ module DatabaseCleaner::ActiveRecord
55
10
  def clean
56
11
  connection = connection_class.connection
57
12
  connection.disable_referential_integrity do
58
- tables_to_truncate(connection).each do |table_name|
59
- connection.delete_table table_name
60
- end
13
+ sql = tables_to_truncate(connection).map do |table_name|
14
+ "DELETE FROM #{connection.quote_table_name(table_name)}"
15
+ end.join(";")
16
+
17
+ connection.execute sql
61
18
  end
62
19
  end
63
20
 
@@ -7,25 +7,47 @@ module DatabaseCleaner::ActiveRecord
7
7
  include ::DatabaseCleaner::Generic::Transaction
8
8
 
9
9
  def start
10
- if connection_class.connection.respond_to?(:increment_open_transactions)
11
- connection_class.connection.increment_open_transactions
10
+ if connection_maintains_transaction_count?
11
+ if connection_class.connection.respond_to?(:increment_open_transactions)
12
+ connection_class.connection.increment_open_transactions
13
+ else
14
+ connection_class.__send__(:increment_open_transactions)
15
+ end
16
+ end
17
+ if connection_class.connection.respond_to?(:begin_transaction)
18
+ connection_class.connection.begin_transaction
12
19
  else
13
- connection_class.__send__(:increment_open_transactions)
20
+ connection_class.connection.begin_db_transaction
14
21
  end
15
- connection_class.connection.begin_db_transaction
16
22
  end
17
23
 
18
24
 
19
25
  def clean
20
26
  return unless connection_class.connection.open_transactions > 0
21
27
 
22
- connection_class.connection.rollback_db_transaction
23
-
24
- if connection_class.connection.respond_to?(:decrement_open_transactions)
25
- connection_class.connection.decrement_open_transactions
28
+ if connection_class.connection.respond_to?(:rollback_transaction)
29
+ connection_class.connection.rollback_transaction
26
30
  else
27
- connection_class.__send__(:decrement_open_transactions)
31
+ connection_class.connection.rollback_db_transaction
28
32
  end
33
+
34
+ # The below is for handling after_commit hooks.. see https://github.com/bmabey/database_cleaner/issues/99
35
+ if connection_class.connection.respond_to?(:rollback_transaction_records)
36
+ connection_class.connection.send(:rollback_transaction_records, true)
37
+ end
38
+
39
+ if connection_maintains_transaction_count?
40
+ if connection_class.connection.respond_to?(:decrement_open_transactions)
41
+ connection_class.connection.decrement_open_transactions
42
+ else
43
+ connection_class.__send__(:decrement_open_transactions)
44
+ end
45
+ end
46
+ end
47
+
48
+ def connection_maintains_transaction_count?
49
+ ActiveRecord::VERSION::MAJOR < 4
29
50
  end
51
+
30
52
  end
31
53
  end
@@ -55,7 +55,9 @@ module DatabaseCleaner
55
55
 
56
56
 
57
57
  def row_count(table)
58
- select_value("SELECT EXISTS (SELECT 1 FROM #{quote_table_name(table)} LIMIT 1)")
58
+ # Patch for MysqlAdapter with ActiveRecord 3.2.7 later
59
+ # select_value("SELECT 1") #=> "1"
60
+ select_value("SELECT EXISTS (SELECT 1 FROM #{quote_table_name(table)} LIMIT 1)").to_i
59
61
  end
60
62
 
61
63
  # Returns a boolean indicating if the given table has an auto-inc number higher than 0.
@@ -66,7 +68,9 @@ module DatabaseCleaner
66
68
  if row_count(table) > 0
67
69
  true
68
70
  else
69
- select_value(<<-SQL) > 1 # returns nil if not present
71
+ # Patch for MysqlAdapter with ActiveRecord 3.2.7 later
72
+ # select_value("SELECT 1") #=> "1"
73
+ select_value(<<-SQL).to_i > 1 # returns nil if not present
70
74
  SELECT Auto_increment
71
75
  FROM information_schema.tables
72
76
  WHERE table_name='#{table}';
@@ -90,9 +94,18 @@ module DatabaseCleaner
90
94
  module SQLiteAdapter
91
95
  def delete_table(table_name)
92
96
  execute("DELETE FROM #{quote_table_name(table_name)};")
93
- execute("DELETE FROM sqlite_sequence where name = '#{table_name}';")
97
+ if uses_sequence
98
+ execute("DELETE FROM sqlite_sequence where name = '#{table_name}';")
99
+ end
94
100
  end
95
101
  alias truncate_table delete_table
102
+
103
+ private
104
+
105
+ # Returns a boolean indicating if the SQLite database is using the sqlite_sequence table.
106
+ def uses_sequence
107
+ select_value("SELECT name FROM sqlite_master WHERE type='table' AND name='sqlite_sequence';")
108
+ end
96
109
  end
97
110
 
98
111
  module TruncateOrDelete
@@ -139,8 +152,8 @@ module DatabaseCleaner
139
152
  # but then the table is cleaned. In other words, this function tells us if the given table
140
153
  # was ever inserted into.
141
154
  def has_been_used?(table)
142
- cur_val = select_value("SELECT currval('#{table}_id_seq');").to_i rescue ActiveRecord::StatementInvalid
143
- cur_val && cur_val > 0
155
+ cur_val = select_value("SELECT currval('#{table}_id_seq');").to_i rescue 0
156
+ cur_val > 0
144
157
  end
145
158
 
146
159
  def has_rows?(table)
@@ -182,7 +195,11 @@ module ActiveRecord
182
195
  MYSQL2_ADAPTER_PARENT = AbstractAdapter
183
196
  end
184
197
 
185
- SQLITE_ADAPTER_PARENT = USE_ARJDBC_WORKAROUND ? JdbcAdapter : SQLiteAdapter
198
+ if defined?(SQLite3Adapter) && SQLite3Adapter.superclass == ActiveRecord::ConnectionAdapters::AbstractAdapter
199
+ SQLITE_ADAPTER_PARENT = USE_ARJDBC_WORKAROUND ? JdbcAdapter : AbstractAdapter
200
+ else
201
+ SQLITE_ADAPTER_PARENT = USE_ARJDBC_WORKAROUND ? JdbcAdapter : SQLiteAdapter
202
+ end
186
203
  POSTGRE_ADAPTER_PARENT = USE_ARJDBC_WORKAROUND ? JdbcAdapter : AbstractAdapter
187
204
 
188
205
  class MysqlAdapter < MYSQL_ADAPTER_PARENT
@@ -53,7 +53,9 @@ module DataMapper
53
53
 
54
54
  def truncate_table(table_name)
55
55
  execute("DELETE FROM #{quote_name(table_name)};")
56
- execute("DELETE FROM sqlite_sequence where name = '#{table_name}';")
56
+ if uses_sequence
57
+ execute("DELETE FROM sqlite_sequence where name = '#{table_name}';")
58
+ end
57
59
  end
58
60
 
59
61
  # this is a no-op copied from activerecord
@@ -80,7 +82,9 @@ module DataMapper
80
82
 
81
83
  def truncate_table(table_name)
82
84
  execute("DELETE FROM #{quote_name(table_name)};")
83
- execute("DELETE FROM sqlite_sequence where name = '#{table_name}';")
85
+ if uses_sequence
86
+ execute("DELETE FROM sqlite_sequence where name = '#{table_name}';")
87
+ end
84
88
  end
85
89
 
86
90
  # this is a no-op copied from activerecord
@@ -35,7 +35,7 @@ module DatabaseCleaner
35
35
  private
36
36
 
37
37
  def tables_to_truncate(db)
38
- (@only || db.tables) - @tables_to_exclude
38
+ (@only || db.tables.map(&:to_s)) - @tables_to_exclude
39
39
  end
40
40
 
41
41
  # overwritten
@@ -62,7 +62,7 @@ module DatabaseCleaner
62
62
 
63
63
  before do
64
64
  subject.db = :my_db
65
- yaml = <<-Y
65
+ yaml = <<-Y
66
66
  my_db:
67
67
  database: <%= "ONE".downcase %>
68
68
  Y
@@ -71,7 +71,7 @@ my_db:
71
71
  end
72
72
 
73
73
  it "should parse the config" do
74
- YAML.should_receive(:load).and_return( {:nil => nil} )
74
+ YAML.should_receive(:load).and_return({ :nil => nil })
75
75
  subject.load_config
76
76
  end
77
77
 
@@ -80,19 +80,26 @@ my_db:
80
80
  my_db:
81
81
  database: one
82
82
  Y
83
- YAML.should_receive(:load).with(transformed).and_return({ "my_db" => {"database" => "one"} })
83
+ YAML.should_receive(:load).with(transformed).and_return({ "my_db" => { "database" => "one" } })
84
84
  subject.load_config
85
85
  end
86
86
 
87
87
  it "should store the relevant config in connection_hash" do
88
88
  subject.load_config
89
- subject.connection_hash.should == {"database" => "one"}
89
+ subject.connection_hash.should == { "database" => "one" }
90
90
  end
91
91
 
92
92
  it "should skip config if config file is not available" do
93
93
  File.should_receive(:file?).with(config_location).and_return(false)
94
94
  subject.load_config
95
- subject.connection_hash.should be_blank
95
+ subject.connection_hash.should_not be
96
+ end
97
+
98
+ it "skips the file when the model is set" do
99
+ subject.db = FakeModel
100
+ YAML.should_not_receive(:load)
101
+ subject.load_config
102
+ subject.connection_hash.should_not be
96
103
  end
97
104
 
98
105
  it "skips the file when the db is set to :default" do
@@ -100,6 +107,7 @@ my_db:
100
107
  subject.db = :default
101
108
  YAML.should_not_receive(:load)
102
109
  subject.load_config
110
+ subject.connection_hash.should_not be
103
111
  end
104
112
 
105
113
  end
@@ -122,19 +130,32 @@ my_db:
122
130
  end
123
131
 
124
132
  describe "connection_class" do
125
- it { expect{ subject.connection_class }.to_not raise_error }
133
+ it { expect { subject.connection_class }.to_not raise_error }
126
134
  it "should default to ActiveRecord::Base" do
127
135
  subject.connection_class.should == ::ActiveRecord::Base
128
136
  end
129
137
 
130
- it "allows for database models to be passed in" do
131
- subject.db = FakeModel
132
- subject.connection_class.should == FakeModel
138
+ context "with database models" do
139
+ context "connection_hash is set" do
140
+ it "allows for database models to be passed in" do
141
+ subject.db = FakeModel
142
+ subject.connection_hash = { }
143
+ subject.load_config
144
+ subject.connection_class.should == FakeModel
145
+ end
146
+ end
147
+
148
+ context "connection_hash is not set" do
149
+ it "allows for database models to be passed in" do
150
+ subject.db = FakeModel
151
+ subject.connection_class.should == FakeModel
152
+ end
153
+ end
133
154
  end
134
155
 
135
156
  context "when connection_hash is set" do
136
157
  let(:hash) { mock("hash") }
137
- before { ::ActiveRecord::Base.stub!(:respond_to?).and_return(false)}
158
+ before { ::ActiveRecord::Base.stub!(:respond_to?).and_return(false) }
138
159
  before { subject.stub(:connection_hash).and_return(hash) }
139
160
 
140
161
  it "should create connection_class if it doesnt exist if connection_hash is set" do
@@ -142,7 +163,7 @@ my_db:
142
163
  subject.connection_class
143
164
  end
144
165
 
145
- it "should configure the class from create_connection_class if connection_hash is set" do
166
+ it "should configure the class from create_connection_class if connection_hash is set" do
146
167
  strategy_class = mock('strategy_class')
147
168
  strategy_class.should_receive(:establish_connection).with(hash)
148
169
 
@@ -8,70 +8,129 @@ module DatabaseCleaner
8
8
  describe Transaction do
9
9
  let (:connection) { mock("connection") }
10
10
  before(:each) do
11
- ::ActiveRecord::Base.stub!(:connection).and_return(connection)
11
+ ::ActiveRecord::Base.stub(:connection).and_return(connection)
12
12
  end
13
13
 
14
14
  describe "#start" do
15
- it "should increment open transactions if possible" do
16
- connection.stub!(:respond_to?).with(:increment_open_transactions).and_return(true)
17
- connection.stub!(:begin_db_transaction)
18
-
19
- connection.should_receive(:increment_open_transactions)
20
- Transaction.new.start
21
- end
22
-
23
- it "should tell ActiveRecord to increment connection if its not possible to increment current connection" do
24
- connection.stub!(:respond_to?).with(:increment_open_transactions).and_return(false)
25
- connection.stub!(:begin_db_transaction)
26
-
27
- ::ActiveRecord::Base.should_receive(:increment_open_transactions)
28
- Transaction.new.start
29
- end
30
-
31
- it "should start a transaction" do
32
- connection.stub!(:increment_open_transactions)
33
-
34
- connection.should_receive(:begin_db_transaction)
35
- Transaction.new.start
15
+ [:begin_transaction, :begin_db_transaction].each do |begin_transaction_method|
16
+ context "using #{begin_transaction_method}" do
17
+ before do
18
+ connection.stub(begin_transaction_method)
19
+ connection.stub(:respond_to?).with(:begin_transaction).and_return(:begin_transaction == begin_transaction_method)
20
+ end
21
+
22
+ it "should increment open transactions if possible" do
23
+ connection.stub(:respond_to?).with(:increment_open_transactions).and_return(true)
24
+ connection.should_receive(:increment_open_transactions)
25
+ Transaction.new.start
26
+ end
27
+
28
+ it "should tell ActiveRecord to increment connection if its not possible to increment current connection" do
29
+ connection.stub(:respond_to?).with(:increment_open_transactions).and_return(false)
30
+ ::ActiveRecord::Base.should_receive(:increment_open_transactions)
31
+ Transaction.new.start
32
+ end
33
+
34
+ it "should start a transaction" do
35
+ connection.stub(:respond_to?).with(:increment_open_transactions).and_return(true)
36
+ connection.stub(:increment_open_transactions)
37
+ connection.should_receive(begin_transaction_method)
38
+ Transaction.new.start
39
+ end
40
+ end
36
41
  end
37
42
  end
38
43
 
39
44
  describe "#clean" do
40
- it "should start a transaction" do
45
+ context "manual accounting of transaction count" do
46
+ it "should start a transaction" do
41
47
  connection.should_receive(:open_transactions).and_return(1)
42
48
 
43
- connection.stub!(:decrement_open_transactions)
49
+ connection.stub(:decrement_open_transactions)
44
50
 
45
51
  connection.should_receive(:rollback_db_transaction)
46
52
  Transaction.new.clean
47
- end
53
+ end
54
+
55
+ it "should decrement open transactions if possible" do
56
+ connection.should_receive(:open_transactions).and_return(1)
48
57
 
49
- it "should decrement open transactions if possible" do
50
- connection.should_receive(:open_transactions).and_return(1)
58
+ connection.stub(:respond_to?).with(:decrement_open_transactions).and_return(true)
59
+ connection.stub(:respond_to?).with(:rollback_transaction_records).and_return(false)
60
+ connection.stub(:respond_to?).with(:rollback_transaction).and_return(false)
61
+ connection.stub(:rollback_db_transaction)
51
62
 
52
- connection.stub!(:respond_to?).with(:decrement_open_transactions).and_return(true)
53
- connection.stub!(:rollback_db_transaction)
63
+ connection.should_receive(:decrement_open_transactions)
64
+ Transaction.new.clean
65
+ end
54
66
 
55
- connection.should_receive(:decrement_open_transactions)
56
- Transaction.new.clean
57
- end
67
+ it "should not try to decrement or rollback if open_transactions is 0 for whatever reason" do
68
+ connection.should_receive(:open_transactions).and_return(0)
58
69
 
59
- it "should not try to decrement or rollback if open_transactions is 0 for whatever reason" do
60
- connection.should_receive(:open_transactions).and_return(0)
70
+ Transaction.new.clean
71
+ end
61
72
 
62
- Transaction.new.clean
73
+ it "should decrement connection via ActiveRecord::Base if connection won't" do
74
+ connection.should_receive(:open_transactions).and_return(1)
75
+ connection.stub(:respond_to?).with(:decrement_open_transactions).and_return(false)
76
+ connection.stub(:respond_to?).with(:rollback_transaction_records).and_return(false)
77
+ connection.stub(:respond_to?).with(:rollback_transaction).and_return(false)
78
+ connection.stub(:rollback_db_transaction)
79
+
80
+ ::ActiveRecord::Base.should_receive(:decrement_open_transactions)
81
+ Transaction.new.clean
82
+ end
63
83
  end
64
84
 
65
- it "should decrement connection via ActiveRecord::Base if connection won't" do
66
- connection.should_receive(:open_transactions).and_return(1)
67
- connection.stub!(:respond_to?).with(:decrement_open_transactions).and_return(false)
68
- connection.stub!(:rollback_db_transaction)
85
+ context "automatic accounting of transaction count (AR 4)" do
86
+ before {stub_const("ActiveRecord::VERSION::MAJOR", 4) }
87
+
88
+ it "should start a transaction" do
89
+ connection.stub(:rollback_db_transaction)
90
+ connection.should_receive(:open_transactions).and_return(1)
91
+
92
+ connection.should_not_receive(:decrement_open_transactions)
93
+ connection.should_receive(:rollback_transaction)
94
+ Transaction.new.clean
95
+ end
96
+
97
+ it "should decrement open transactions if possible" do
98
+ connection.stub(:rollback_transaction)
99
+ connection.should_receive(:open_transactions).and_return(1)
100
+
101
+ connection.should_not_receive(:decrement_open_transactions)
102
+ Transaction.new.clean
103
+ end
104
+
105
+ it "should not try to decrement or rollback if open_transactions is 0 for whatever reason" do
106
+ connection.should_receive(:open_transactions).and_return(0)
107
+
108
+ Transaction.new.clean
109
+ end
110
+
111
+ it "should decrement connection via ActiveRecord::Base if connection won't" do
112
+ connection.should_receive(:open_transactions).and_return(1)
113
+ connection.stub(:respond_to?).with(:rollback_transaction_records).and_return(false)
114
+ connection.stub(:respond_to?).with(:rollback_transaction).and_return(true)
115
+ connection.stub(:rollback_transaction)
69
116
 
70
- ::ActiveRecord::Base.should_receive(:decrement_open_transactions)
71
- Transaction.new.clean
117
+ ::ActiveRecord::Base.should_not_receive(:decrement_open_transactions)
118
+ Transaction.new.clean
119
+ end
72
120
  end
73
121
  end
74
- end
75
122
 
123
+ describe "#connection_maintains_transaction_count?" do
124
+ it "should return true if the major active record version is < 4" do
125
+ stub_const("ActiveRecord::VERSION::MAJOR", 3)
126
+ Transaction.new.connection_maintains_transaction_count?.should be_true
127
+ end
128
+ it "should return false if the major active record version is > 3" do
129
+ stub_const("ActiveRecord::VERSION::MAJOR", 4)
130
+ Transaction.new.connection_maintains_transaction_count?.should be_false
131
+ end
132
+ end
133
+
134
+ end
76
135
  end
77
136
  end