jonrowe-database_cleaner 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
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