saimonmoore-database_cleaner 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/History.txt +81 -0
  2. data/LICENSE +20 -0
  3. data/README.textile +127 -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/couchrest_models.rb +23 -0
  14. data/examples/lib/datamapper_models.rb +16 -0
  15. data/examples/lib/mongomapper_models.rb +17 -0
  16. data/features/cleaning.feature +21 -0
  17. data/features/step_definitions/database_cleaner_steps.rb +25 -0
  18. data/features/support/env.rb +7 -0
  19. data/lib/database_cleaner.rb +3 -0
  20. data/lib/database_cleaner/active_record/transaction.rb +26 -0
  21. data/lib/database_cleaner/active_record/truncation.rb +79 -0
  22. data/lib/database_cleaner/configuration.rb +128 -0
  23. data/lib/database_cleaner/couch_potato/truncation.rb +26 -0
  24. data/lib/database_cleaner/couchrest/compatibility.rb +12 -0
  25. data/lib/database_cleaner/couchrest/truncation.rb +28 -0
  26. data/lib/database_cleaner/cucumber.rb +8 -0
  27. data/lib/database_cleaner/data_mapper/transaction.rb +23 -0
  28. data/lib/database_cleaner/data_mapper/truncation.rb +142 -0
  29. data/lib/database_cleaner/mongo_mapper/truncation.rb +30 -0
  30. data/lib/database_cleaner/truncation_base.rb +41 -0
  31. data/spec/database_cleaner/active_record/truncation_spec.rb +66 -0
  32. data/spec/database_cleaner/configuration_spec.rb +104 -0
  33. data/spec/database_cleaner/couch_potato/truncation_spec.rb +40 -0
  34. data/spec/database_cleaner/couchrest/truncation_spec.rb +56 -0
  35. data/spec/database_cleaner/mongo_mapper/truncation_spec.rb +81 -0
  36. data/spec/spec.opts +6 -0
  37. data/spec/spec_helper.rb +12 -0
  38. 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,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,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,12 @@
1
+ require 'couchrest'
2
+ ::CouchRest.module_eval do
3
+ class << self
4
+ def default_database_name=(name)
5
+ @default_database_name = name
6
+ end
7
+ def default_database_name
8
+ @default_database_name
9
+ end
10
+ end
11
+ end
12
+
@@ -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,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