database_cleaner-core 2.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,3 @@
1
+ Around do |scenario, block|
2
+ DatabaseCleaner.cleaning(&block)
3
+ 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,11 @@
1
+ module DatabaseCleaner
2
+ module Generic
3
+ module Transaction
4
+ def initialize(opts = {})
5
+ if !opts.empty?
6
+ raise ArgumentError, "Options are not available for transaction strategies."
7
+ end
8
+ end
9
+ end
10
+ end
11
+ 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,20 @@
1
+ module DatabaseCleaner
2
+ class NullStrategy
3
+ def start
4
+ # no-op
5
+ end
6
+
7
+ def db=(connection)
8
+ # no-op
9
+ end
10
+
11
+ def clean
12
+ # no-op
13
+ end
14
+
15
+ def cleaning(&block)
16
+ # no-op
17
+ yield
18
+ end
19
+ end
20
+ 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