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