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