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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTE.markdown +28 -0
  3. data/Gemfile.lock +182 -116
  4. data/History.rdoc +18 -1
  5. data/README.markdown +41 -4
  6. data/Rakefile +1 -17
  7. data/VERSION.yml +2 -3
  8. data/examples/Gemfile +17 -15
  9. data/examples/Gemfile.lock +182 -116
  10. data/examples/features/step_definitions/neo4j_steps.rb +23 -0
  11. data/examples/features/support/env.rb +0 -3
  12. data/examples/lib/mongoid_models.rb +1 -6
  13. data/examples/lib/neo4j_models.rb +17 -0
  14. data/examples/lib/sequel_models.rb +9 -0
  15. data/features/cleaning.feature +6 -0
  16. data/features/cleaning_default_strategy.feature +2 -0
  17. data/features/cleaning_multiple_dbs.feature +2 -0
  18. data/features/cleaning_multiple_orms.feature +19 -0
  19. data/features/step_definitions/database_cleaner_steps.rb +1 -1
  20. data/lib/database_cleaner/active_record/deletion.rb +30 -0
  21. data/lib/database_cleaner/active_record/truncation.rb +29 -1
  22. data/lib/database_cleaner/base.rb +4 -0
  23. data/lib/database_cleaner/configuration.rb +2 -0
  24. data/lib/database_cleaner/data_mapper/truncation.rb +5 -42
  25. data/lib/database_cleaner/mongo/base.rb +2 -0
  26. data/lib/database_cleaner/mongo/truncation.rb +40 -0
  27. data/lib/database_cleaner/mongoid/truncation.rb +8 -1
  28. data/lib/database_cleaner/moped/truncation_base.rb +1 -1
  29. data/lib/database_cleaner/neo4j/base.rb +58 -0
  30. data/lib/database_cleaner/neo4j/deletion.rb +16 -0
  31. data/lib/database_cleaner/neo4j/transaction.rb +35 -0
  32. data/lib/database_cleaner/neo4j/truncation.rb +9 -0
  33. data/lib/database_cleaner/sequel/base.rb +2 -2
  34. data/lib/database_cleaner/sequel/deletion.rb +47 -0
  35. data/lib/database_cleaner/sequel/transaction.rb +5 -5
  36. data/lib/database_cleaner/sequel/truncation.rb +41 -13
  37. data/spec/database_cleaner/active_record/truncation/mysql2_spec.rb +1 -3
  38. data/spec/database_cleaner/active_record/truncation/mysql_spec.rb +2 -4
  39. data/spec/database_cleaner/active_record/truncation/postgresql_spec.rb +13 -4
  40. data/spec/database_cleaner/active_record/truncation/sqlite3_spec.rb +0 -2
  41. data/spec/database_cleaner/base_spec.rb +48 -12
  42. data/spec/database_cleaner/configuration_spec.rb +5 -0
  43. data/spec/database_cleaner/data_mapper/truncation/sqlite3_spec.rb +41 -0
  44. data/spec/database_cleaner/moped/moped_examples.rb +6 -0
  45. data/spec/database_cleaner/moped/truncation_spec.rb +7 -2
  46. data/spec/database_cleaner/neo4j/base_spec.rb +36 -0
  47. data/spec/database_cleaner/neo4j/transaction_spec.rb +25 -0
  48. data/spec/database_cleaner/sequel/deletion_spec.rb +58 -0
  49. data/spec/database_cleaner/sequel/truncation_spec.rb +38 -0
  50. data/spec/support/active_record/mysql2_setup.rb +1 -2
  51. data/spec/support/active_record/mysql_setup.rb +1 -1
  52. data/spec/support/active_record/postgresql_setup.rb +1 -1
  53. data/spec/support/active_record/schema_setup.rb +8 -1
  54. data/spec/support/active_record/sqlite3_setup.rb +1 -1
  55. data/spec/support/data_mapper/schema_setup.rb +15 -0
  56. data/spec/support/data_mapper/sqlite3_setup.rb +39 -0
  57. 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
- name = 'database_cleaner_test'
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
@@ -0,0 +1,9 @@
1
+ require 'sequel'
2
+
3
+ db = Sequel.sqlite # memory database
4
+
5
+ db.run 'DROP TABLE IF EXISTS "sequel_widgets"'
6
+
7
+ db.create_table :sequel_widgets do
8
+ String :name
9
+ end
@@ -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 |
@@ -14,8 +14,10 @@ Feature: database cleaning
14
14
  | ORM |
15
15
  | ActiveRecord |
16
16
  | DataMapper |
17
+ | Sequel |
17
18
  | MongoMapper |
18
19
  | Mongoid |
19
20
  | CouchPotato |
20
21
  | Redis |
21
22
  | Ohm |
23
+ | Neo4j |
@@ -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( abstract_mysql_adapter postgresql_adapter sqlite3_adapter ).each do |known_adapter|
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
@@ -123,6 +123,8 @@ module DatabaseCleaner
123
123
  DatabaseCleaner::Ohm
124
124
  when :redis
125
125
  DatabaseCleaner::Redis
126
+ when :neo4j
127
+ DatabaseCleaner::Neo4j
126
128
  end
127
129
  end
128
130
  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
- adapter.truncate_table table_name
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 < DataObjectsAdapter
87
- # taken from http://github.com/godfat/dm-mapping/tree/master
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
@@ -1,3 +1,5 @@
1
+ require 'mongoid'
2
+
1
3
  module DatabaseCleaner
2
4
  module Mongo
3
5
  def self.available_strategies
@@ -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