jonrowe-database_cleaner 0.5.2

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 (35) hide show
  1. data/History.txt +85 -0
  2. data/LICENSE +20 -0
  3. data/README.textile +118 -0
  4. data/Rakefile +46 -0
  5. data/TODO +0 -0
  6. data/VERSION.yml +5 -0
  7. data/cucumber.yml +1 -0
  8. data/examples/features/example.feature +11 -0
  9. data/examples/features/step_definitions/example_steps.rb +8 -0
  10. data/examples/features/support/env.rb +23 -0
  11. data/examples/lib/activerecord_models.rb +12 -0
  12. data/examples/lib/couchpotato_models.rb +21 -0
  13. data/examples/lib/datamapper_models.rb +16 -0
  14. data/examples/lib/mongomapper_models.rb +17 -0
  15. data/features/cleaning.feature +20 -0
  16. data/features/step_definitions/database_cleaner_steps.rb +25 -0
  17. data/features/support/env.rb +7 -0
  18. data/lib/database_cleaner.rb +3 -0
  19. data/lib/database_cleaner/active_record/transaction.rb +30 -0
  20. data/lib/database_cleaner/active_record/truncation.rb +110 -0
  21. data/lib/database_cleaner/configuration.rb +127 -0
  22. data/lib/database_cleaner/couch_potato/truncation.rb +26 -0
  23. data/lib/database_cleaner/cucumber.rb +8 -0
  24. data/lib/database_cleaner/data_mapper/transaction.rb +23 -0
  25. data/lib/database_cleaner/data_mapper/truncation.rb +142 -0
  26. data/lib/database_cleaner/mongo_mapper/truncation.rb +30 -0
  27. data/lib/database_cleaner/truncation_base.rb +41 -0
  28. data/spec/database_cleaner/active_record/transaction_spec.rb +165 -0
  29. data/spec/database_cleaner/active_record/truncation_spec.rb +136 -0
  30. data/spec/database_cleaner/configuration_spec.rb +118 -0
  31. data/spec/database_cleaner/couch_potato/truncation_spec.rb +40 -0
  32. data/spec/database_cleaner/mongo_mapper/truncation_spec.rb +81 -0
  33. data/spec/spec.opts +6 -0
  34. data/spec/spec_helper.rb +12 -0
  35. metadata +109 -0
@@ -0,0 +1,7 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
2
+ require 'database_cleaner'
3
+
4
+ require 'spec/expectations'
5
+
6
+ require 'test/unit/assertions'
7
+
@@ -0,0 +1,3 @@
1
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__))) unless $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__)))
2
+ require 'database_cleaner/configuration'
3
+
@@ -0,0 +1,30 @@
1
+ module DatabaseCleaner::ActiveRecord
2
+ class Transaction
3
+
4
+ def start
5
+ DatabaseCleaner::ActiveRecord.connection_klasses.each do |klass|
6
+ if klass.connection.respond_to?(:increment_open_transactions)
7
+ klass.connection.increment_open_transactions
8
+ else
9
+ klass.__send__(:increment_open_transactions)
10
+ end
11
+
12
+ klass.connection.begin_db_transaction
13
+ end
14
+ end
15
+
16
+
17
+ def clean
18
+ DatabaseCleaner::ActiveRecord.connection_klasses.each do |klass|
19
+ klass.connection.rollback_db_transaction
20
+
21
+ if klass.connection.respond_to?(:decrement_open_transactions)
22
+ klass.connection.decrement_open_transactions
23
+ else
24
+ klass.__send__(:decrement_open_transactions)
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,110 @@
1
+ require 'active_record/base'
2
+ require 'active_record/connection_adapters/abstract_adapter'
3
+ require "database_cleaner/truncation_base"
4
+
5
+ module ActiveRecord
6
+ module ConnectionAdapters
7
+
8
+ class AbstractAdapter
9
+ end
10
+
11
+ class SQLiteAdapter < AbstractAdapter
12
+ end
13
+
14
+ class MysqlAdapter < AbstractAdapter
15
+ def truncate_table(table_name)
16
+ execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
17
+ end
18
+ end
19
+
20
+ class SQLite3Adapter < SQLiteAdapter
21
+ def truncate_table(table_name)
22
+ execute("DELETE FROM #{quote_table_name(table_name)};")
23
+ end
24
+ end
25
+
26
+ class JdbcAdapter < AbstractAdapter
27
+ def truncate_table(table_name)
28
+ begin
29
+ execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
30
+ rescue ActiveRecord::StatementInvalid
31
+ execute("DELETE FROM #{quote_table_name(table_name)};")
32
+ end
33
+ end
34
+ end
35
+
36
+ class PostgreSQLAdapter < AbstractAdapter
37
+
38
+ def self.db_version
39
+ @db_version ||= ActiveRecord::Base.connection.select_values(
40
+ "SELECT CHARACTER_VALUE
41
+ FROM INFORMATION_SCHEMA.SQL_IMPLEMENTATION_INFO
42
+ WHERE IMPLEMENTATION_INFO_NAME = 'DBMS VERSION' ").to_s
43
+ end
44
+
45
+ def self.cascade
46
+ @cascade ||= db_version >= "08.02" ? "CASCADE" : ""
47
+ end
48
+
49
+ def truncate_table(table_name)
50
+ execute("TRUNCATE TABLE #{quote_table_name(table_name)} #{self.class.cascade};")
51
+ end
52
+
53
+ end
54
+
55
+ class SQLServerAdapter < AbstractAdapter
56
+ def truncate_table(table_name)
57
+ execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
58
+ end
59
+ end
60
+
61
+ class OracleEnhancedAdapter < AbstractAdapter
62
+ def truncate_table(table_name)
63
+ execute("TRUNCATE TABLE #{quote_table_name(table_name)}")
64
+ end
65
+ end
66
+
67
+ end
68
+ end
69
+
70
+
71
+ module DatabaseCleaner::ActiveRecord
72
+ class Truncation < ::DatabaseCleaner::TruncationBase
73
+
74
+ def clean
75
+ connections.each do |connection|
76
+ connection.disable_referential_integrity do
77
+ tables_to_truncate_for_connection(connection).each do |table_name|
78
+ connection.truncate_table table_name
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def tables_to_truncate_for_connection(connection)
87
+ (@only || connection.tables) - @tables_to_exclude
88
+ end
89
+
90
+ # def tables_to_truncate
91
+ # (@only || connection.tables) - @tables_to_exclude
92
+ # end
93
+
94
+ # def connection
95
+ # ::ActiveRecord::Base.connection
96
+ # end
97
+
98
+ def connections
99
+ DatabaseCleaner::ActiveRecord.connection_klasses.map{ |klass| klass.connection }
100
+ end
101
+
102
+ # overwritten
103
+ def migration_storage_name
104
+ 'schema_migrations'
105
+ end
106
+
107
+ end
108
+ end
109
+
110
+
@@ -0,0 +1,127 @@
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
+
12
+ def self.connection_klasses
13
+ @klasses || [::ActiveRecord::Base]
14
+ end
15
+
16
+ def self.connection_klasses=(other)
17
+ other.concat [::ActiveRecord::Base] unless other.include? ::ActiveRecord::Base
18
+ @klasses = other
19
+ end
20
+ end
21
+
22
+ module DataMapper
23
+ def self.available_strategies
24
+ %w[truncation transaction]
25
+ end
26
+ end
27
+
28
+ module MongoMapper
29
+ def self.available_strategies
30
+ %w[truncation]
31
+ end
32
+ end
33
+
34
+ module CouchPotato
35
+ def self.available_strategies
36
+ %w[truncation]
37
+ end
38
+ end
39
+
40
+ class << self
41
+
42
+ def create_strategy(*args)
43
+ strategy, *strategy_args = args
44
+ orm_strategy(strategy).new(*strategy_args)
45
+ end
46
+
47
+ def clean_with(*args)
48
+ strategy = create_strategy(*args)
49
+ strategy.clean
50
+ strategy
51
+ end
52
+
53
+ alias clean_with! clean_with
54
+
55
+ def strategy=(args)
56
+ strategy, *strategy_args = args
57
+ if strategy.is_a?(Symbol)
58
+ @strategy = create_strategy(*args)
59
+ elsif strategy_args.empty?
60
+ @strategy = strategy
61
+ else
62
+ raise ArgumentError, "You must provide a strategy object, or a symbol for a know strategy along with initialization params."
63
+ end
64
+ end
65
+
66
+ def orm=(orm_string)
67
+ @orm = orm_string
68
+ end
69
+
70
+ def start
71
+ strategy.start
72
+ end
73
+
74
+ def clean
75
+ strategy.clean
76
+ end
77
+
78
+ alias clean! clean
79
+
80
+ private
81
+
82
+ def strategy
83
+ return @strategy if @strategy
84
+ raise NoStrategySetError, "Please set a strategy with DatabaseCleaner.strategy=."
85
+ end
86
+
87
+ def orm_strategy(strategy)
88
+ require "database_cleaner/#{orm}/#{strategy}"
89
+ orm_module.const_get(strategy.to_s.capitalize)
90
+ rescue LoadError => e
91
+ raise UnknownStrategySpecified, "The '#{strategy}' strategy does not exist for the #{orm} ORM! Available strategies: #{orm_module.available_strategies.join(', ')}"
92
+ end
93
+
94
+
95
+ def orm
96
+ @orm ||=begin
97
+ if defined? ::ActiveRecord
98
+ 'active_record'
99
+ elsif defined? ::DataMapper
100
+ 'data_mapper'
101
+ elsif defined? ::MongoMapper
102
+ 'mongo_mapper'
103
+ elsif defined? ::CouchPotato
104
+ 'couch_potato'
105
+ else
106
+ raise NoORMDetected, "No known ORM was detected! Is ActiveRecord, DataMapper, MongoMapper or CouchPotato loaded?"
107
+ end
108
+ end
109
+ end
110
+
111
+
112
+ def orm_module
113
+ case orm
114
+ when 'active_record'
115
+ DatabaseCleaner::ActiveRecord
116
+ when 'data_mapper'
117
+ DatabaseCleaner::DataMapper
118
+ when 'mongo_mapper'
119
+ DatabaseCleaner::MongoMapper
120
+ when 'couch_potato'
121
+ DatabaseCleaner::CouchPotato
122
+ end
123
+ end
124
+
125
+ end
126
+
127
+ 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,8 @@
1
+
2
+ Before do
3
+ DatabaseCleaner.start
4
+ end
5
+
6
+ After do
7
+ DatabaseCleaner.clean
8
+ 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