database_cleaner 0.8.0 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
|