database_cleaner 0.8.0 → 0.9.1
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.
- data/Gemfile.lock +150 -82
- data/History.txt +19 -1
- data/README.textile +61 -59
- data/Rakefile +7 -17
- data/VERSION.yml +3 -3
- data/examples/Gemfile +14 -20
- data/examples/Gemfile.lock +150 -82
- data/examples/features/step_definitions/translation_steps.rb +14 -14
- data/examples/features/support/env.rb +6 -2
- data/features/cleaning.feature +1 -1
- data/features/cleaning_default_strategy.feature +1 -1
- data/features/cleaning_multiple_dbs.feature +1 -2
- data/features/cleaning_multiple_orms.feature +7 -7
- data/features/support/env.rb +1 -1
- data/lib/database_cleaner/active_record/base.rb +26 -6
- data/lib/database_cleaner/active_record/deletion.rb +2 -2
- data/lib/database_cleaner/active_record/transaction.rb +9 -9
- data/lib/database_cleaner/active_record/truncation.rb +157 -50
- data/lib/database_cleaner/base.rb +2 -2
- data/lib/database_cleaner/configuration.rb +26 -3
- data/lib/database_cleaner/data_mapper/truncation.rb +2 -2
- data/lib/database_cleaner/generic/truncation.rb +7 -5
- data/lib/database_cleaner/mongo/base.rb +16 -0
- data/lib/database_cleaner/mongo/truncation.rb +9 -14
- data/lib/database_cleaner/mongo/truncation_mixin.rb +22 -0
- data/lib/database_cleaner/mongo_mapper/truncation.rb +2 -2
- data/lib/database_cleaner/mongoid/truncation.rb +2 -2
- data/lib/database_cleaner/moped/truncation.rb +6 -2
- data/lib/database_cleaner/sequel/truncation.rb +6 -6
- data/spec/database_cleaner/active_record/base_spec.rb +27 -15
- data/spec/database_cleaner/active_record/truncation/mysql2_spec.rb +40 -0
- data/spec/database_cleaner/active_record/truncation/mysql_spec.rb +40 -0
- data/spec/database_cleaner/active_record/truncation/postgresql_spec.rb +48 -0
- data/spec/database_cleaner/active_record/truncation/shared_fast_truncation.rb +40 -0
- data/spec/database_cleaner/active_record/truncation_spec.rb +102 -33
- data/spec/database_cleaner/base_spec.rb +14 -14
- data/spec/database_cleaner/configuration_spec.rb +15 -10
- data/spec/database_cleaner/data_mapper/base_spec.rb +1 -1
- data/spec/database_cleaner/data_mapper/transaction_spec.rb +1 -1
- data/spec/database_cleaner/data_mapper/truncation_spec.rb +1 -1
- data/spec/database_cleaner/generic/base_spec.rb +1 -1
- data/spec/database_cleaner/generic/truncation_spec.rb +35 -5
- data/spec/database_cleaner/mongo/mongo_examples.rb +26 -0
- data/spec/database_cleaner/mongo/truncation_spec.rb +72 -0
- data/spec/database_cleaner/mongo_mapper/base_spec.rb +1 -1
- data/spec/database_cleaner/sequel/base_spec.rb +1 -1
- data/spec/database_cleaner/sequel/transaction_spec.rb +1 -1
- data/spec/database_cleaner/sequel/truncation_spec.rb +1 -1
- data/spec/database_cleaner/{shared_strategy_spec.rb → shared_strategy.rb} +0 -0
- data/spec/spec_helper.rb +6 -4
- data/spec/support/active_record/database_setup.rb +4 -0
- data/spec/support/active_record/mysql2_setup.rb +38 -0
- data/spec/support/active_record/mysql_setup.rb +38 -0
- data/spec/support/active_record/postgresql_setup.rb +41 -0
- data/spec/support/active_record/schema_setup.rb +10 -0
- metadata +313 -46
- data/spec/spec.opts +0 -7
@@ -5,7 +5,7 @@ require 'rubygems'
|
|
5
5
|
require 'bundler'
|
6
6
|
|
7
7
|
Bundler.setup
|
8
|
-
require '
|
8
|
+
require 'rspec/expectations'
|
9
9
|
require 'ruby-debug'
|
10
10
|
|
11
11
|
DB_DIR = "#{File.dirname(__FILE__)}/../../db"
|
@@ -42,9 +42,13 @@ if orm && strategy
|
|
42
42
|
DatabaseCleaner.app_root = "#{File.dirname(__FILE__)}/../.."
|
43
43
|
orm_sym = orm.gsub(/(.)([A-Z]+)/,'\1_\2').downcase.to_sym
|
44
44
|
|
45
|
-
|
45
|
+
case orm_sym
|
46
|
+
when :mongo_mapper
|
46
47
|
DatabaseCleaner[ orm_sym, {:connection => 'database_cleaner_test_one'} ].strategy = strategy.to_sym
|
47
48
|
DatabaseCleaner[ orm_sym, {:connection => 'database_cleaner_test_two'} ].strategy = strategy.to_sym
|
49
|
+
when :active_record
|
50
|
+
DatabaseCleaner[:active_record, {:model => ActiveRecordWidgetUsingDatabaseOne} ].strategy = strategy.to_sym
|
51
|
+
DatabaseCleaner[:active_record, {:model => ActiveRecordWidgetUsingDatabaseTwo} ].strategy = strategy.to_sym
|
48
52
|
else
|
49
53
|
DatabaseCleaner[ orm_sym, {:connection => :one} ].strategy = strategy.to_sym
|
50
54
|
DatabaseCleaner[ orm_sym, {:connection => :two} ].strategy = strategy.to_sym
|
data/features/cleaning.feature
CHANGED
@@ -14,16 +14,16 @@ Feature: database cleaning using multiple ORMs
|
|
14
14
|
| ActiveRecord | DataMapper |
|
15
15
|
| ActiveRecord | MongoMapper |
|
16
16
|
| ActiveRecord | Mongoid |
|
17
|
-
|
17
|
+
| ActiveRecord | CouchPotato |
|
18
18
|
| DataMapper | ActiveRecord |
|
19
19
|
| DataMapper | MongoMapper |
|
20
20
|
| DataMapper | Mongoid |
|
21
|
-
|
21
|
+
| DataMapper | CouchPotato |
|
22
22
|
| MongoMapper | ActiveRecord |
|
23
23
|
| MongoMapper | DataMapper |
|
24
24
|
| MongoMapper | Mongoid |
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
| MongoMapper | CouchPotato |
|
26
|
+
| CouchPotato | ActiveRecord |
|
27
|
+
| CouchPotato | DataMapper |
|
28
|
+
| CouchPotato | MongoMapper |
|
29
|
+
| CouchPotato | Mongoid |
|
data/features/support/env.rb
CHANGED
@@ -38,16 +38,36 @@ module DatabaseCleaner
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
def
|
41
|
+
def create_connection_class
|
42
42
|
Class.new(::ActiveRecord::Base)
|
43
43
|
end
|
44
44
|
|
45
|
-
def
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
45
|
+
def connection_class
|
46
|
+
@connection_class ||= if @db == :default || (@db.nil? && connection_hash.nil?)
|
47
|
+
::ActiveRecord::Base
|
48
|
+
elsif connection_hash
|
49
|
+
lookup_from_connection_pool || establish_connection
|
50
|
+
else
|
51
|
+
@db # allows for an actual class to be passed in
|
52
|
+
end
|
50
53
|
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def lookup_from_connection_pool
|
58
|
+
if ::ActiveRecord::Base.respond_to?(:descendants)
|
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}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def establish_connection
|
66
|
+
strategy_class = create_connection_class
|
67
|
+
strategy_class.send :establish_connection, connection_hash
|
68
|
+
strategy_class
|
69
|
+
end
|
70
|
+
|
51
71
|
end
|
52
72
|
end
|
53
73
|
end
|
@@ -27,7 +27,7 @@ module ActiveRecord
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
class PostgreSQLAdapter <
|
30
|
+
class PostgreSQLAdapter < POSTGRE_ADAPTER_PARENT
|
31
31
|
def delete_table(table_name)
|
32
32
|
execute("DELETE FROM #{quote_table_name(table_name)};")
|
33
33
|
end
|
@@ -53,7 +53,7 @@ module DatabaseCleaner::ActiveRecord
|
|
53
53
|
class Deletion < Truncation
|
54
54
|
|
55
55
|
def clean
|
56
|
-
connection =
|
56
|
+
connection = connection_class.connection
|
57
57
|
connection.disable_referential_integrity do
|
58
58
|
tables_to_truncate(connection).each do |table_name|
|
59
59
|
connection.delete_table table_name
|
@@ -7,24 +7,24 @@ module DatabaseCleaner::ActiveRecord
|
|
7
7
|
include ::DatabaseCleaner::Generic::Transaction
|
8
8
|
|
9
9
|
def start
|
10
|
-
if
|
11
|
-
|
10
|
+
if connection_class.connection.respond_to?(:increment_open_transactions)
|
11
|
+
connection_class.connection.increment_open_transactions
|
12
12
|
else
|
13
|
-
|
13
|
+
connection_class.__send__(:increment_open_transactions)
|
14
14
|
end
|
15
|
-
|
15
|
+
connection_class.connection.begin_db_transaction
|
16
16
|
end
|
17
17
|
|
18
18
|
|
19
19
|
def clean
|
20
|
-
return unless
|
20
|
+
return unless connection_class.connection.open_transactions > 0
|
21
21
|
|
22
|
-
|
22
|
+
connection_class.connection.rollback_db_transaction
|
23
23
|
|
24
|
-
if
|
25
|
-
|
24
|
+
if connection_class.connection.respond_to?(:decrement_open_transactions)
|
25
|
+
connection_class.connection.decrement_open_transactions
|
26
26
|
else
|
27
|
-
|
27
|
+
connection_class.__send__(:decrement_open_transactions)
|
28
28
|
end
|
29
29
|
end
|
30
30
|
end
|
@@ -1,18 +1,30 @@
|
|
1
1
|
require 'active_record/base'
|
2
|
+
|
2
3
|
require 'active_record/connection_adapters/abstract_adapter'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'active_record/connection_adapters/abstract_mysql_adapter'
|
7
|
+
rescue LoadError
|
8
|
+
end
|
9
|
+
|
3
10
|
require "database_cleaner/generic/truncation"
|
4
11
|
require 'database_cleaner/active_record/base'
|
5
12
|
|
6
|
-
module
|
7
|
-
module
|
8
|
-
# Activerecord-jdbc-adapter defines class dependencies a bit differently - if it is present, confirm to ArJdbc hierarchy to avoid 'superclass mismatch' errors.
|
9
|
-
USE_ARJDBC_WORKAROUND = defined?(ArJdbc)
|
13
|
+
module DatabaseCleaner
|
14
|
+
module ActiveRecord
|
10
15
|
|
11
|
-
|
12
|
-
|
16
|
+
module AbstractAdapter
|
17
|
+
# used to be called views but that can clash with gems like schema_plus
|
18
|
+
# this gem is not meant to be exposing such an extra interface any way
|
19
|
+
def database_cleaner_view_cache
|
13
20
|
@views ||= select_values("select table_name from information_schema.views where table_schema = '#{current_database}'") rescue []
|
14
21
|
end
|
15
22
|
|
23
|
+
def database_cleaner_table_cache
|
24
|
+
# the adapters don't do caching (#130) but we make the assumption that the list stays the same in tests
|
25
|
+
@database_cleaner_tables ||= tables
|
26
|
+
end
|
27
|
+
|
16
28
|
def truncate_table(table_name)
|
17
29
|
raise NotImplementedError
|
18
30
|
end
|
@@ -24,42 +36,58 @@ module ActiveRecord
|
|
24
36
|
end
|
25
37
|
end
|
26
38
|
|
27
|
-
|
28
|
-
class SQLiteAdapter < AbstractAdapter
|
29
|
-
end
|
30
|
-
end
|
39
|
+
module MysqlAdapter
|
31
40
|
|
32
|
-
# ActiveRecord 3.1 support
|
33
|
-
if defined?(AbstractMysqlAdapter)
|
34
|
-
MYSQL_ADAPTER_PARENT = USE_ARJDBC_WORKAROUND ? JdbcAdapter : AbstractMysqlAdapter
|
35
|
-
MYSQL2_ADAPTER_PARENT = AbstractMysqlAdapter
|
36
|
-
else
|
37
|
-
MYSQL_ADAPTER_PARENT = USE_ARJDBC_WORKAROUND ? JdbcAdapter : AbstractAdapter
|
38
|
-
MYSQL2_ADAPTER_PARENT = AbstractAdapter
|
39
|
-
end
|
40
|
-
|
41
|
-
SQLITE_ADAPTER_PARENT = USE_ARJDBC_WORKAROUND ? JdbcAdapter : SQLiteAdapter
|
42
|
-
POSTGRE_ADAPTER_PARENT = USE_ARJDBC_WORKAROUND ? JdbcAdapter : AbstractAdapter
|
43
|
-
|
44
|
-
class MysqlAdapter < MYSQL_ADAPTER_PARENT
|
45
41
|
def truncate_table(table_name)
|
46
42
|
execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
|
47
43
|
end
|
48
|
-
end
|
49
44
|
|
50
|
-
|
51
|
-
|
52
|
-
|
45
|
+
def truncate_tables(tables)
|
46
|
+
tables.each { |t| truncate_table(t) }
|
47
|
+
end
|
48
|
+
|
49
|
+
def pre_count_truncate_tables(tables, options = {:reset_ids => true})
|
50
|
+
filter = options[:reset_ids] ? method(:has_been_used?) : method(:has_rows?)
|
51
|
+
truncate_tables(tables.select(&filter))
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
|
57
|
+
def row_count(table)
|
58
|
+
select_value("SELECT EXISTS (SELECT 1 FROM #{quote_table_name(table)} LIMIT 1)")
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns a boolean indicating if the given table has an auto-inc number higher than 0.
|
62
|
+
# Note, this is different than an empty table since an table may populated, the index increased,
|
63
|
+
# but then the table is cleaned. In other words, this function tells us if the given table
|
64
|
+
# was ever inserted into.
|
65
|
+
def has_been_used?(table)
|
66
|
+
if row_count(table) > 0
|
67
|
+
true
|
68
|
+
else
|
69
|
+
select_value(<<-SQL) > 1 # returns nil if not present
|
70
|
+
SELECT Auto_increment
|
71
|
+
FROM information_schema.tables
|
72
|
+
WHERE table_name='#{table}';
|
73
|
+
SQL
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def has_rows?(table)
|
78
|
+
row_count(table) > 0
|
53
79
|
end
|
54
80
|
end
|
55
81
|
|
56
|
-
|
82
|
+
|
83
|
+
module IBM_DBAdapter
|
57
84
|
def truncate_table(table_name)
|
58
85
|
execute("TRUNCATE #{quote_table_name(table_name)} IMMEDIATE")
|
59
86
|
end
|
60
87
|
end
|
61
88
|
|
62
|
-
|
89
|
+
|
90
|
+
module SQLiteAdapter
|
63
91
|
def delete_table(table_name)
|
64
92
|
execute("DELETE FROM #{quote_table_name(table_name)};")
|
65
93
|
execute("DELETE FROM sqlite_sequence where name = '#{table_name}';")
|
@@ -67,7 +95,7 @@ module ActiveRecord
|
|
67
95
|
alias truncate_table delete_table
|
68
96
|
end
|
69
97
|
|
70
|
-
|
98
|
+
module TruncateOrDelete
|
71
99
|
def truncate_table(table_name)
|
72
100
|
begin
|
73
101
|
execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
|
@@ -77,8 +105,7 @@ module ActiveRecord
|
|
77
105
|
end
|
78
106
|
end
|
79
107
|
|
80
|
-
|
81
|
-
|
108
|
+
module PostgreSQLAdapter
|
82
109
|
def db_version
|
83
110
|
@db_version ||= postgresql_version
|
84
111
|
end
|
@@ -94,25 +121,34 @@ module ActiveRecord
|
|
94
121
|
def truncate_table(table_name)
|
95
122
|
truncate_tables([table_name])
|
96
123
|
end
|
97
|
-
|
124
|
+
|
98
125
|
def truncate_tables(table_names)
|
99
126
|
return if table_names.nil? || table_names.empty?
|
100
127
|
execute("TRUNCATE TABLE #{table_names.map{|name| quote_table_name(name)}.join(', ')} #{restart_identity} #{cascade};")
|
101
128
|
end
|
102
129
|
|
103
|
-
|
130
|
+
def pre_count_truncate_tables(tables, options = {:reset_ids => true})
|
131
|
+
filter = options[:reset_ids] ? method(:has_been_used?) : method(:has_rows?)
|
132
|
+
truncate_tables(tables.select(&filter))
|
133
|
+
end
|
104
134
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
135
|
+
private
|
136
|
+
|
137
|
+
# Returns a boolean indicating if the given table has an auto-inc number higher than 0.
|
138
|
+
# Note, this is different than an empty table since an table may populated, the index increased,
|
139
|
+
# but then the table is cleaned. In other words, this function tells us if the given table
|
140
|
+
# was ever inserted into.
|
141
|
+
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
|
144
|
+
end
|
145
|
+
|
146
|
+
def has_rows?(table)
|
147
|
+
select_value("SELECT true FROM #{table} LIMIT 1;")
|
112
148
|
end
|
113
149
|
end
|
114
150
|
|
115
|
-
|
151
|
+
module OracleEnhancedAdapter
|
116
152
|
def truncate_table(table_name)
|
117
153
|
execute("TRUNCATE TABLE #{quote_table_name(table_name)}")
|
118
154
|
end
|
@@ -121,6 +157,69 @@ module ActiveRecord
|
|
121
157
|
end
|
122
158
|
end
|
123
159
|
|
160
|
+
#TODO: Remove monkeypatching and decorate the connection instead!
|
161
|
+
|
162
|
+
module ActiveRecord
|
163
|
+
module ConnectionAdapters
|
164
|
+
# Activerecord-jdbc-adapter defines class dependencies a bit differently - if it is present, confirm to ArJdbc hierarchy to avoid 'superclass mismatch' errors.
|
165
|
+
USE_ARJDBC_WORKAROUND = defined?(ArJdbc)
|
166
|
+
|
167
|
+
class AbstractAdapter
|
168
|
+
include ::DatabaseCleaner::ActiveRecord::AbstractAdapter
|
169
|
+
end
|
170
|
+
|
171
|
+
unless USE_ARJDBC_WORKAROUND
|
172
|
+
class SQLiteAdapter < AbstractAdapter
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# ActiveRecord 3.1 support
|
177
|
+
if defined?(AbstractMysqlAdapter)
|
178
|
+
MYSQL_ADAPTER_PARENT = USE_ARJDBC_WORKAROUND ? JdbcAdapter : AbstractMysqlAdapter
|
179
|
+
MYSQL2_ADAPTER_PARENT = AbstractMysqlAdapter
|
180
|
+
else
|
181
|
+
MYSQL_ADAPTER_PARENT = USE_ARJDBC_WORKAROUND ? JdbcAdapter : AbstractAdapter
|
182
|
+
MYSQL2_ADAPTER_PARENT = AbstractAdapter
|
183
|
+
end
|
184
|
+
|
185
|
+
SQLITE_ADAPTER_PARENT = USE_ARJDBC_WORKAROUND ? JdbcAdapter : SQLiteAdapter
|
186
|
+
POSTGRE_ADAPTER_PARENT = USE_ARJDBC_WORKAROUND ? JdbcAdapter : AbstractAdapter
|
187
|
+
|
188
|
+
class MysqlAdapter < MYSQL_ADAPTER_PARENT
|
189
|
+
include ::DatabaseCleaner::ActiveRecord::MysqlAdapter
|
190
|
+
end
|
191
|
+
|
192
|
+
class Mysql2Adapter < MYSQL2_ADAPTER_PARENT
|
193
|
+
include ::DatabaseCleaner::ActiveRecord::MysqlAdapter
|
194
|
+
end
|
195
|
+
|
196
|
+
class IBM_DBAdapter < AbstractAdapter
|
197
|
+
include ::DatabaseCleaner::ActiveRecord::IBM_DBAdapter
|
198
|
+
end
|
199
|
+
|
200
|
+
class SQLite3Adapter < SQLITE_ADAPTER_PARENT
|
201
|
+
include ::DatabaseCleaner::ActiveRecord::SQLiteAdapter
|
202
|
+
end
|
203
|
+
|
204
|
+
class JdbcAdapter < AbstractAdapter
|
205
|
+
include ::DatabaseCleaner::ActiveRecord::TruncateOrDelete
|
206
|
+
end
|
207
|
+
|
208
|
+
class PostgreSQLAdapter < POSTGRE_ADAPTER_PARENT
|
209
|
+
include ::DatabaseCleaner::ActiveRecord::PostgreSQLAdapter
|
210
|
+
end
|
211
|
+
|
212
|
+
class SQLServerAdapter < AbstractAdapter
|
213
|
+
include ::DatabaseCleaner::ActiveRecord::TruncateOrDelete
|
214
|
+
end
|
215
|
+
|
216
|
+
class OracleEnhancedAdapter < AbstractAdapter
|
217
|
+
include ::DatabaseCleaner::ActiveRecord::OracleEnhancedAdapter
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
124
223
|
|
125
224
|
module DatabaseCleaner::ActiveRecord
|
126
225
|
class Truncation
|
@@ -128,25 +227,33 @@ module DatabaseCleaner::ActiveRecord
|
|
128
227
|
include ::DatabaseCleaner::Generic::Truncation
|
129
228
|
|
130
229
|
def clean
|
131
|
-
connection =
|
230
|
+
connection = connection_class.connection
|
132
231
|
connection.disable_referential_integrity do
|
133
|
-
connection.
|
232
|
+
if pre_count? && connection.respond_to?(:pre_count_truncate_tables)
|
233
|
+
connection.pre_count_truncate_tables(tables_to_truncate(connection), {:reset_ids => reset_ids?})
|
234
|
+
else
|
235
|
+
connection.truncate_tables(tables_to_truncate(connection))
|
236
|
+
end
|
134
237
|
end
|
135
238
|
end
|
136
239
|
|
137
240
|
private
|
138
241
|
|
139
242
|
def tables_to_truncate(connection)
|
140
|
-
|
243
|
+
(@only || connection.database_cleaner_table_cache) - @tables_to_exclude - connection.database_cleaner_view_cache
|
141
244
|
end
|
142
245
|
|
143
246
|
# overwritten
|
144
|
-
def
|
145
|
-
|
247
|
+
def migration_storage_names
|
248
|
+
%w[schema_migrations]
|
249
|
+
end
|
250
|
+
|
251
|
+
def pre_count?
|
252
|
+
@pre_count == true
|
146
253
|
end
|
147
254
|
|
255
|
+
def reset_ids?
|
256
|
+
@reset_ids != false
|
257
|
+
end
|
148
258
|
end
|
149
259
|
end
|
150
|
-
|
151
|
-
|
152
|
-
|