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.
Files changed (57) hide show
  1. data/Gemfile.lock +150 -82
  2. data/History.txt +19 -1
  3. data/README.textile +61 -59
  4. data/Rakefile +7 -17
  5. data/VERSION.yml +3 -3
  6. data/examples/Gemfile +14 -20
  7. data/examples/Gemfile.lock +150 -82
  8. data/examples/features/step_definitions/translation_steps.rb +14 -14
  9. data/examples/features/support/env.rb +6 -2
  10. data/features/cleaning.feature +1 -1
  11. data/features/cleaning_default_strategy.feature +1 -1
  12. data/features/cleaning_multiple_dbs.feature +1 -2
  13. data/features/cleaning_multiple_orms.feature +7 -7
  14. data/features/support/env.rb +1 -1
  15. data/lib/database_cleaner/active_record/base.rb +26 -6
  16. data/lib/database_cleaner/active_record/deletion.rb +2 -2
  17. data/lib/database_cleaner/active_record/transaction.rb +9 -9
  18. data/lib/database_cleaner/active_record/truncation.rb +157 -50
  19. data/lib/database_cleaner/base.rb +2 -2
  20. data/lib/database_cleaner/configuration.rb +26 -3
  21. data/lib/database_cleaner/data_mapper/truncation.rb +2 -2
  22. data/lib/database_cleaner/generic/truncation.rb +7 -5
  23. data/lib/database_cleaner/mongo/base.rb +16 -0
  24. data/lib/database_cleaner/mongo/truncation.rb +9 -14
  25. data/lib/database_cleaner/mongo/truncation_mixin.rb +22 -0
  26. data/lib/database_cleaner/mongo_mapper/truncation.rb +2 -2
  27. data/lib/database_cleaner/mongoid/truncation.rb +2 -2
  28. data/lib/database_cleaner/moped/truncation.rb +6 -2
  29. data/lib/database_cleaner/sequel/truncation.rb +6 -6
  30. data/spec/database_cleaner/active_record/base_spec.rb +27 -15
  31. data/spec/database_cleaner/active_record/truncation/mysql2_spec.rb +40 -0
  32. data/spec/database_cleaner/active_record/truncation/mysql_spec.rb +40 -0
  33. data/spec/database_cleaner/active_record/truncation/postgresql_spec.rb +48 -0
  34. data/spec/database_cleaner/active_record/truncation/shared_fast_truncation.rb +40 -0
  35. data/spec/database_cleaner/active_record/truncation_spec.rb +102 -33
  36. data/spec/database_cleaner/base_spec.rb +14 -14
  37. data/spec/database_cleaner/configuration_spec.rb +15 -10
  38. data/spec/database_cleaner/data_mapper/base_spec.rb +1 -1
  39. data/spec/database_cleaner/data_mapper/transaction_spec.rb +1 -1
  40. data/spec/database_cleaner/data_mapper/truncation_spec.rb +1 -1
  41. data/spec/database_cleaner/generic/base_spec.rb +1 -1
  42. data/spec/database_cleaner/generic/truncation_spec.rb +35 -5
  43. data/spec/database_cleaner/mongo/mongo_examples.rb +26 -0
  44. data/spec/database_cleaner/mongo/truncation_spec.rb +72 -0
  45. data/spec/database_cleaner/mongo_mapper/base_spec.rb +1 -1
  46. data/spec/database_cleaner/sequel/base_spec.rb +1 -1
  47. data/spec/database_cleaner/sequel/transaction_spec.rb +1 -1
  48. data/spec/database_cleaner/sequel/truncation_spec.rb +1 -1
  49. data/spec/database_cleaner/{shared_strategy_spec.rb → shared_strategy.rb} +0 -0
  50. data/spec/spec_helper.rb +6 -4
  51. data/spec/support/active_record/database_setup.rb +4 -0
  52. data/spec/support/active_record/mysql2_setup.rb +38 -0
  53. data/spec/support/active_record/mysql_setup.rb +38 -0
  54. data/spec/support/active_record/postgresql_setup.rb +41 -0
  55. data/spec/support/active_record/schema_setup.rb +10 -0
  56. metadata +313 -46
  57. data/spec/spec.opts +0 -7
@@ -5,7 +5,7 @@ require 'rubygems'
5
5
  require 'bundler'
6
6
 
7
7
  Bundler.setup
8
- require 'spec/expectations'
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
- if orm_sym == :mongo_mapper
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
@@ -19,4 +19,4 @@ Feature: database cleaning
19
19
  | DataMapper | truncation |
20
20
  | MongoMapper | truncation |
21
21
  | Mongoid | truncation |
22
- # | CouchPotato | truncation |
22
+ | CouchPotato | truncation |
@@ -16,4 +16,4 @@ Feature: database cleaning
16
16
  | DataMapper |
17
17
  | MongoMapper |
18
18
  | Mongoid |
19
- # | CouchPotato |
19
+ | CouchPotato |
@@ -17,5 +17,4 @@ Feature: multiple database cleaning
17
17
  | DataMapper | truncation |
18
18
  | MongoMapper | truncation |
19
19
  | DataMapper | transaction |
20
- # Not working...
21
- #| ActiveRecord | transaction |
20
+ | ActiveRecord | transaction |
@@ -14,16 +14,16 @@ Feature: database cleaning using multiple ORMs
14
14
  | ActiveRecord | DataMapper |
15
15
  | ActiveRecord | MongoMapper |
16
16
  | ActiveRecord | Mongoid |
17
- #| ActiveRecord | CouchPotato |
17
+ | ActiveRecord | CouchPotato |
18
18
  | DataMapper | ActiveRecord |
19
19
  | DataMapper | MongoMapper |
20
20
  | DataMapper | Mongoid |
21
- #| DataMapper | CouchPotato |
21
+ | DataMapper | CouchPotato |
22
22
  | MongoMapper | ActiveRecord |
23
23
  | MongoMapper | DataMapper |
24
24
  | MongoMapper | Mongoid |
25
- #| MongoMapper | CouchPotato |
26
- # | CouchPotato | ActiveRecord |
27
- #| CouchPotato | DataMapper |
28
- #| CouchPotato | MongoMapper |
29
- #| CouchPotato | Mongoid |
25
+ | MongoMapper | CouchPotato |
26
+ | CouchPotato | ActiveRecord |
27
+ | CouchPotato | DataMapper |
28
+ | CouchPotato | MongoMapper |
29
+ | CouchPotato | Mongoid |
@@ -1,7 +1,7 @@
1
1
  $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
2
2
  require 'database_cleaner'
3
3
 
4
- require 'spec/expectations'
4
+ require 'rspec/expectations'
5
5
 
6
6
  require 'test/unit/assertions'
7
7
 
@@ -38,16 +38,36 @@ module DatabaseCleaner
38
38
  end
39
39
  end
40
40
 
41
- def create_connection_klass
41
+ def create_connection_class
42
42
  Class.new(::ActiveRecord::Base)
43
43
  end
44
44
 
45
- def connection_klass
46
- return ::ActiveRecord::Base unless connection_hash
47
- klass = create_connection_klass
48
- klass.send :establish_connection, connection_hash
49
- klass
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 < AbstractAdapter
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 = connection_klass.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 connection_klass.connection.respond_to?(:increment_open_transactions)
11
- connection_klass.connection.increment_open_transactions
10
+ if connection_class.connection.respond_to?(:increment_open_transactions)
11
+ connection_class.connection.increment_open_transactions
12
12
  else
13
- connection_klass.__send__(:increment_open_transactions)
13
+ connection_class.__send__(:increment_open_transactions)
14
14
  end
15
- connection_klass.connection.begin_db_transaction
15
+ connection_class.connection.begin_db_transaction
16
16
  end
17
17
 
18
18
 
19
19
  def clean
20
- return unless connection_klass.connection.open_transactions > 0
20
+ return unless connection_class.connection.open_transactions > 0
21
21
 
22
- connection_klass.connection.rollback_db_transaction
22
+ connection_class.connection.rollback_db_transaction
23
23
 
24
- if connection_klass.connection.respond_to?(:decrement_open_transactions)
25
- connection_klass.connection.decrement_open_transactions
24
+ if connection_class.connection.respond_to?(:decrement_open_transactions)
25
+ connection_class.connection.decrement_open_transactions
26
26
  else
27
- connection_klass.__send__(:decrement_open_transactions)
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 ActiveRecord
7
- module ConnectionAdapters
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
- class AbstractAdapter
12
- def views
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
- unless USE_ARJDBC_WORKAROUND
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
- class Mysql2Adapter < MYSQL2_ADAPTER_PARENT
51
- def truncate_table(table_name)
52
- execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
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
- class IBM_DBAdapter < AbstractAdapter
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
- class SQLite3Adapter < SQLITE_ADAPTER_PARENT
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
- class JdbcAdapter < AbstractAdapter
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
- class PostgreSQLAdapter < POSTGRE_ADAPTER_PARENT
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
- end
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
- class SQLServerAdapter < AbstractAdapter
106
- def truncate_table(table_name)
107
- begin
108
- execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
109
- rescue ActiveRecord::StatementInvalid
110
- execute("DELETE FROM #{quote_table_name(table_name)};")
111
- end
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
- class OracleEnhancedAdapter < AbstractAdapter
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 = connection_klass.connection
230
+ connection = connection_class.connection
132
231
  connection.disable_referential_integrity do
133
- connection.truncate_tables(tables_to_truncate(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
- (@only || connection.tables) - @tables_to_exclude - connection.views
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 migration_storage_name
145
- 'schema_migrations'
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
-