database_cleaner-core 2.0.0.beta
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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +4 -0
- data/.ruby-version.sample +1 -0
- data/.travis.yml +13 -0
- data/CONTRIBUTE.markdown +27 -0
- data/Gemfile +5 -0
- data/Guardfile +5 -0
- data/History.rdoc +482 -0
- data/LICENSE +20 -0
- data/README.markdown +355 -0
- data/Rakefile +11 -0
- data/bin/setup +7 -0
- data/cucumber.yml +1 -0
- data/database_cleaner-core.gemspec +29 -0
- data/database_cleaner.gemspec +18 -0
- data/lib/database_cleaner-core.rb +1 -0
- data/lib/database_cleaner/base.rb +101 -0
- data/lib/database_cleaner/configuration.rb +86 -0
- data/lib/database_cleaner/core.rb +29 -0
- data/lib/database_cleaner/cucumber.rb +3 -0
- data/lib/database_cleaner/deprecation.rb +20 -0
- data/lib/database_cleaner/generic/base.rb +29 -0
- data/lib/database_cleaner/generic/transaction.rb +11 -0
- data/lib/database_cleaner/generic/truncation.rb +40 -0
- data/lib/database_cleaner/null_strategy.rb +20 -0
- data/lib/database_cleaner/safeguard.rb +107 -0
- data/lib/database_cleaner/spec.rb +2 -0
- data/lib/database_cleaner/spec/database_helper.rb +82 -0
- data/lib/database_cleaner/spec/shared_examples.rb +24 -0
- data/lib/database_cleaner/version.rb +3 -0
- data/tmp/.keep +0 -0
- metadata +161 -0
@@ -0,0 +1 @@
|
|
1
|
+
require "database_cleaner/core"
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'database_cleaner/deprecation'
|
2
|
+
require 'database_cleaner/null_strategy'
|
3
|
+
require 'database_cleaner/safeguard'
|
4
|
+
require 'forwardable'
|
5
|
+
|
6
|
+
module DatabaseCleaner
|
7
|
+
class Base
|
8
|
+
include Comparable
|
9
|
+
|
10
|
+
def <=>(other)
|
11
|
+
[orm, db] <=> [other.orm, other.db]
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(orm = :null, opts = {})
|
15
|
+
self.orm = orm
|
16
|
+
self.db = opts[:connection] || opts[:model] if opts.has_key?(:connection) || opts.has_key?(:model)
|
17
|
+
Safeguard.new.run
|
18
|
+
end
|
19
|
+
|
20
|
+
def db=(desired_db)
|
21
|
+
@db = self.strategy_db = desired_db
|
22
|
+
end
|
23
|
+
|
24
|
+
def db
|
25
|
+
@db ||= :default
|
26
|
+
end
|
27
|
+
|
28
|
+
def strategy=(args)
|
29
|
+
strategy, *strategy_args = args
|
30
|
+
@strategy = if strategy.is_a?(Symbol)
|
31
|
+
create_strategy(*args)
|
32
|
+
elsif strategy_args.empty?
|
33
|
+
strategy
|
34
|
+
else
|
35
|
+
raise ArgumentError, "You must provide a strategy object, or a symbol for a known strategy along with initialization params."
|
36
|
+
end
|
37
|
+
|
38
|
+
set_strategy_db @strategy, db
|
39
|
+
end
|
40
|
+
|
41
|
+
def strategy
|
42
|
+
@strategy ||= NullStrategy.new
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :orm
|
46
|
+
|
47
|
+
def orm= orm
|
48
|
+
raise ArgumentError if orm.nil?
|
49
|
+
@orm = orm.to_sym
|
50
|
+
end
|
51
|
+
|
52
|
+
extend Forwardable
|
53
|
+
delegate [:start, :clean, :cleaning] => :strategy
|
54
|
+
|
55
|
+
def clean_with(*args)
|
56
|
+
strategy = create_strategy(*args)
|
57
|
+
set_strategy_db strategy, db
|
58
|
+
strategy.clean
|
59
|
+
strategy
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def strategy_db=(desired_db)
|
65
|
+
set_strategy_db(strategy, desired_db)
|
66
|
+
end
|
67
|
+
|
68
|
+
def set_strategy_db(strategy, desired_db)
|
69
|
+
if strategy.respond_to? :db=
|
70
|
+
strategy.db = desired_db
|
71
|
+
elsif desired_db != :default
|
72
|
+
raise ArgumentError, "You must provide a strategy object that supports non default databases when you specify a database"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def create_strategy(*args)
|
77
|
+
strategy, *strategy_args = args
|
78
|
+
orm_strategy(strategy).new(*strategy_args)
|
79
|
+
end
|
80
|
+
|
81
|
+
def orm_strategy(strategy)
|
82
|
+
strategy_module_name = strategy.to_s.capitalize
|
83
|
+
orm_module.const_get(strategy_module_name)
|
84
|
+
rescue NameError
|
85
|
+
raise UnknownStrategySpecified, "The '#{strategy}' strategy does not exist for the #{orm} ORM! Available strategies: #{orm_module.available_strategies.join(', ')}"
|
86
|
+
end
|
87
|
+
|
88
|
+
def orm_module
|
89
|
+
orm_module_name = camelize(orm)
|
90
|
+
DatabaseCleaner.const_get(orm_module_name)
|
91
|
+
end
|
92
|
+
|
93
|
+
def camelize(term)
|
94
|
+
string = term.to_s
|
95
|
+
string = string.sub(/^[a-z\d]*/) { |match| match.capitalize }
|
96
|
+
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" }
|
97
|
+
string.gsub!("/", "::")
|
98
|
+
string
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'database_cleaner/base'
|
2
|
+
require 'database_cleaner/deprecation'
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module DatabaseCleaner
|
6
|
+
|
7
|
+
class UnknownStrategySpecified < ArgumentError; end
|
8
|
+
|
9
|
+
class Cleaners < Hash
|
10
|
+
# FIXME this method conflates creation with lookup... both a command and a query. yuck.
|
11
|
+
def [](orm, opts = {})
|
12
|
+
raise ArgumentError if orm.nil?
|
13
|
+
fetch([orm, opts]) { add_cleaner(orm, opts) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def strategy=(strategy)
|
17
|
+
values.each { |cleaner| cleaner.strategy = strategy }
|
18
|
+
remove_duplicates
|
19
|
+
end
|
20
|
+
|
21
|
+
def orm=(orm)
|
22
|
+
values.each { |cleaner| cleaner.orm = orm }
|
23
|
+
remove_duplicates
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def add_cleaner(orm, opts = {})
|
29
|
+
self[[orm, opts]] = ::DatabaseCleaner::Base.new(orm, opts)
|
30
|
+
end
|
31
|
+
|
32
|
+
def remove_duplicates
|
33
|
+
replace(reduce(Cleaners.new) do |cleaners, (key, value)|
|
34
|
+
cleaners[key] = value unless cleaners.values.include?(value)
|
35
|
+
cleaners
|
36
|
+
end)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Configuration
|
41
|
+
def initialize
|
42
|
+
@cleaners ||= Cleaners.new
|
43
|
+
end
|
44
|
+
|
45
|
+
extend Forwardable
|
46
|
+
delegate [
|
47
|
+
:[],
|
48
|
+
:strategy=,
|
49
|
+
:orm=,
|
50
|
+
] => :cleaners
|
51
|
+
|
52
|
+
attr_accessor :cleaners
|
53
|
+
|
54
|
+
def start
|
55
|
+
connections.each { |connection| connection.start }
|
56
|
+
end
|
57
|
+
|
58
|
+
def clean
|
59
|
+
connections.each { |connection| connection.clean }
|
60
|
+
end
|
61
|
+
|
62
|
+
def cleaning(&inner_block)
|
63
|
+
connections.inject(inner_block) do |curr_block, connection|
|
64
|
+
proc { connection.cleaning(&curr_block) }
|
65
|
+
end.call
|
66
|
+
end
|
67
|
+
|
68
|
+
def clean_with(*args)
|
69
|
+
connections.each { |connection| connection.clean_with(*args) }
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def connections
|
75
|
+
@cleaners.values
|
76
|
+
end
|
77
|
+
|
78
|
+
def add_cleaner(orm, opts = {})
|
79
|
+
@cleaners.add_cleaner(orm, opts = {})
|
80
|
+
end
|
81
|
+
|
82
|
+
def remove_duplicates
|
83
|
+
@cleaners.remove_duplicates
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'database_cleaner/version'
|
2
|
+
require 'database_cleaner/configuration'
|
3
|
+
require 'database_cleaner/deprecation'
|
4
|
+
require 'forwardable'
|
5
|
+
|
6
|
+
module DatabaseCleaner
|
7
|
+
class << self
|
8
|
+
extend Forwardable
|
9
|
+
delegate [
|
10
|
+
:[],
|
11
|
+
:cleaners,
|
12
|
+
:cleaners=,
|
13
|
+
:strategy=,
|
14
|
+
:orm=,
|
15
|
+
:start,
|
16
|
+
:clean,
|
17
|
+
:clean_with,
|
18
|
+
:cleaning,
|
19
|
+
] => :configuration
|
20
|
+
|
21
|
+
attr_accessor :allow_remote_database_url, :allow_production, :url_whitelist
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def configuration
|
26
|
+
@configuration ||= Configuration.new
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module DatabaseCleaner
|
2
|
+
def deprecate message
|
3
|
+
method = caller.first[/\d+:in `(.*)'$/, 1].to_sym
|
4
|
+
@@deprecator ||= Deprecator.new
|
5
|
+
@@deprecator.deprecate method, message
|
6
|
+
end
|
7
|
+
module_function :deprecate
|
8
|
+
|
9
|
+
class Deprecator
|
10
|
+
def initialize
|
11
|
+
@methods_already_warned = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def deprecate method, message
|
15
|
+
return if @methods_already_warned.key?(method)
|
16
|
+
$stderr.puts message
|
17
|
+
@methods_already_warned[method] = true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ::DatabaseCleaner
|
2
|
+
module Generic
|
3
|
+
module Base
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
def db
|
10
|
+
:default
|
11
|
+
end
|
12
|
+
|
13
|
+
def cleaning(&block)
|
14
|
+
begin
|
15
|
+
start
|
16
|
+
yield
|
17
|
+
ensure
|
18
|
+
clean
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
def available_strategies
|
24
|
+
%W[]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module DatabaseCleaner
|
2
|
+
module Generic
|
3
|
+
module Truncation
|
4
|
+
def initialize(opts={})
|
5
|
+
if !opts.empty? && !(opts.keys - [:only, :except, :pre_count, :reset_ids, :cache_tables]).empty?
|
6
|
+
raise ArgumentError, "The only valid options are :only, :except, :pre_count, :reset_ids or :cache_tables. You specified #{opts.keys.join(',')}."
|
7
|
+
end
|
8
|
+
if opts.has_key?(:only) && opts.has_key?(:except)
|
9
|
+
raise ArgumentError, "You may only specify either :only or :except. Doing both doesn't really make sense does it?"
|
10
|
+
end
|
11
|
+
|
12
|
+
@only = opts[:only]
|
13
|
+
@tables_to_exclude = Array( (opts[:except] || []).dup ).flatten
|
14
|
+
@tables_to_exclude += migration_storage_names
|
15
|
+
@pre_count = opts[:pre_count]
|
16
|
+
@reset_ids = opts[:reset_ids]
|
17
|
+
@cache_tables = opts.has_key?(:cache_tables) ? !!opts[:cache_tables] : true
|
18
|
+
end
|
19
|
+
|
20
|
+
def start
|
21
|
+
#included for compatability reasons, do nothing if you don't need to
|
22
|
+
end
|
23
|
+
|
24
|
+
def clean
|
25
|
+
raise NotImplementedError
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def tables_to_truncate
|
30
|
+
raise NotImplementedError
|
31
|
+
end
|
32
|
+
|
33
|
+
# overwrite in subclasses
|
34
|
+
# default implementation given because migration storage need not be present
|
35
|
+
def migration_storage_names
|
36
|
+
%w[]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module DatabaseCleaner
|
2
|
+
class Safeguard
|
3
|
+
class Error < Exception
|
4
|
+
class RemoteDatabaseUrl < Error
|
5
|
+
def initialize
|
6
|
+
super("ENV['DATABASE_URL'] is set to a remote URL. Please refer to https://github.com/DatabaseCleaner/database_cleaner#safeguards")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class ProductionEnv < Error
|
11
|
+
def initialize(env)
|
12
|
+
super("ENV['#{env}'] is set to production. Please refer to https://github.com/DatabaseCleaner/database_cleaner#safeguards")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class NotWhitelistedUrl < Error
|
17
|
+
def initialize
|
18
|
+
super("ENV['DATABASE_URL'] is set to a URL that is not on the whitelist. Please refer to https://github.com/DatabaseCleaner/database_cleaner#safeguards")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class WhitelistedUrl
|
24
|
+
def run
|
25
|
+
return if skip?
|
26
|
+
raise Error::NotWhitelistedUrl if database_url_not_whitelisted?
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def database_url_not_whitelisted?
|
32
|
+
!DatabaseCleaner.url_whitelist.include?(ENV['DATABASE_URL'])
|
33
|
+
end
|
34
|
+
|
35
|
+
def skip?
|
36
|
+
!DatabaseCleaner.url_whitelist
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
class RemoteDatabaseUrl
|
42
|
+
LOCAL = %w(localhost 127.0.0.1)
|
43
|
+
|
44
|
+
def run
|
45
|
+
raise Error::RemoteDatabaseUrl if !skip? && given?
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def given?
|
51
|
+
remote?(ENV['DATABASE_URL'])
|
52
|
+
end
|
53
|
+
|
54
|
+
def remote?(url)
|
55
|
+
return false unless url
|
56
|
+
|
57
|
+
parsed = URI.parse(url)
|
58
|
+
return false if parsed.scheme == 'sqlite3:'
|
59
|
+
|
60
|
+
host = parsed.host
|
61
|
+
return false unless host
|
62
|
+
return false if LOCAL.include?(host)
|
63
|
+
return false if host.end_with? '.local'
|
64
|
+
true
|
65
|
+
end
|
66
|
+
|
67
|
+
def skip?
|
68
|
+
ENV['DATABASE_CLEANER_ALLOW_REMOTE_DATABASE_URL'] ||
|
69
|
+
DatabaseCleaner.allow_remote_database_url ||
|
70
|
+
DatabaseCleaner.url_whitelist
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class Production
|
75
|
+
KEYS = %w(ENV RACK_ENV RAILS_ENV)
|
76
|
+
|
77
|
+
def run
|
78
|
+
raise Error::ProductionEnv.new(key) if !skip? && given?
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def given?
|
84
|
+
!!key
|
85
|
+
end
|
86
|
+
|
87
|
+
def key
|
88
|
+
@key ||= KEYS.detect { |key| ENV[key] == 'production' }
|
89
|
+
end
|
90
|
+
|
91
|
+
def skip?
|
92
|
+
ENV['DATABASE_CLEANER_ALLOW_PRODUCTION'] ||
|
93
|
+
DatabaseCleaner.allow_production
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
CHECKS = [
|
98
|
+
RemoteDatabaseUrl,
|
99
|
+
Production,
|
100
|
+
WhitelistedUrl
|
101
|
+
]
|
102
|
+
|
103
|
+
def run
|
104
|
+
CHECKS.each { |const| const.new.run }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|