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