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