database_cleaner 1.3.0 → 1.4.0
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.
- 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
|