database_cleaner-active_record 1.8.0.beta

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f8d7d0428f1d0316620ed6aaf8d5053d7171e47f156fc806a79811a2eca9b91e
4
+ data.tar.gz: faa79553fe66d634d08557260827d88cd6463622903aae6192ec5126db8112a7
5
+ SHA512:
6
+ metadata.gz: 33d786270e2e14441c98f79ad82bf226bb2a4d5a1fe95c777e32433ffb43edddbe4b17516eafc5aef70e557272201dd7997238ad99aa1af1960cb92257f0bbd7
7
+ data.tar.gz: d3c310d5f09216552da3ac9998cd8a2443610194fc3a8b0f3de2dc8cfd941bf8899b088231a65507c5f45b19767e65e83c6ab58bd5bd3416941e17be234ea7d6
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/support/config.yml
9
+ /tmp/
10
+ !/tmp/.keep
11
+
12
+ # rspec failure tracking
13
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.2.9
5
+ before_install: gem install bundler -v 1.17.2
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in database_cleaner-active_record.gemspec
6
+ gemspec
7
+
8
+ gem "database_cleaner", path: "../.."
@@ -0,0 +1,73 @@
1
+ PATH
2
+ remote: ../..
3
+ specs:
4
+ database_cleaner (1.8.0.beta)
5
+
6
+ PATH
7
+ remote: .
8
+ specs:
9
+ database_cleaner-active_record (1.8.0.beta)
10
+ activerecord
11
+ database_cleaner (~> 1.8.0.beta)
12
+
13
+ GEM
14
+ remote: https://rubygems.org/
15
+ specs:
16
+ activemodel (5.2.4.1)
17
+ activesupport (= 5.2.4.1)
18
+ activerecord (5.2.4.1)
19
+ activemodel (= 5.2.4.1)
20
+ activesupport (= 5.2.4.1)
21
+ arel (>= 9.0)
22
+ activerecord-mysql2-adapter (0.0.3)
23
+ mysql2
24
+ activesupport (5.2.4.1)
25
+ concurrent-ruby (~> 1.0, >= 1.0.2)
26
+ i18n (>= 0.7, < 2)
27
+ minitest (~> 5.1)
28
+ tzinfo (~> 1.1)
29
+ arel (9.0.0)
30
+ concurrent-ruby (1.1.5)
31
+ diff-lcs (1.3)
32
+ i18n (1.5.1)
33
+ concurrent-ruby (~> 1.0)
34
+ minitest (5.14.0)
35
+ mysql (2.9.1)
36
+ mysql2 (0.3.18)
37
+ pg (0.18.2)
38
+ rake (10.4.2)
39
+ rspec (3.7.0)
40
+ rspec-core (~> 3.7.0)
41
+ rspec-expectations (~> 3.7.0)
42
+ rspec-mocks (~> 3.7.0)
43
+ rspec-core (3.7.1)
44
+ rspec-support (~> 3.7.0)
45
+ rspec-expectations (3.7.0)
46
+ diff-lcs (>= 1.2.0, < 2.0)
47
+ rspec-support (~> 3.7.0)
48
+ rspec-mocks (3.7.0)
49
+ diff-lcs (>= 1.2.0, < 2.0)
50
+ rspec-support (~> 3.7.0)
51
+ rspec-support (3.7.1)
52
+ sqlite3 (1.3.10)
53
+ thread_safe (0.3.6)
54
+ tzinfo (1.2.6)
55
+ thread_safe (~> 0.1)
56
+
57
+ PLATFORMS
58
+ ruby
59
+
60
+ DEPENDENCIES
61
+ activerecord-mysql2-adapter
62
+ bundler (~> 1.16)
63
+ database_cleaner!
64
+ database_cleaner-active_record!
65
+ mysql (~> 2.9.1)
66
+ mysql2
67
+ pg
68
+ rake (~> 10.0)
69
+ rspec (~> 3.0)
70
+ sqlite3
71
+
72
+ BUNDLED WITH
73
+ 1.17.3
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2009 Ben Mabey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,110 @@
1
+ # Database Cleaner Adapter for ActiveRecord
2
+
3
+ [![Build Status](https://travis-ci.org/DatabaseCleaner/database_cleaner-active_record.svg?branch=master)](https://travis-ci.org/DatabaseCleaner/database_cleaner-active_record)
4
+ [![Code Climate](https://codeclimate.com/github/DatabaseCleaner/database_cleaner-active_record/badges/gpa.svg)](https://codeclimate.com/github/DatabaseCleaner/database_cleaner-active_record)
5
+
6
+ Clean your ActiveRecord databases with Database Cleaner.
7
+
8
+ See https://github.com/DatabaseCleaner/database_cleaner for more information.
9
+
10
+ ## Installation
11
+
12
+ ```ruby
13
+ # Gemfile
14
+ group :test do
15
+ gem 'database_cleaner-active_record'
16
+ end
17
+ ```
18
+
19
+ ## Supported Strategies
20
+
21
+ Here is an overview of the supported strategies:
22
+
23
+ <table>
24
+ <tbody>
25
+ <tr>
26
+ <th>Truncation</th>
27
+ <th>Transaction</th>
28
+ <th>Deletion</th>
29
+ </tr>
30
+ <tr>
31
+ <td> Yes</td>
32
+ <td> <b>Yes</b></td>
33
+ <td> Yes</td>
34
+ </tr>
35
+ </tbody>
36
+ </table>
37
+
38
+ (Default strategy is denoted in bold)
39
+
40
+ For support or to discuss development please use the [Google Group](https://groups.google.com/group/database_cleaner).
41
+
42
+ ## What strategy is fastest?
43
+
44
+ For the SQL libraries the fastest option will be to use `:transaction` as transactions are simply rolled back. If you can use this strategy you should. However, if you wind up needing to use multiple database connections in your tests (i.e. your tests run in a different process than your application) then using this strategy becomes a bit more difficult. You can get around the problem a number of ways.
45
+
46
+ One common approach is to force all processes to use the same database connection ([common ActiveRecord hack](http://blog.plataformatec.com.br/2011/12/three-tips-to-improve-the-performance-of-your-test-suite/)) however this approach has been reported to result in non-deterministic failures.
47
+
48
+ Another approach is to have the transactions rolled back in the application's process and relax the isolation level of the database (so the tests can read the uncommitted transactions).
49
+
50
+ An easier, but slower, solution is to use the `:truncation` or `:deletion` strategy.
51
+
52
+ So what is fastest out of `:deletion` and `:truncation`? Well, it depends on your table structure and what percentage of tables you populate in an average test. The reasoning is out of the scope of this README but here is a [good SO answer on this topic for Postgres](https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886).
53
+
54
+ Some people report much faster speeds with `:deletion` while others say `:truncation` is faster for them. The best approach therefore is it try all options on your test suite and see what is faster.
55
+
56
+ If you are using ActiveRecord then take a look at the [additional options](#additional-activerecord-options-for-truncation) available for `:truncation`.
57
+
58
+ ## Configuration options
59
+
60
+ <table>
61
+ <tbody>
62
+ <tr>
63
+ <th>ORM</th>
64
+ <th>How to access</th>
65
+ <th>Notes</th>
66
+ </tr>
67
+ <tr>
68
+ <td> Active Record </td>
69
+ <td> <code>DatabaseCleaner[:active_record]</code></td>
70
+ <td> Connection specified as <code>:symbol</code> keys, loaded from <code>config/database.yml</code>. You may also pass in the ActiveRecord model under the <code>:model</code> key.</td>
71
+ </tr>
72
+ </tbody>
73
+ </table>
74
+
75
+ ### Additional ActiveRecord options for Truncation
76
+
77
+ The following options are available for ActiveRecord's `:truncation` strategy _only_ for MySQL and Postgres.
78
+
79
+ * `:pre_count` - When set to `true` this will check each table for existing rows before truncating it. This can speed up test suites when many of the tables to be truncated are never populated. Defaults to `:false`. (Also, see the section on [What strategy is fastest?](#what-strategy-is-fastest))
80
+ * `:reset_ids` - This only matters when `:pre_count` is used, and it will make sure that a tables auto-incrementing id is reset even if there are no rows in the table (e.g. records were created in the test but also removed before DatabaseCleaner gets to it). Defaults to `true`.
81
+
82
+ The following option is available for ActiveRecord's `:truncation` and `:deletion` strategy for any DB.
83
+
84
+ * `:cache_tables` - When set to `true` the list of tables to truncate or delete from will only be read from the DB once, otherwise it will be read before each cleanup run. Set this to `false` if (1) you create and drop tables in your tests, or (2) you change Postgres schemas (`ActiveRecord::Base.connection.schema_search_path`) in your tests (for example, in a multitenancy setup with each tenant in a different Postgres schema). Defaults to `true`.
85
+
86
+
87
+ ## Common Errors
88
+
89
+ ### STDERR is being flooded when using Postgres
90
+
91
+ If you are using Postgres and have foreign key constraints, the truncation strategy will cause a lot of extra noise to appear on STDERR (in the form of "NOTICE truncate cascades" messages).
92
+
93
+ To silence these warnings set the following log level in your `postgresql.conf` file:
94
+
95
+ ```ruby
96
+ client_min_messages = warning
97
+ ```
98
+
99
+ For ActiveRecord, you add the following parameter in your database.yml file:
100
+
101
+ <pre>
102
+ test:
103
+ adapter: postgresql
104
+ # ...
105
+ min_messages: WARNING
106
+ </pre>
107
+
108
+ ## COPYRIGHT
109
+
110
+ See [LICENSE] for details.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "database_cleaner/active_record"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle check || bundle install
7
+ cp spec/support/sample.config.yml spec/support/config.yml
8
+
@@ -0,0 +1,40 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "database_cleaner/active_record/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "database_cleaner-active_record"
8
+ spec.version = DatabaseCleaner::ActiveRecord::VERSION
9
+ spec.authors = ["Ernesto Tagwerker"]
10
+ spec.email = ["ernesto@ombulabs.com"]
11
+
12
+ spec.summary = "Strategies for cleaning databases using ActiveRecord. Can be used to ensure a clean state for testing."
13
+ spec.description = "Strategies for cleaning databases using ActiveRecord. Can be used to ensure a clean state for testing."
14
+ spec.homepage = "https://github.com/DatabaseCleaner/database_cleaner-active_record"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_dependency "database_cleaner", "~> 1.8.0.beta"
25
+ spec.add_dependency "activerecord"
26
+
27
+ spec.add_development_dependency "rake", "~> 10.0"
28
+ spec.add_development_dependency "bundler", "~> 1.16"
29
+ spec.add_development_dependency "rspec", "~> 3.0"
30
+
31
+ unless RUBY_PLATFORM =~ /java/
32
+ spec.add_development_dependency 'mysql', '~> 2.9.1'
33
+ spec.add_development_dependency 'mysql2'
34
+ spec.add_development_dependency "activerecord-mysql2-adapter"
35
+ spec.add_development_dependency 'pg'
36
+ spec.add_development_dependency "sqlite3"
37
+ else
38
+ spec.add_development_dependency "activerecord-jdbc-adapter"
39
+ end
40
+ end
@@ -0,0 +1 @@
1
+ require "database_cleaner/active_record"
@@ -0,0 +1,6 @@
1
+ require 'database_cleaner/active_record/version'
2
+ require 'database_cleaner'
3
+ require 'database_cleaner/active_record/deletion'
4
+ require 'database_cleaner/active_record/transaction'
5
+ require 'database_cleaner/active_record/truncation'
6
+
@@ -0,0 +1,101 @@
1
+ require 'active_record'
2
+ require 'database_cleaner/generic/base'
3
+ require 'erb'
4
+ require 'yaml'
5
+
6
+ module DatabaseCleaner
7
+ module ActiveRecord
8
+ def self.available_strategies
9
+ %w[truncation transaction deletion]
10
+ end
11
+
12
+ def self.default_strategy
13
+ :transaction
14
+ end
15
+
16
+ def self.config_file_location=(path)
17
+ @config_file_location = path
18
+ end
19
+
20
+ def self.config_file_location
21
+ @config_file_location ||= begin
22
+ # Has DC.app_root been set? Check in this intrusive way to avoid triggering deprecation warnings if it hasn't.
23
+ app_root = DatabaseCleaner.send(:configuration).instance_variable_get(:@app_root)
24
+ root = app_root || Dir.pwd
25
+ "#{root}/config/database.yml"
26
+ end
27
+ end
28
+
29
+ module Base
30
+ include ::DatabaseCleaner::Generic::Base
31
+
32
+ attr_accessor :connection_hash
33
+
34
+ def db=(desired_db)
35
+ @db = desired_db
36
+ load_config
37
+ end
38
+
39
+ def db
40
+ @db ||= super
41
+ end
42
+
43
+ def load_config
44
+ if self.db != :default && self.db.is_a?(Symbol) && File.file?(ActiveRecord.config_file_location)
45
+ connection_details = YAML::load(ERB.new(IO.read(ActiveRecord.config_file_location)).result)
46
+ @connection_hash = valid_config(connection_details)[self.db.to_s]
47
+ end
48
+ end
49
+
50
+ def valid_config(connection_file)
51
+ if !::ActiveRecord::Base.configurations.nil? && !::ActiveRecord::Base.configurations.empty?
52
+ if connection_file != ::ActiveRecord::Base.configurations
53
+ return ::ActiveRecord::Base.configurations
54
+ end
55
+ end
56
+ connection_file
57
+ end
58
+
59
+ def connection_class
60
+ @connection_class ||= if db && !db.is_a?(Symbol)
61
+ db
62
+ elsif connection_hash
63
+ lookup_from_connection_pool || establish_connection
64
+ else
65
+ ::ActiveRecord::Base
66
+ end
67
+ end
68
+
69
+ def self.migration_table_name
70
+ if ::ActiveRecord::VERSION::MAJOR < 5
71
+ ::ActiveRecord::Migrator.schema_migrations_table_name
72
+ else
73
+ ::ActiveRecord::SchemaMigration.table_name
74
+ end
75
+ end
76
+
77
+ def self.exclusion_condition(column_name)
78
+ result = " #{column_name} <> '#{::DatabaseCleaner::ActiveRecord::Base.migration_table_name}' "
79
+ if ::ActiveRecord::VERSION::MAJOR >= 5
80
+ result += " AND #{column_name} <> '#{::ActiveRecord::Base.internal_metadata_table_name}' "
81
+ end
82
+ result
83
+ end
84
+
85
+ private
86
+
87
+ def lookup_from_connection_pool
88
+ if ::ActiveRecord::Base.respond_to?(:descendants)
89
+ database_name = connection_hash["database"] || connection_hash[:database]
90
+ models = ::ActiveRecord::Base.descendants
91
+ models.select(&:connection_pool).detect { |m| m.connection_pool.spec.config[:database] == database_name }
92
+ end
93
+ end
94
+
95
+ def establish_connection
96
+ ::ActiveRecord::Base.establish_connection(connection_hash)
97
+ ::ActiveRecord::Base
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,108 @@
1
+ require 'active_record'
2
+ require 'active_record/connection_adapters/abstract_adapter'
3
+ require "database_cleaner/generic/truncation"
4
+ require 'database_cleaner/active_record/truncation'
5
+
6
+ module DatabaseCleaner
7
+ module ConnectionAdapters
8
+ module AbstractDeleteAdapter
9
+ def delete_table(table_name)
10
+ raise NotImplementedError
11
+ end
12
+ end
13
+
14
+ module GenericDeleteAdapter
15
+ def delete_table(table_name)
16
+ execute("DELETE FROM #{quote_table_name(table_name)};")
17
+ end
18
+ end
19
+
20
+ module OracleDeleteAdapter
21
+ def delete_table(table_name)
22
+ execute("DELETE FROM #{quote_table_name(table_name)}")
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ module ActiveRecord
29
+ module ConnectionAdapters
30
+ AbstractAdapter.class_eval { include DatabaseCleaner::ConnectionAdapters::AbstractDeleteAdapter }
31
+
32
+ JdbcAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(JdbcAdapter)
33
+ AbstractMysqlAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(AbstractMysqlAdapter)
34
+ Mysql2Adapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(Mysql2Adapter)
35
+ SQLiteAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(SQLiteAdapter)
36
+ SQLite3Adapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(SQLite3Adapter)
37
+ PostgreSQLAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(PostgreSQLAdapter)
38
+ IBM_DBAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(IBM_DBAdapter)
39
+ SQLServerAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(SQLServerAdapter)
40
+ OracleEnhancedAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::OracleDeleteAdapter } if defined?(OracleEnhancedAdapter)
41
+ end
42
+ end
43
+
44
+ module DatabaseCleaner::ActiveRecord
45
+ module SelectiveTruncation
46
+ def tables_to_truncate(connection)
47
+ if information_schema_exists?(connection)
48
+ (@only || tables_with_new_rows(connection)) - @tables_to_exclude
49
+ else
50
+ super
51
+ end
52
+ end
53
+
54
+ def tables_with_new_rows(connection)
55
+ stats = table_stats_query(connection)
56
+ if stats != ''
57
+ connection.select_values(stats)
58
+ else
59
+ []
60
+ end
61
+ end
62
+
63
+ def table_stats_query(connection)
64
+ @table_stats_query ||= build_table_stats_query(connection)
65
+ ensure
66
+ @table_stats_query = nil unless @cache_tables
67
+ end
68
+
69
+ def build_table_stats_query(connection)
70
+ tables = connection.select_values(<<-SQL)
71
+ SELECT table_name
72
+ FROM information_schema.tables
73
+ WHERE table_schema = database()
74
+ AND #{::DatabaseCleaner::ActiveRecord::Base.exclusion_condition('table_name')};
75
+ SQL
76
+ queries = tables.map do |table|
77
+ "(SELECT #{connection.quote(table)} FROM #{connection.quote_table_name(table)} LIMIT 1)"
78
+ end
79
+ queries.join(' UNION ALL ')
80
+ end
81
+
82
+ def information_schema_exists? connection
83
+ return false unless connection.is_a? ActiveRecord::ConnectionAdapters::Mysql2Adapter
84
+ @information_schema_exists ||=
85
+ begin
86
+ connection.execute("SELECT 1 FROM information_schema.tables")
87
+ true
88
+ rescue
89
+ false
90
+ end
91
+ end
92
+ end
93
+
94
+ class Deletion < Truncation
95
+ if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
96
+ include SelectiveTruncation
97
+ end
98
+
99
+ def clean
100
+ connection = connection_class.connection
101
+ connection.disable_referential_integrity do
102
+ tables_to_truncate(connection).each do |table_name|
103
+ connection.delete_table table_name
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,59 @@
1
+ require 'database_cleaner/active_record/base'
2
+ require 'database_cleaner/generic/transaction'
3
+
4
+ module DatabaseCleaner::ActiveRecord
5
+ class Transaction
6
+ include ::DatabaseCleaner::ActiveRecord::Base
7
+ include ::DatabaseCleaner::Generic::Transaction
8
+
9
+ def start
10
+ # Hack to make sure that the connection is properly setup for
11
+ # the clean code.
12
+ connection_class.connection.transaction{ }
13
+
14
+ if connection_maintains_transaction_count?
15
+ if connection_class.connection.respond_to?(:increment_open_transactions)
16
+ connection_class.connection.increment_open_transactions
17
+ else
18
+ connection_class.__send__(:increment_open_transactions)
19
+ end
20
+ end
21
+ if connection_class.connection.respond_to?(:begin_transaction)
22
+ connection_class.connection.begin_transaction :joinable => false
23
+ else
24
+ connection_class.connection.begin_db_transaction
25
+ end
26
+ end
27
+
28
+
29
+ def clean
30
+ connection_class.connection_pool.connections.each do |connection|
31
+ next unless connection.open_transactions > 0
32
+
33
+ if connection.respond_to?(:rollback_transaction)
34
+ connection.rollback_transaction
35
+ else
36
+ connection.rollback_db_transaction
37
+ end
38
+
39
+ # The below is for handling after_commit hooks.. see https://github.com/bmabey/database_cleaner/issues/99
40
+ if connection.respond_to?(:rollback_transaction_records, true)
41
+ connection.send(:rollback_transaction_records, true)
42
+ end
43
+
44
+ if connection_maintains_transaction_count?
45
+ if connection.respond_to?(:decrement_open_transactions)
46
+ connection.decrement_open_transactions
47
+ else
48
+ connection_class.__send__(:decrement_open_transactions)
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ def connection_maintains_transaction_count?
55
+ ActiveRecord::VERSION::MAJOR < 4
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,278 @@
1
+ require 'active_record/base'
2
+ require 'database_cleaner/active_record/base'
3
+ require 'active_record/connection_adapters/abstract_adapter'
4
+
5
+ #Load available connection adapters
6
+ %w(
7
+ abstract_mysql_adapter postgresql_adapter sqlite3_adapter mysql_adapter mysql2_adapter oracle_enhanced_adapter
8
+ ).each do |known_adapter|
9
+ begin
10
+ require "active_record/connection_adapters/#{known_adapter}"
11
+ rescue LoadError
12
+ end
13
+ end
14
+
15
+ require "database_cleaner/generic/truncation"
16
+ require 'database_cleaner/active_record/base'
17
+
18
+ module DatabaseCleaner
19
+ module ConnectionAdapters
20
+
21
+ module AbstractAdapter
22
+ # used to be called views but that can clash with gems like schema_plus
23
+ # this gem is not meant to be exposing such an extra interface any way
24
+ def database_cleaner_view_cache
25
+ @views ||= select_values("select table_name from information_schema.views where table_schema = '#{current_database}'") rescue []
26
+ end
27
+
28
+ def database_cleaner_table_cache
29
+ # the adapters don't do caching (#130) but we make the assumption that the list stays the same in tests
30
+ @database_cleaner_tables ||= database_tables
31
+ end
32
+
33
+ def database_tables
34
+ ::ActiveRecord::VERSION::MAJOR >= 5 ? data_sources : tables
35
+ end
36
+
37
+ def truncate_table(table_name)
38
+ raise NotImplementedError
39
+ end
40
+
41
+ def truncate_tables(tables)
42
+ tables.each do |table_name|
43
+ self.truncate_table(table_name)
44
+ end
45
+ end
46
+ end
47
+
48
+ module AbstractMysqlAdapter
49
+ def truncate_table(table_name)
50
+ execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
51
+ end
52
+
53
+ def truncate_tables(tables)
54
+ tables.each { |t| truncate_table(t) }
55
+ end
56
+
57
+ def pre_count_truncate_tables(tables, options = {:reset_ids => true})
58
+ filter = options[:reset_ids] ? method(:has_been_used?) : method(:has_rows?)
59
+ truncate_tables(tables.select(&filter))
60
+ end
61
+
62
+ private
63
+
64
+ def row_count(table)
65
+ # Patch for MysqlAdapter with ActiveRecord 3.2.7 later
66
+ # select_value("SELECT 1") #=> "1"
67
+ select_value("SELECT EXISTS (SELECT 1 FROM #{quote_table_name(table)} LIMIT 1)").to_i
68
+ end
69
+
70
+ def auto_increment_value(table)
71
+ select_value(<<-SQL).to_i
72
+ SELECT auto_increment
73
+ FROM information_schema.tables
74
+ WHERE table_name = '#{table}'
75
+ AND table_schema = database()
76
+ SQL
77
+ end
78
+
79
+ # This method tells us if the given table has been inserted into since its
80
+ # last truncation. Note that the table might have been populated, which
81
+ # increased the auto-increment counter, but then cleaned again such that
82
+ # it appears empty now.
83
+ def has_been_used?(table)
84
+ has_rows?(table) || auto_increment_value(table) > 1
85
+ end
86
+
87
+ def has_rows?(table)
88
+ row_count(table) > 0
89
+ end
90
+ end
91
+
92
+ module IBM_DBAdapter
93
+ def truncate_table(table_name)
94
+ execute("TRUNCATE #{quote_table_name(table_name)} IMMEDIATE")
95
+ end
96
+ end
97
+
98
+ module SQLiteAdapter
99
+ def delete_table(table_name)
100
+ execute("DELETE FROM #{quote_table_name(table_name)};")
101
+ if uses_sequence
102
+ execute("DELETE FROM sqlite_sequence where name = '#{table_name}';")
103
+ end
104
+ end
105
+ alias truncate_table delete_table
106
+
107
+ def truncate_tables(tables)
108
+ tables.each { |t| truncate_table(t) }
109
+ end
110
+
111
+ private
112
+
113
+ # Returns a boolean indicating if the SQLite database is using the sqlite_sequence table.
114
+ def uses_sequence
115
+ select_value("SELECT name FROM sqlite_master WHERE type='table' AND name='sqlite_sequence';")
116
+ end
117
+ end
118
+
119
+ module TruncateOrDelete
120
+ def truncate_table(table_name)
121
+ begin
122
+ execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
123
+ rescue ::ActiveRecord::StatementInvalid
124
+ execute("DELETE FROM #{quote_table_name(table_name)};")
125
+ end
126
+ end
127
+ end
128
+
129
+ module OracleAdapter
130
+ def truncate_table(table_name)
131
+ execute("TRUNCATE TABLE #{quote_table_name(table_name)}")
132
+ end
133
+ end
134
+
135
+ module PostgreSQLAdapter
136
+ def db_version
137
+ @db_version ||= postgresql_version
138
+ end
139
+
140
+ def cascade
141
+ @cascade ||= db_version >= 80200 ? 'CASCADE' : ''
142
+ end
143
+
144
+ def restart_identity
145
+ @restart_identity ||= db_version >= 80400 ? 'RESTART IDENTITY' : ''
146
+ end
147
+
148
+ def truncate_table(table_name)
149
+ truncate_tables([table_name])
150
+ end
151
+
152
+ def truncate_tables(table_names)
153
+ return if table_names.nil? || table_names.empty?
154
+ execute("TRUNCATE TABLE #{table_names.map{|name| quote_table_name(name)}.join(', ')} #{restart_identity} #{cascade};")
155
+ end
156
+
157
+ def pre_count_truncate_tables(tables, options = {:reset_ids => true})
158
+ filter = options[:reset_ids] ? method(:has_been_used?) : method(:has_rows?)
159
+ truncate_tables(tables.select(&filter))
160
+ end
161
+
162
+ def database_cleaner_table_cache
163
+ # AR returns a list of tables without schema but then returns a
164
+ # migrations table with the schema. There are other problems, too,
165
+ # with using the base list. If a table exists in multiple schemas
166
+ # within the search path, truncation without the schema name could
167
+ # result in confusing, if not unexpected results.
168
+ @database_cleaner_tables ||= tables_with_schema
169
+ end
170
+
171
+ private
172
+
173
+ # Returns a boolean indicating if the given table has an auto-inc number higher than 0.
174
+ # Note, this is different than an empty table since an table may populated, the index increased,
175
+ # but then the table is cleaned. In other words, this function tells us if the given table
176
+ # was ever inserted into.
177
+ def has_been_used?(table)
178
+ return has_rows?(table) unless has_sequence?(table)
179
+
180
+ cur_val = select_value("SELECT currval('#{table}_id_seq');").to_i rescue 0
181
+ cur_val > 0
182
+ end
183
+
184
+ def has_sequence?(table)
185
+ select_value("SELECT true FROM pg_class WHERE relname = '#{table}_id_seq';")
186
+ end
187
+
188
+ def has_rows?(table)
189
+ select_value("SELECT true FROM #{table} LIMIT 1;")
190
+ end
191
+
192
+ def tables_with_schema
193
+ rows = select_rows <<-_SQL
194
+ SELECT schemaname || '.' || tablename
195
+ FROM pg_tables
196
+ WHERE
197
+ tablename !~ '_prt_' AND
198
+ #{::DatabaseCleaner::ActiveRecord::Base.exclusion_condition('tablename')} AND
199
+ schemaname = ANY (current_schemas(false))
200
+ _SQL
201
+ rows.collect { |result| result.first }
202
+ end
203
+ end
204
+ end
205
+ end
206
+
207
+ module ActiveRecord
208
+ module ConnectionAdapters
209
+ #Apply adapter decoraters where applicable (adapter should be loaded)
210
+ AbstractAdapter.class_eval { include DatabaseCleaner::ConnectionAdapters::AbstractAdapter }
211
+
212
+ if defined?(JdbcAdapter)
213
+ if defined?(OracleJdbcConnection)
214
+ JdbcAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::OracleAdapter }
215
+ else
216
+ JdbcAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::TruncateOrDelete }
217
+ end
218
+ end
219
+ AbstractMysqlAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::AbstractMysqlAdapter } if defined?(AbstractMysqlAdapter)
220
+ Mysql2Adapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::AbstractMysqlAdapter } if defined?(Mysql2Adapter)
221
+ MysqlAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::AbstractMysqlAdapter } if defined?(MysqlAdapter)
222
+ SQLiteAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::SQLiteAdapter } if defined?(SQLiteAdapter)
223
+ SQLite3Adapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::SQLiteAdapter } if defined?(SQLite3Adapter)
224
+ PostgreSQLAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::PostgreSQLAdapter } if defined?(PostgreSQLAdapter)
225
+ IBM_DBAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::IBM_DBAdapter } if defined?(IBM_DBAdapter)
226
+ SQLServerAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::TruncateOrDelete } if defined?(SQLServerAdapter)
227
+ OracleEnhancedAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::OracleAdapter } if defined?(OracleEnhancedAdapter)
228
+ end
229
+ end
230
+
231
+ module DatabaseCleaner::ActiveRecord
232
+ class Truncation
233
+ include ::DatabaseCleaner::ActiveRecord::Base
234
+ include ::DatabaseCleaner::Generic::Truncation
235
+
236
+ def clean
237
+ connection = connection_class.connection
238
+ connection.disable_referential_integrity do
239
+ if pre_count? && connection.respond_to?(:pre_count_truncate_tables)
240
+ connection.pre_count_truncate_tables(tables_to_truncate(connection), {:reset_ids => reset_ids?})
241
+ else
242
+ connection.truncate_tables(tables_to_truncate(connection))
243
+ end
244
+ end
245
+ end
246
+
247
+ private
248
+
249
+ def tables_to_truncate(connection)
250
+ tables_in_db = cache_tables? ? connection.database_cleaner_table_cache : connection.database_tables
251
+ to_reject = (@tables_to_exclude + connection.database_cleaner_view_cache)
252
+ (@only || tables_in_db).reject do |table|
253
+ if ( m = table.match(/([^.]+)$/) )
254
+ to_reject.include?(m[1])
255
+ else
256
+ false
257
+ end
258
+ end
259
+ end
260
+
261
+ # overwritten
262
+ def migration_storage_names
263
+ [::DatabaseCleaner::ActiveRecord::Base.migration_table_name]
264
+ end
265
+
266
+ def cache_tables?
267
+ !!@cache_tables
268
+ end
269
+
270
+ def pre_count?
271
+ @pre_count == true
272
+ end
273
+
274
+ def reset_ids?
275
+ @reset_ids != false
276
+ end
277
+ end
278
+ end
@@ -0,0 +1,5 @@
1
+ module DatabaseCleaner
2
+ module ActiveRecord
3
+ VERSION = "1.8.0.beta"
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,204 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: database_cleaner-active_record
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.8.0.beta
5
+ platform: ruby
6
+ authors:
7
+ - Ernesto Tagwerker
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-01-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: database_cleaner
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.8.0.beta
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.8.0.beta
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.16'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.16'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: mysql
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 2.9.1
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 2.9.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: mysql2
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: activerecord-mysql2-adapter
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: pg
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: sqlite3
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ description: Strategies for cleaning databases using ActiveRecord. Can be used to
154
+ ensure a clean state for testing.
155
+ email:
156
+ - ernesto@ombulabs.com
157
+ executables: []
158
+ extensions: []
159
+ extra_rdoc_files: []
160
+ files:
161
+ - ".gitignore"
162
+ - ".rspec"
163
+ - ".travis.yml"
164
+ - Gemfile
165
+ - Gemfile.lock
166
+ - LICENSE.txt
167
+ - README.md
168
+ - Rakefile
169
+ - bin/console
170
+ - bin/setup
171
+ - database_cleaner-active_record.gemspec
172
+ - lib/database_cleaner-active_record.rb
173
+ - lib/database_cleaner/active_record.rb
174
+ - lib/database_cleaner/active_record/base.rb
175
+ - lib/database_cleaner/active_record/deletion.rb
176
+ - lib/database_cleaner/active_record/transaction.rb
177
+ - lib/database_cleaner/active_record/truncation.rb
178
+ - lib/database_cleaner/active_record/version.rb
179
+ - tmp/.keep
180
+ homepage: https://github.com/DatabaseCleaner/database_cleaner-active_record
181
+ licenses:
182
+ - MIT
183
+ metadata: {}
184
+ post_install_message:
185
+ rdoc_options: []
186
+ require_paths:
187
+ - lib
188
+ required_ruby_version: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - ">="
191
+ - !ruby/object:Gem::Version
192
+ version: '0'
193
+ required_rubygems_version: !ruby/object:Gem::Requirement
194
+ requirements:
195
+ - - ">"
196
+ - !ruby/object:Gem::Version
197
+ version: 1.3.1
198
+ requirements: []
199
+ rubygems_version: 3.0.4
200
+ signing_key:
201
+ specification_version: 4
202
+ summary: Strategies for cleaning databases using ActiveRecord. Can be used to ensure
203
+ a clean state for testing.
204
+ test_files: []