database_cleaner 1.3.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CONTRIBUTE.markdown +28 -0
- data/Gemfile.lock +182 -116
- data/History.rdoc +18 -1
- data/README.markdown +41 -4
- data/Rakefile +1 -17
- data/VERSION.yml +2 -3
- data/examples/Gemfile +17 -15
- data/examples/Gemfile.lock +182 -116
- data/examples/features/step_definitions/neo4j_steps.rb +23 -0
- data/examples/features/support/env.rb +0 -3
- data/examples/lib/mongoid_models.rb +1 -6
- data/examples/lib/neo4j_models.rb +17 -0
- data/examples/lib/sequel_models.rb +9 -0
- data/features/cleaning.feature +6 -0
- data/features/cleaning_default_strategy.feature +2 -0
- data/features/cleaning_multiple_dbs.feature +2 -0
- data/features/cleaning_multiple_orms.feature +19 -0
- data/features/step_definitions/database_cleaner_steps.rb +1 -1
- data/lib/database_cleaner/active_record/deletion.rb +30 -0
- data/lib/database_cleaner/active_record/truncation.rb +29 -1
- data/lib/database_cleaner/base.rb +4 -0
- data/lib/database_cleaner/configuration.rb +2 -0
- data/lib/database_cleaner/data_mapper/truncation.rb +5 -42
- data/lib/database_cleaner/mongo/base.rb +2 -0
- data/lib/database_cleaner/mongo/truncation.rb +40 -0
- data/lib/database_cleaner/mongoid/truncation.rb +8 -1
- data/lib/database_cleaner/moped/truncation_base.rb +1 -1
- data/lib/database_cleaner/neo4j/base.rb +58 -0
- data/lib/database_cleaner/neo4j/deletion.rb +16 -0
- data/lib/database_cleaner/neo4j/transaction.rb +35 -0
- data/lib/database_cleaner/neo4j/truncation.rb +9 -0
- data/lib/database_cleaner/sequel/base.rb +2 -2
- data/lib/database_cleaner/sequel/deletion.rb +47 -0
- data/lib/database_cleaner/sequel/transaction.rb +5 -5
- data/lib/database_cleaner/sequel/truncation.rb +41 -13
- data/spec/database_cleaner/active_record/truncation/mysql2_spec.rb +1 -3
- data/spec/database_cleaner/active_record/truncation/mysql_spec.rb +2 -4
- data/spec/database_cleaner/active_record/truncation/postgresql_spec.rb +13 -4
- data/spec/database_cleaner/active_record/truncation/sqlite3_spec.rb +0 -2
- data/spec/database_cleaner/base_spec.rb +48 -12
- data/spec/database_cleaner/configuration_spec.rb +5 -0
- data/spec/database_cleaner/data_mapper/truncation/sqlite3_spec.rb +41 -0
- data/spec/database_cleaner/moped/moped_examples.rb +6 -0
- data/spec/database_cleaner/moped/truncation_spec.rb +7 -2
- data/spec/database_cleaner/neo4j/base_spec.rb +36 -0
- data/spec/database_cleaner/neo4j/transaction_spec.rb +25 -0
- data/spec/database_cleaner/sequel/deletion_spec.rb +58 -0
- data/spec/database_cleaner/sequel/truncation_spec.rb +38 -0
- data/spec/support/active_record/mysql2_setup.rb +1 -2
- data/spec/support/active_record/mysql_setup.rb +1 -1
- data/spec/support/active_record/postgresql_setup.rb +1 -1
- data/spec/support/active_record/schema_setup.rb +8 -1
- data/spec/support/active_record/sqlite3_setup.rb +1 -1
- data/spec/support/data_mapper/schema_setup.rb +15 -0
- data/spec/support/data_mapper/sqlite3_setup.rb +39 -0
- metadata +136 -111
@@ -0,0 +1,23 @@
|
|
1
|
+
When /^I create a widget using neo4j$/ do
|
2
|
+
Neo4jWidget.create!
|
3
|
+
end
|
4
|
+
|
5
|
+
Then /^I should see ([\d]+) widget using neo4j$/ do |widget_count|
|
6
|
+
Neo4jWidget.count.should == widget_count.to_i
|
7
|
+
end
|
8
|
+
|
9
|
+
When /^I create a widget in one db using neo4j$/ do
|
10
|
+
Neo4jWidgetUsingDatabaseOne.create!
|
11
|
+
end
|
12
|
+
|
13
|
+
When /^I create a widget in another db using neo4j$/ do
|
14
|
+
Neo4jWidgetUsingDatabaseTwo.create!
|
15
|
+
end
|
16
|
+
|
17
|
+
Then /^I should see ([\d]+) widget in one db using neo4j$/ do |widget_count|
|
18
|
+
Neo4jWidgetUsingDatabaseOne.count.should == widget_count.to_i
|
19
|
+
end
|
20
|
+
|
21
|
+
Then /^I should see ([\d]+) widget in another db using neo4j$/ do |widget_count|
|
22
|
+
Neo4jWidgetUsingDatabaseTwo.count.should == widget_count.to_i
|
23
|
+
end
|
@@ -30,9 +30,6 @@ if orm && strategy
|
|
30
30
|
require "#{File.dirname(__FILE__)}/../../lib/#{another_orm.downcase}_models"
|
31
31
|
end
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
33
|
if multiple_db
|
37
34
|
DatabaseCleaner.app_root = "#{File.dirname(__FILE__)}/../.."
|
38
35
|
orm_sym = orm.gsub(/(.)([A-Z]+)/,'\1_\2').downcase.to_sym
|
@@ -1,14 +1,9 @@
|
|
1
1
|
require 'mongoid'
|
2
2
|
|
3
3
|
Mongoid.configure do |config|
|
4
|
-
|
5
|
-
config.connect_to(name)
|
4
|
+
config.master = Mongo::Connection.new.db('database_cleaner_test')
|
6
5
|
end
|
7
6
|
|
8
|
-
|
9
|
-
#::MongoMapper.connection = Mongo::Connection.new('127.0.0.1')
|
10
|
-
#::MongoMapper.database = 'database_cleaner_test'
|
11
|
-
|
12
7
|
class MongoidWidget
|
13
8
|
include Mongoid::Document
|
14
9
|
field :id, :type => Integer
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'neo4j-core'
|
2
|
+
|
3
|
+
class Neo4jWidget < Neo4j::Node
|
4
|
+
def self.create!(*args)
|
5
|
+
create(*args)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.count
|
9
|
+
Neo4j::Session.query.match('n').pluck('COUNT(n)').first
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Neo4jWidgetUsingDatabaseOne < Neo4jWidget
|
14
|
+
end
|
15
|
+
|
16
|
+
class Neo4jWidgetUsingDatabaseTwo < Neo4jWidget
|
17
|
+
end
|
data/features/cleaning.feature
CHANGED
@@ -17,8 +17,14 @@ Feature: database cleaning
|
|
17
17
|
| ActiveRecord | deletion |
|
18
18
|
| DataMapper | transaction |
|
19
19
|
| DataMapper | truncation |
|
20
|
+
| Sequel | transaction |
|
21
|
+
| Sequel | truncation |
|
22
|
+
| Sequel | deletion |
|
20
23
|
| MongoMapper | truncation |
|
21
24
|
| Mongoid | truncation |
|
22
25
|
| CouchPotato | truncation |
|
23
26
|
| Redis | truncation |
|
24
27
|
| Ohm | truncation |
|
28
|
+
| Neo4j | deletion |
|
29
|
+
| Neo4j | truncation |
|
30
|
+
| Neo4j | transaction |
|
@@ -15,6 +15,8 @@ Feature: multiple database cleaning
|
|
15
15
|
| ActiveRecord | truncation |
|
16
16
|
| ActiveRecord | deletion |
|
17
17
|
| DataMapper | truncation |
|
18
|
+
| Sequel | truncation |
|
18
19
|
| MongoMapper | truncation |
|
19
20
|
| DataMapper | transaction |
|
20
21
|
| ActiveRecord | transaction |
|
22
|
+
| Sequel | transaction |
|
@@ -12,37 +12,56 @@ Feature: database cleaning using multiple ORMs
|
|
12
12
|
Examples:
|
13
13
|
| ORM1 | ORM2 |
|
14
14
|
| ActiveRecord | DataMapper |
|
15
|
+
| ActiveRecord | Sequel |
|
15
16
|
| ActiveRecord | MongoMapper |
|
16
17
|
| ActiveRecord | Mongoid |
|
17
18
|
| ActiveRecord | CouchPotato |
|
18
19
|
| ActiveRecord | Ohm |
|
19
20
|
| ActiveRecord | Redis |
|
20
21
|
| DataMapper | ActiveRecord |
|
22
|
+
| DataMapper | Sequel |
|
21
23
|
| DataMapper | MongoMapper |
|
22
24
|
| DataMapper | Mongoid |
|
23
25
|
| DataMapper | CouchPotato |
|
24
26
|
| DataMapper | Ohm |
|
25
27
|
| DataMapper | Redis |
|
28
|
+
| Sequel | ActiveRecord |
|
29
|
+
| Sequel | DataMapper |
|
30
|
+
| Sequel | MongoMapper |
|
31
|
+
| Sequel | Mongoid |
|
32
|
+
| Sequel | CouchPotato |
|
33
|
+
| Sequel | Ohm |
|
34
|
+
| Sequel | Redis |
|
26
35
|
| MongoMapper | ActiveRecord |
|
27
36
|
| MongoMapper | DataMapper |
|
37
|
+
| MongoMapper | Sequel |
|
28
38
|
| MongoMapper | Mongoid |
|
29
39
|
| MongoMapper | CouchPotato |
|
30
40
|
| MongoMapper | Ohm |
|
31
41
|
| MongoMapper | Redis |
|
32
42
|
| CouchPotato | ActiveRecord |
|
33
43
|
| CouchPotato | DataMapper |
|
44
|
+
| CouchPotato | Sequel |
|
34
45
|
| CouchPotato | MongoMapper |
|
35
46
|
| CouchPotato | Mongoid |
|
36
47
|
| CouchPotato | Ohm |
|
37
48
|
| CouchPotato | Redis |
|
38
49
|
| Ohm | ActiveRecord |
|
39
50
|
| Ohm | DataMapper |
|
51
|
+
| Ohm | Sequel |
|
40
52
|
| Ohm | MongoMapper |
|
41
53
|
| Ohm | Mongoid |
|
42
54
|
| Ohm | CouchPotato |
|
43
55
|
| Redis | ActiveRecord |
|
44
56
|
| Redis | DataMapper |
|
57
|
+
| Redis | Sequel |
|
45
58
|
| Redis | MongoMapper |
|
46
59
|
| Redis | Mongoid |
|
47
60
|
| Redis | CouchPotato |
|
48
61
|
| Redis | Ohm |
|
62
|
+
| Neo4j | ActiveRecord |
|
63
|
+
| Neo4j | DataMapper |
|
64
|
+
| Neo4j | Sequel |
|
65
|
+
| Neo4j | Redis |
|
66
|
+
| Neo4j | Ohm |
|
67
|
+
| Neo4j | MongoMapper |
|
@@ -1,4 +1,4 @@
|
|
1
|
-
orms_pattern = /(ActiveRecord|DataMapper|MongoMapper|Mongoid|CouchPotato|Redis|Ohm)/
|
1
|
+
orms_pattern = /(ActiveRecord|DataMapper|Sequel|MongoMapper|Mongoid|CouchPotato|Redis|Ohm|Neo4j)/
|
2
2
|
|
3
3
|
Given /^I am using #{orms_pattern}$/ do |orm|
|
4
4
|
@feature_runner = FeatureRunner.new
|
@@ -43,7 +43,37 @@ module ActiveRecord
|
|
43
43
|
end
|
44
44
|
|
45
45
|
module DatabaseCleaner::ActiveRecord
|
46
|
+
module SelectiveTruncation
|
47
|
+
def tables_to_truncate(connection)
|
48
|
+
if information_schema_exists?(connection)
|
49
|
+
(@only || tables_with_new_rows(connection)) - @tables_to_exclude
|
50
|
+
else
|
51
|
+
super
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def tables_with_new_rows(connection)
|
56
|
+
@db_name ||= connection.instance_variable_get('@config')[:database]
|
57
|
+
result = connection.execute("SELECT table_name FROM information_schema.tables WHERE table_schema = '#{@db_name}' AND table_rows > 0")
|
58
|
+
result.map{ |row| row[0] } - ['schema_migrations']
|
59
|
+
end
|
60
|
+
|
61
|
+
def information_schema_exists? connection
|
62
|
+
@information_schema_exists ||=
|
63
|
+
begin
|
64
|
+
connection.execute("SELECT * FROM information_schema.tables")
|
65
|
+
true
|
66
|
+
rescue
|
67
|
+
false
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
46
72
|
class Deletion < Truncation
|
73
|
+
if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
|
74
|
+
include SelectiveTruncation
|
75
|
+
end
|
76
|
+
|
47
77
|
def clean
|
48
78
|
connection = connection_class.connection
|
49
79
|
connection.disable_referential_integrity do
|
@@ -3,7 +3,9 @@ require 'active_record/base'
|
|
3
3
|
require 'active_record/connection_adapters/abstract_adapter'
|
4
4
|
|
5
5
|
#Load available connection adapters
|
6
|
-
%w(
|
6
|
+
%w(
|
7
|
+
abstract_mysql_adapter postgresql_adapter sqlite3_adapter mysql_adapter mysql2_adapter
|
8
|
+
).each do |known_adapter|
|
7
9
|
begin
|
8
10
|
require "active_record/connection_adapters/#{known_adapter}"
|
9
11
|
rescue LoadError
|
@@ -22,6 +24,7 @@ module DatabaseCleaner
|
|
22
24
|
def database_cleaner_view_cache
|
23
25
|
@views ||= select_values("select table_name from information_schema.views where table_schema = '#{current_database}'") rescue []
|
24
26
|
end
|
27
|
+
|
25
28
|
def database_cleaner_table_cache
|
26
29
|
# the adapters don't do caching (#130) but we make the assumption that the list stays the same in tests
|
27
30
|
@database_cleaner_tables ||= tables
|
@@ -153,6 +156,15 @@ module DatabaseCleaner
|
|
153
156
|
truncate_tables(tables.select(&filter))
|
154
157
|
end
|
155
158
|
|
159
|
+
def database_cleaner_table_cache
|
160
|
+
# AR returns a list of tables without schema but then returns a
|
161
|
+
# migrations table with the schema. There are other problems, too,
|
162
|
+
# with using the base list. If a table exists in multiple schemas
|
163
|
+
# within the search path, truncation without the schema name could
|
164
|
+
# result in confusing, if not unexpected results.
|
165
|
+
@database_cleaner_tables ||= tables_with_schema
|
166
|
+
end
|
167
|
+
|
156
168
|
private
|
157
169
|
|
158
170
|
# Returns a boolean indicating if the given table has an auto-inc number higher than 0.
|
@@ -160,13 +172,28 @@ module DatabaseCleaner
|
|
160
172
|
# but then the table is cleaned. In other words, this function tells us if the given table
|
161
173
|
# was ever inserted into.
|
162
174
|
def has_been_used?(table)
|
175
|
+
return has_rows?(table) unless has_sequence?(table)
|
176
|
+
|
163
177
|
cur_val = select_value("SELECT currval('#{table}_id_seq');").to_i rescue 0
|
164
178
|
cur_val > 0
|
165
179
|
end
|
180
|
+
|
181
|
+
def has_sequence?(table)
|
182
|
+
select_value("SELECT true FROM pg_class WHERE relname = '#{table}_id_seq';")
|
183
|
+
end
|
166
184
|
|
167
185
|
def has_rows?(table)
|
168
186
|
select_value("SELECT true FROM #{table} LIMIT 1;")
|
169
187
|
end
|
188
|
+
|
189
|
+
def tables_with_schema
|
190
|
+
rows = select_rows <<-_SQL
|
191
|
+
SELECT schemaname || '.' || tablename
|
192
|
+
FROM pg_tables
|
193
|
+
WHERE tablename !~ '_prt_' AND schemaname = ANY (current_schemas(false))
|
194
|
+
_SQL
|
195
|
+
rows.collect { |result| result.first }
|
196
|
+
end
|
170
197
|
end
|
171
198
|
end
|
172
199
|
end
|
@@ -185,6 +212,7 @@ module ActiveRecord
|
|
185
212
|
end
|
186
213
|
AbstractMysqlAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::AbstractMysqlAdapter } if defined?(AbstractMysqlAdapter)
|
187
214
|
Mysql2Adapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::AbstractMysqlAdapter } if defined?(Mysql2Adapter)
|
215
|
+
MysqlAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::AbstractMysqlAdapter } if defined?(MysqlAdapter)
|
188
216
|
SQLiteAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::SQLiteAdapter } if defined?(SQLiteAdapter)
|
189
217
|
SQLite3Adapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::SQLiteAdapter } if defined?(SQLite3Adapter)
|
190
218
|
PostgreSQLAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::PostgreSQLAdapter } if defined?(PostgreSQLAdapter)
|
@@ -120,6 +120,8 @@ module DatabaseCleaner
|
|
120
120
|
:ohm
|
121
121
|
elsif defined? ::Redis
|
122
122
|
:redis
|
123
|
+
elsif defined? ::Neo4j
|
124
|
+
:neo4j
|
123
125
|
end
|
124
126
|
end
|
125
127
|
|
@@ -153,6 +155,8 @@ module DatabaseCleaner
|
|
153
155
|
self.strategy = :transaction
|
154
156
|
when :mongo_mapper, :mongoid, :couch_potato, :moped, :ohm, :redis
|
155
157
|
self.strategy = :truncation
|
158
|
+
when :neo4j
|
159
|
+
self.strategy = :transaction
|
156
160
|
end
|
157
161
|
end
|
158
162
|
end
|
@@ -12,7 +12,7 @@ module DataMapper
|
|
12
12
|
|
13
13
|
def truncate_tables(table_names)
|
14
14
|
table_names.each do |table_name|
|
15
|
-
|
15
|
+
truncate_table table_name
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
@@ -42,8 +42,7 @@ module DataMapper
|
|
42
42
|
|
43
43
|
end
|
44
44
|
|
45
|
-
|
46
|
-
class Sqlite3Adapter < DataObjectsAdapter
|
45
|
+
module SqliteAdapterMethods
|
47
46
|
|
48
47
|
# taken from http://github.com/godfat/dm-mapping/tree/master
|
49
48
|
def storage_names(repository = :default)
|
@@ -73,6 +72,7 @@ module DataMapper
|
|
73
72
|
|
74
73
|
private
|
75
74
|
|
75
|
+
# Returns a boolean indicating if the SQLite database is using the sqlite_sequence table.
|
76
76
|
def uses_sequence?
|
77
77
|
sql = <<-SQL
|
78
78
|
SELECT name FROM sqlite_master
|
@@ -80,47 +80,10 @@ module DataMapper
|
|
80
80
|
SQL
|
81
81
|
select(sql).first
|
82
82
|
end
|
83
|
-
|
84
83
|
end
|
85
84
|
|
86
|
-
class SqliteAdapter
|
87
|
-
|
88
|
-
def storage_names(repository = :default)
|
89
|
-
# activerecord-2.1.0/lib/active_record/connection_adapters/sqlite_adapter.rb: 177
|
90
|
-
sql = <<-SQL
|
91
|
-
SELECT name
|
92
|
-
FROM sqlite_master
|
93
|
-
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
|
94
|
-
SQL
|
95
|
-
# activerecord-2.1.0/lib/active_record/connection_adapters/sqlite_adapter.rb: 181
|
96
|
-
select(sql)
|
97
|
-
end
|
98
|
-
|
99
|
-
def truncate_table(table_name)
|
100
|
-
execute("DELETE FROM #{quote_name(table_name)};")
|
101
|
-
if uses_sequence?
|
102
|
-
execute("DELETE FROM sqlite_sequence where name = '#{table_name}';")
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
# this is a no-op copied from activerecord
|
107
|
-
# i didn't find out if/how this is possible
|
108
|
-
# activerecord also doesn't do more here
|
109
|
-
def disable_referential_integrity
|
110
|
-
yield
|
111
|
-
end
|
112
|
-
|
113
|
-
private
|
114
|
-
|
115
|
-
def uses_sequence?
|
116
|
-
sql = <<-SQL
|
117
|
-
SELECT name FROM sqlite_master
|
118
|
-
WHERE type='table' AND name='sqlite_sequence'
|
119
|
-
SQL
|
120
|
-
select(sql).first
|
121
|
-
end
|
122
|
-
|
123
|
-
end
|
85
|
+
class SqliteAdapter; include SqliteAdapterMethods; end
|
86
|
+
class Sqlite3Adapter; include SqliteAdapterMethods; end
|
124
87
|
|
125
88
|
# FIXME
|
126
89
|
# i don't know if this works
|
@@ -12,6 +12,46 @@ module DatabaseCleaner
|
|
12
12
|
def database
|
13
13
|
db
|
14
14
|
end
|
15
|
+
|
16
|
+
def collections_cache
|
17
|
+
@@collections_cache ||= {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def mongoid_collection_names
|
21
|
+
@@mongoid_collection_names ||= Hash.new{|h,k| h[k]=[]}.tap do |names|
|
22
|
+
ObjectSpace.each_object(Class) do |klass|
|
23
|
+
next unless klass.ancestors.include?(::Mongoid::Document)
|
24
|
+
next if klass.embedded
|
25
|
+
next if klass.collection_name.empty?
|
26
|
+
names[klass.db.name] << klass.collection_name
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def not_caching(db_name, list)
|
32
|
+
@@not_caching ||= {}
|
33
|
+
|
34
|
+
unless @@not_caching.has_key?(db_name)
|
35
|
+
@@not_caching[db_name] = true
|
36
|
+
|
37
|
+
puts "Not caching collection names for db #{db_name}. Missing these from models: #{list}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def collections
|
42
|
+
return collections_cache[database.name] if collections_cache.has_key?(database.name)
|
43
|
+
db_collections = database.collections.select { |c| c.name !~ /^system\./ }
|
44
|
+
|
45
|
+
missing_collections = mongoid_collection_names[database.name] - db_collections.map(&:name)
|
46
|
+
|
47
|
+
if missing_collections.empty?
|
48
|
+
collections_cache[database.name] = db_collections
|
49
|
+
else
|
50
|
+
not_caching(database.name, missing_collections)
|
51
|
+
end
|
52
|
+
|
53
|
+
db_collections
|
54
|
+
end
|
15
55
|
end
|
16
56
|
end
|
17
57
|
end
|