saimonmoore-database_cleaner 0.5.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.
- data/History.txt +81 -0
- data/LICENSE +20 -0
- data/README.textile +127 -0
- data/Rakefile +46 -0
- data/TODO +0 -0
- data/VERSION.yml +5 -0
- data/cucumber.yml +1 -0
- data/examples/features/example.feature +11 -0
- data/examples/features/step_definitions/example_steps.rb +8 -0
- data/examples/features/support/env.rb +23 -0
- data/examples/lib/activerecord_models.rb +12 -0
- data/examples/lib/couchpotato_models.rb +21 -0
- data/examples/lib/couchrest_models.rb +23 -0
- data/examples/lib/datamapper_models.rb +16 -0
- data/examples/lib/mongomapper_models.rb +17 -0
- data/features/cleaning.feature +21 -0
- data/features/step_definitions/database_cleaner_steps.rb +25 -0
- data/features/support/env.rb +7 -0
- data/lib/database_cleaner.rb +3 -0
- data/lib/database_cleaner/active_record/transaction.rb +26 -0
- data/lib/database_cleaner/active_record/truncation.rb +79 -0
- data/lib/database_cleaner/configuration.rb +128 -0
- data/lib/database_cleaner/couch_potato/truncation.rb +26 -0
- data/lib/database_cleaner/couchrest/compatibility.rb +12 -0
- data/lib/database_cleaner/couchrest/truncation.rb +28 -0
- data/lib/database_cleaner/cucumber.rb +8 -0
- data/lib/database_cleaner/data_mapper/transaction.rb +23 -0
- data/lib/database_cleaner/data_mapper/truncation.rb +142 -0
- data/lib/database_cleaner/mongo_mapper/truncation.rb +30 -0
- data/lib/database_cleaner/truncation_base.rb +41 -0
- data/spec/database_cleaner/active_record/truncation_spec.rb +66 -0
- data/spec/database_cleaner/configuration_spec.rb +104 -0
- data/spec/database_cleaner/couch_potato/truncation_spec.rb +40 -0
- data/spec/database_cleaner/couchrest/truncation_spec.rb +56 -0
- data/spec/database_cleaner/mongo_mapper/truncation_spec.rb +81 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +12 -0
- metadata +104 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
Given /^I am using (ActiveRecord|DataMapper|MongoMapper|CouchPotato)$/ do |orm|
|
2
|
+
@orm = orm
|
3
|
+
end
|
4
|
+
|
5
|
+
Given /^the (.+) cleaning strategy$/ do |strategy|
|
6
|
+
@strategy = strategy
|
7
|
+
end
|
8
|
+
|
9
|
+
When "I run my scenarios that rely on a clean database" do
|
10
|
+
full_dir ||= File.expand_path(File.dirname(__FILE__) + "/../../examples/")
|
11
|
+
Dir.chdir(full_dir) do
|
12
|
+
ENV['ORM'] = @orm.downcase
|
13
|
+
ENV['STRATEGY'] = @strategy
|
14
|
+
@out = `#{"jruby -S " if defined?(JRUBY_VERSION)}cucumber features`
|
15
|
+
@status = $?.exitstatus
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Then "I should see all green" do
|
20
|
+
unless @status == 0
|
21
|
+
raise "Expected to see all green but we saw FAIL! Output:\n#{@out}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module DatabaseCleaner::ActiveRecord
|
2
|
+
class Transaction
|
3
|
+
|
4
|
+
def start
|
5
|
+
if ActiveRecord::Base.connection.respond_to?(:increment_open_transactions)
|
6
|
+
ActiveRecord::Base.connection.increment_open_transactions
|
7
|
+
else
|
8
|
+
ActiveRecord::Base.__send__(:increment_open_transactions)
|
9
|
+
end
|
10
|
+
|
11
|
+
ActiveRecord::Base.connection.begin_db_transaction
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def clean
|
16
|
+
ActiveRecord::Base.connection.rollback_db_transaction
|
17
|
+
|
18
|
+
if ActiveRecord::Base.connection.respond_to?(:decrement_open_transactions)
|
19
|
+
ActiveRecord::Base.connection.decrement_open_transactions
|
20
|
+
else
|
21
|
+
ActiveRecord::Base.__send__(:decrement_open_transactions)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require "database_cleaner/truncation_base"
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
|
6
|
+
class MysqlAdapter
|
7
|
+
def truncate_table(table_name)
|
8
|
+
execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class SQLite3Adapter
|
13
|
+
def truncate_table(table_name)
|
14
|
+
execute("DELETE FROM #{quote_table_name(table_name)};")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class JdbcAdapter
|
19
|
+
def truncate_table(table_name)
|
20
|
+
begin
|
21
|
+
execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
|
22
|
+
rescue ActiveRecord::StatementInvalid
|
23
|
+
execute("DELETE FROM #{quote_table_name(table_name)};")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class PostgreSQLAdapter
|
29
|
+
def truncate_table(table_name)
|
30
|
+
execute("TRUNCATE TABLE #{quote_table_name(table_name)} CASCADE;")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class SQLServerAdapter
|
35
|
+
def truncate_table(table_name)
|
36
|
+
execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class OracleEnhancedAdapter
|
41
|
+
def truncate_table(table_name)
|
42
|
+
execute("TRUNCATE TABLE #{quote_table_name(table_name)}")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
module DatabaseCleaner::ActiveRecord
|
51
|
+
class Truncation < ::DatabaseCleaner::TruncationBase
|
52
|
+
|
53
|
+
def clean
|
54
|
+
connection.disable_referential_integrity do
|
55
|
+
tables_to_truncate.each do |table_name|
|
56
|
+
connection.truncate_table table_name
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def tables_to_truncate
|
64
|
+
(@only || connection.tables) - @tables_to_exclude
|
65
|
+
end
|
66
|
+
|
67
|
+
def connection
|
68
|
+
::ActiveRecord::Base.connection
|
69
|
+
end
|
70
|
+
|
71
|
+
# overwritten
|
72
|
+
def migration_storage_name
|
73
|
+
'schema_migrations'
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module DatabaseCleaner
|
2
|
+
|
3
|
+
class NoStrategySetError < StandardError; end
|
4
|
+
class NoORMDetected < StandardError; end
|
5
|
+
class UnknownStrategySpecified < ArgumentError; end
|
6
|
+
|
7
|
+
module ActiveRecord
|
8
|
+
def self.available_strategies
|
9
|
+
%w[truncation transaction]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module DataMapper
|
14
|
+
def self.available_strategies
|
15
|
+
%w[truncation transaction]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module MongoMapper
|
20
|
+
def self.available_strategies
|
21
|
+
%w[truncation]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module CouchPotato
|
26
|
+
def self.available_strategies
|
27
|
+
%w[truncation]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module CouchRest
|
32
|
+
def self.available_strategies
|
33
|
+
%w[truncation]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class << self
|
38
|
+
|
39
|
+
def create_strategy(*args)
|
40
|
+
strategy, *strategy_args = args
|
41
|
+
orm_strategy(strategy).new(*strategy_args)
|
42
|
+
end
|
43
|
+
|
44
|
+
def clean_with(*args)
|
45
|
+
strategy = create_strategy(*args)
|
46
|
+
strategy.clean
|
47
|
+
strategy
|
48
|
+
end
|
49
|
+
|
50
|
+
alias clean_with! clean_with
|
51
|
+
|
52
|
+
def strategy=(args)
|
53
|
+
strategy, *strategy_args = args
|
54
|
+
if strategy.is_a?(Symbol)
|
55
|
+
@strategy = create_strategy(*args)
|
56
|
+
elsif strategy_args.empty?
|
57
|
+
@strategy = strategy
|
58
|
+
else
|
59
|
+
raise ArgumentError, "You must provide a strategy object, or a symbol for a know strategy along with initialization params."
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def orm=(orm_string)
|
64
|
+
@orm = orm_string
|
65
|
+
end
|
66
|
+
|
67
|
+
def start
|
68
|
+
strategy.start
|
69
|
+
end
|
70
|
+
|
71
|
+
def clean
|
72
|
+
strategy.clean
|
73
|
+
end
|
74
|
+
|
75
|
+
alias clean! clean
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def strategy
|
80
|
+
return @strategy if @strategy
|
81
|
+
raise NoStrategySetError, "Please set a strategy with DatabaseCleaner.strategy=."
|
82
|
+
end
|
83
|
+
|
84
|
+
def orm_strategy(strategy)
|
85
|
+
require "database_cleaner/#{orm}/#{strategy}"
|
86
|
+
orm_module.const_get(strategy.to_s.capitalize)
|
87
|
+
rescue LoadError => e
|
88
|
+
raise UnknownStrategySpecified, "The '#{strategy}' strategy does not exist for the #{orm} ORM! Available strategies: #{orm_module.available_strategies.join(', ')}"
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
def orm
|
93
|
+
@orm ||=begin
|
94
|
+
if defined? ::ActiveRecord
|
95
|
+
'active_record'
|
96
|
+
elsif defined? ::DataMapper
|
97
|
+
'data_mapper'
|
98
|
+
elsif defined? ::MongoMapper
|
99
|
+
'mongo_mapper'
|
100
|
+
elsif defined? ::CouchPotato
|
101
|
+
'couch_potato'
|
102
|
+
elsif defined? ::CouchRest
|
103
|
+
'couchrest'
|
104
|
+
else
|
105
|
+
raise NoORMDetected, "No known ORM was detected! Is ActiveRecord, DataMapper, MongoMapper or CouchPotato, CouchRest loaded?"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
def orm_module
|
112
|
+
case orm
|
113
|
+
when 'active_record'
|
114
|
+
DatabaseCleaner::ActiveRecord
|
115
|
+
when 'data_mapper'
|
116
|
+
DatabaseCleaner::DataMapper
|
117
|
+
when 'mongo_mapper'
|
118
|
+
DatabaseCleaner::MongoMapper
|
119
|
+
when 'couch_potato'
|
120
|
+
DatabaseCleaner::CouchPotato
|
121
|
+
when 'couchrest'
|
122
|
+
DatabaseCleaner::CouchRest
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'database_cleaner/truncation_base'
|
2
|
+
|
3
|
+
module DatabaseCleaner
|
4
|
+
module CouchPotato
|
5
|
+
class Truncation < DatabaseCleaner::TruncationBase
|
6
|
+
def initialize(options = {})
|
7
|
+
if options.has_key?(:only) || options.has_key?(:except)
|
8
|
+
raise ArgumentError, "The :only and :except options are not available for use with CouchPotato/CouchDB."
|
9
|
+
elsif !options.empty?
|
10
|
+
raise ArgumentError, "Unsupported option. You specified #{options.keys.join(',')}."
|
11
|
+
end
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def clean
|
16
|
+
database.recreate!
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def database
|
22
|
+
::CouchPotato.couchrest_database
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'database_cleaner/truncation_base'
|
2
|
+
require 'database_cleaner/couchrest/compatibility'
|
3
|
+
|
4
|
+
module DatabaseCleaner
|
5
|
+
module CouchRest
|
6
|
+
class Truncation < DatabaseCleaner::TruncationBase
|
7
|
+
def initialize(options = {})
|
8
|
+
if options.has_key?(:only) || options.has_key?(:except)
|
9
|
+
raise ArgumentError, "The :only and :except options are not available for use with CouchRest/CouchDB."
|
10
|
+
elsif !options.empty?
|
11
|
+
raise ArgumentError, "Unsupported option. You specified #{options.keys.join(',')}."
|
12
|
+
end
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def clean
|
17
|
+
database.recreate!
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def database
|
23
|
+
raise ArgumentError, "Missing database name (or url). Set CouchRest.default_database_name= 'mydbname'." unless ::CouchRest::default_database_name
|
24
|
+
::CouchRest.database!(::CouchRest::default_database_name)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module DatabaseCleaner::DataMapper
|
2
|
+
class Transaction
|
3
|
+
|
4
|
+
def start(repo = :default)
|
5
|
+
DataMapper.repository(repo) do |r|
|
6
|
+
transaction = DataMapper::Transaction.new(r)
|
7
|
+
transaction.begin
|
8
|
+
r.adapter.push_transaction(transaction)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def clean(repo = :default)
|
13
|
+
DataMapper.repository(repo) do |r|
|
14
|
+
adapter = r.adapter
|
15
|
+
while adapter.current_transaction
|
16
|
+
adapter.current_transaction.rollback
|
17
|
+
adapter.pop_transaction
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require "database_cleaner/truncation_base"
|
2
|
+
|
3
|
+
module DataMapper
|
4
|
+
module Adapters
|
5
|
+
|
6
|
+
class DataObjectsAdapter
|
7
|
+
|
8
|
+
def storage_names(repository = :default)
|
9
|
+
raise NotImplementedError
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
class MysqlAdapter < DataObjectsAdapter
|
15
|
+
|
16
|
+
# taken from http://github.com/godfat/dm-mapping/tree/master
|
17
|
+
def storage_names(repository = :default)
|
18
|
+
select 'SHOW TABLES'
|
19
|
+
end
|
20
|
+
|
21
|
+
def truncate_table(table_name)
|
22
|
+
execute("TRUNCATE TABLE #{quote_name(table_name)};")
|
23
|
+
end
|
24
|
+
|
25
|
+
# copied from activerecord
|
26
|
+
def disable_referential_integrity
|
27
|
+
old = select("SELECT @@FOREIGN_KEY_CHECKS;")
|
28
|
+
begin
|
29
|
+
execute("SET FOREIGN_KEY_CHECKS = 0;")
|
30
|
+
yield
|
31
|
+
ensure
|
32
|
+
execute("SET FOREIGN_KEY_CHECKS = #{old};")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
class Sqlite3Adapter < DataObjectsAdapter
|
39
|
+
|
40
|
+
# taken from http://github.com/godfat/dm-mapping/tree/master
|
41
|
+
def storage_names(repository = :default)
|
42
|
+
# activerecord-2.1.0/lib/active_record/connection_adapters/sqlite_adapter.rb: 177
|
43
|
+
sql = <<-SQL.compress_lines
|
44
|
+
SELECT name
|
45
|
+
FROM sqlite_master
|
46
|
+
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
|
47
|
+
SQL
|
48
|
+
# activerecord-2.1.0/lib/active_record/connection_adapters/sqlite_adapter.rb: 181
|
49
|
+
select sql
|
50
|
+
end
|
51
|
+
|
52
|
+
def truncate_table(table_name)
|
53
|
+
execute("DELETE FROM #{quote_name(table_name)};")
|
54
|
+
end
|
55
|
+
|
56
|
+
# this is a no-op copied from activerecord
|
57
|
+
# i didn't find out if/how this is possible
|
58
|
+
# activerecord also doesn't do more here
|
59
|
+
def disable_referential_integrity
|
60
|
+
yield
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
# FIXME
|
67
|
+
# i don't know if this works
|
68
|
+
# i basically just copied activerecord code to get a rough idea what they do.
|
69
|
+
# i don't have postgres available, so i won't be the one to write this.
|
70
|
+
# maybe codes below gets some postgres/datamapper user going, though.
|
71
|
+
class PostgresAdapter < DataObjectsAdapter
|
72
|
+
|
73
|
+
# taken from http://github.com/godfat/dm-mapping/tree/master
|
74
|
+
def storage_names(repository = :default)
|
75
|
+
sql = <<-SQL.compress_lines
|
76
|
+
SELECT table_name FROM "information_schema"."tables"
|
77
|
+
WHERE table_schema = current_schema()
|
78
|
+
SQL
|
79
|
+
select(sql)
|
80
|
+
end
|
81
|
+
|
82
|
+
def truncate_table(table_name)
|
83
|
+
execute("TRUNCATE TABLE #{quote_name(table_name)} CASCADE;")
|
84
|
+
end
|
85
|
+
|
86
|
+
# FIXME
|
87
|
+
# copied from activerecord
|
88
|
+
def supports_disable_referential_integrity?
|
89
|
+
version = select("SHOW server_version")[0][0].split('.')
|
90
|
+
(version[0].to_i >= 8 && version[1].to_i >= 1) ? true : false
|
91
|
+
rescue
|
92
|
+
return false
|
93
|
+
end
|
94
|
+
|
95
|
+
# FIXME
|
96
|
+
# copied unchanged from activerecord
|
97
|
+
def disable_referential_integrity(repository = :default)
|
98
|
+
if supports_disable_referential_integrity? then
|
99
|
+
execute(storage_names(repository).collect do |name|
|
100
|
+
"ALTER TABLE #{quote_name(name)} DISABLE TRIGGER ALL"
|
101
|
+
end.join(";"))
|
102
|
+
end
|
103
|
+
yield
|
104
|
+
ensure
|
105
|
+
if supports_disable_referential_integrity? then
|
106
|
+
execute(storage_names(repository).collect do |name|
|
107
|
+
"ALTER TABLE #{quote_name(name)} ENABLE TRIGGER ALL"
|
108
|
+
end.join(";"))
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
module DatabaseCleaner::DataMapper
|
119
|
+
class Truncation < ::DatabaseCleaner::TruncationBase
|
120
|
+
|
121
|
+
def clean(repository = :default)
|
122
|
+
adapter = DataMapper.repository(repository).adapter
|
123
|
+
adapter.disable_referential_integrity do
|
124
|
+
tables_to_truncate.each do |table_name|
|
125
|
+
adapter.truncate_table table_name
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def tables_to_truncate(repository = :default)
|
133
|
+
(@only || DataMapper.repository(repository).adapter.storage_names(repository)) - @tables_to_exclude
|
134
|
+
end
|
135
|
+
|
136
|
+
# overwritten
|
137
|
+
def migration_storage_name
|
138
|
+
'migration_info'
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|