database_cleaner 0.9.1 → 1.0.0.RC1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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