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.
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
-