database_cleaner-active_record 2.0.0.beta → 2.0.0.beta2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 476fea7e84362519c14f1413c9af334688b3634f1b93eb337b35e52ca0c6aaea
4
- data.tar.gz: e9022df4c0c38d5cb422c2f2daca8b5427c6a95ea452c0cddc46367fecfcc616
3
+ metadata.gz: 53199df54296cd17b30bf241e2ca431323b7bef7c4361240f63a682be6d02ee9
4
+ data.tar.gz: 668c5fbe4726841ff27034bcf81de0f98c6ba9494aa2a0cb3fcbade1bfb63d74
5
5
  SHA512:
6
- metadata.gz: c27d71a958fedeb6391c3218a92bdaed05049f0feafe6402ca2dce691b6d159504f147053ce322f64d05f08216d882d040ea97fce14e39721ee4710da52f4175
7
- data.tar.gz: 32e627ff45a35adc27bcbcc08af228f497fcec093ff869c73f3c5ff4febcafa8b216a200b86ff7ece3b2a36882e2d9fe896b83a69aa58deca6239bc19b3118b2
6
+ metadata.gz: 253bced62511194095bef9671ed30602be05d2c417ba39e79dd7b2df7b58f5f602551ccd53c91ad451746bfb9e6086e107818d089c0b37256e4035098bb10fdf
7
+ data.tar.gz: 5a4b22a83f03b8064b77e966c277ae9ebc98ff9ca44c0bb229df817a6b21fc0853b454f7307ae195e4184281f0f6e4d2bfc207b2845fe95e358488c90de70f92
@@ -3,7 +3,6 @@ services:
3
3
  - mysql
4
4
  - postgresql
5
5
  rvm:
6
- - 2.4
7
6
  - 2.5
8
7
  - 2.6
9
8
  - 2.7
@@ -11,11 +10,7 @@ gemfile:
11
10
  - gemfiles/rails_5.1.gemfile
12
11
  - gemfiles/rails_5.2.gemfile
13
12
  - gemfiles/rails_6.0.gemfile
14
- jobs:
15
- exclude:
16
- - rvm: 2.4
17
- gemfile: gemfiles/rails_6.0.gemfile
18
13
  before_install:
19
- - gem install bundler -v 1.17.2
20
14
  - bin/setup
21
-
15
+ cache:
16
+ bundler: true
data/Gemfile CHANGED
@@ -1,12 +1,13 @@
1
1
  source "https://rubygems.org"
2
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
3
  gemspec
7
4
 
8
- gem "byebug"
5
+ gem "database_cleaner-core", git: "https://github.com/DatabaseCleaner/database_cleaner"
9
6
 
10
7
  gem "rails", "~>5.2"
8
+ gem "byebug"
11
9
 
12
- gem "database_cleaner-core", branch: "master", github: "DatabaseCleaner/database_cleaner"
10
+ group :test do
11
+ gem "simplecov", require: false
12
+ gem "codecov", require: false
13
+ end
data/README.md CHANGED
@@ -2,11 +2,14 @@
2
2
 
3
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
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
+ [![codecov](https://codecov.io/gh/DatabaseCleaner/database_cleaner-active_record/branch/master/graph/badge.svg)](https://codecov.io/gh/DatabaseCleaner/database_cleaner-active_record)
5
6
 
6
7
  Clean your ActiveRecord databases with Database Cleaner.
7
8
 
8
9
  See https://github.com/DatabaseCleaner/database_cleaner for more information.
9
10
 
11
+ For support or to discuss development please use the [Google Group](https://groups.google.com/group/database_cleaner).
12
+
10
13
  ## Installation
11
14
 
12
15
  ```ruby
@@ -18,26 +21,11 @@ end
18
21
 
19
22
  ## Supported Strategies
20
23
 
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)
24
+ Three strategies are supported:
39
25
 
40
- For support or to discuss development please use the [Google Group](https://groups.google.com/group/database_cleaner).
26
+ * Transaction (default)
27
+ * Truncation
28
+ * Deletion
41
29
 
42
30
  ## What strategy is fastest?
43
31
 
@@ -53,36 +41,41 @@ So what is fastest out of `:deletion` and `:truncation`? Well, it depends on you
53
41
 
54
42
  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
43
 
56
- If you are using ActiveRecord then take a look at the [additional options](#additional-activerecord-options-for-truncation) available for `:truncation`.
44
+ ## Strategy configuration options
57
45
 
58
- ## Configuration options
46
+ The transaction strategy accepts no options.
59
47
 
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>
48
+ The truncation and deletion strategies may accept the following options:
74
49
 
75
- ### Additional ActiveRecord options for Truncation
50
+ * `:only` and `:except` may take a list of table names:
76
51
 
77
- The following options are available for ActiveRecord's `:truncation` strategy _only_ for MySQL and Postgres.
52
+ ```ruby
53
+ # Only truncate the "users" table.
54
+ DatabaseCleaner[:active_record].strategy = :truncation, { only: ["users"] }
78
55
 
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`.
56
+ # Delete all tables except the "users" table.
57
+ DatabaseCleaner[:active_record].strategy = :deletion, { except: ["users"] }
58
+ ```
81
59
 
82
- The following option is available for ActiveRecord's `:truncation` and `:deletion` strategy for any DB.
60
+ * `:pre_count` - When set to `true` this will check each table for existing rows before truncating or deleting it. This can speed up test suites when many of the tables are never populated. Defaults to `false`. (Also, see the section on [What strategy is fastest?](#what-strategy-is-fastest))
83
61
 
84
62
  * `: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
63
 
64
+ ## Adapter configuration options
65
+
66
+ `#db` defaults to the default ActiveRecord database, but can be specified manually in a few ways:
67
+
68
+ ```ruby
69
+ # ActiveRecord connection key
70
+ DatabaseCleaner[:active_record].db = :logs
71
+
72
+ # Back to default:
73
+ DatabaseCleaner[:active_record].db = :default
74
+
75
+ # Multiple databases can be specified:
76
+ DatabaseCleaner[:active_record, connection: :default]
77
+ DatabaseCleaner[:active_record, connection: :logs]
78
+ ```
86
79
 
87
80
  ## Common Errors
88
81
 
@@ -92,11 +85,11 @@ If you are using Postgres and have foreign key constraints, the truncation strat
92
85
 
93
86
  To silence these warnings set the following log level in your `postgresql.conf` file:
94
87
 
95
- ```ruby
88
+ ```
96
89
  client_min_messages = warning
97
90
  ```
98
91
 
99
- For ActiveRecord, you add the following parameter in your database.yml file:
92
+ You can also add this parameter to your database.yml file:
100
93
 
101
94
  <pre>
102
95
  test:
@@ -1,7 +1,4 @@
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"
1
+ require_relative "./lib/database_cleaner/active_record/version"
5
2
 
6
3
  Gem::Specification.new do |spec|
7
4
  spec.name = "database_cleaner-active_record"
@@ -21,7 +18,7 @@ Gem::Specification.new do |spec|
21
18
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
19
  spec.require_paths = ["lib"]
23
20
 
24
- spec.add_dependency "database_cleaner-core", "2.0.0.beta"
21
+ spec.add_dependency "database_cleaner-core", "~>2.0.0.beta2"
25
22
  spec.add_dependency "activerecord"
26
23
 
27
24
  spec.add_development_dependency "bundler"
@@ -2,8 +2,13 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "byebug"
5
+ gem "database_cleaner-core", git: "https://github.com/DatabaseCleaner/database_cleaner"
6
6
  gem "rails", "~>5.1.0"
7
- gem "database_cleaner-core", branch: "master", git: "https://github.com/DatabaseCleaner/database_cleaner"
7
+ gem "byebug"
8
+
9
+ group :test do
10
+ gem "simplecov", require: false
11
+ gem "codecov", require: false
12
+ end
8
13
 
9
14
  gemspec path: "../"
@@ -2,8 +2,13 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "byebug"
5
+ gem "database_cleaner-core", git: "https://github.com/DatabaseCleaner/database_cleaner"
6
6
  gem "rails", "~>5.2.0"
7
- gem "database_cleaner-core", branch: "master", git: "https://github.com/DatabaseCleaner/database_cleaner"
7
+ gem "byebug"
8
+
9
+ group :test do
10
+ gem "simplecov", require: false
11
+ gem "codecov", require: false
12
+ end
8
13
 
9
14
  gemspec path: "../"
@@ -2,8 +2,13 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "byebug"
5
+ gem "database_cleaner-core", git: "https://github.com/DatabaseCleaner/database_cleaner"
6
6
  gem "rails", "~>6.0.0"
7
- gem "database_cleaner-core", branch: "master", git: "https://github.com/DatabaseCleaner/database_cleaner"
7
+ gem "byebug"
8
+
9
+ group :test do
10
+ gem "simplecov", require: false
11
+ gem "codecov", require: false
12
+ end
8
13
 
9
14
  gemspec path: "../"
@@ -1,7 +1,7 @@
1
1
  require 'database_cleaner/active_record/version'
2
2
  require 'database_cleaner/core'
3
- require 'database_cleaner/active_record/deletion'
4
3
  require 'database_cleaner/active_record/transaction'
5
4
  require 'database_cleaner/active_record/truncation'
5
+ require 'database_cleaner/active_record/deletion'
6
6
 
7
7
  DatabaseCleaner[:active_record].strategy = :transaction
@@ -1,18 +1,10 @@
1
1
  require 'active_record'
2
- require 'database_cleaner/generic/base'
2
+ require 'database_cleaner/strategy'
3
3
  require 'erb'
4
4
  require 'yaml'
5
5
 
6
6
  module DatabaseCleaner
7
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
8
  def self.config_file_location=(path)
17
9
  @config_file_location = path
18
10
  end
@@ -21,64 +13,53 @@ module DatabaseCleaner
21
13
  @config_file_location ||= "#{Dir.pwd}/config/database.yml"
22
14
  end
23
15
 
24
- module Base
25
- include ::DatabaseCleaner::Generic::Base
26
-
27
- attr_accessor :connection_hash
28
-
29
- def db=(desired_db)
30
- @db = desired_db
31
- load_config
16
+ class Base < DatabaseCleaner::Strategy
17
+ def self.migration_table_name
18
+ ::ActiveRecord::SchemaMigration.table_name
32
19
  end
33
20
 
34
- def db
35
- @db ||= super
21
+ def self.exclusion_condition(column_name)
22
+ <<~SQL
23
+ #{column_name} <> '#{DatabaseCleaner::ActiveRecord::Base.migration_table_name}'
24
+ AND #{column_name} <> '#{::ActiveRecord::Base.internal_metadata_table_name}'
25
+ SQL
36
26
  end
37
27
 
38
- def load_config
39
- if self.db != :default && self.db.is_a?(Symbol) && File.file?(ActiveRecord.config_file_location)
40
- connection_details = YAML::load(ERB.new(IO.read(ActiveRecord.config_file_location)).result)
41
- @connection_hash = valid_config(connection_details)[self.db.to_s]
42
- end
28
+ def db=(*)
29
+ super
30
+ load_config
43
31
  end
44
32
 
45
- def valid_config(connection_file)
46
- if !::ActiveRecord::Base.configurations.nil? && !::ActiveRecord::Base.configurations.empty?
47
- if connection_file != ::ActiveRecord::Base.configurations
48
- return ::ActiveRecord::Base.configurations
49
- end
50
- end
51
- connection_file
52
- end
33
+ attr_accessor :connection_hash
53
34
 
54
35
  def connection_class
55
36
  @connection_class ||= if db && !db.is_a?(Symbol)
56
37
  db
57
38
  elsif connection_hash
58
- lookup_from_connection_pool || establish_connection
39
+ (lookup_from_connection_pool rescue nil) || establish_connection
59
40
  else
60
41
  ::ActiveRecord::Base
61
42
  end
62
43
  end
63
44
 
64
- def self.migration_table_name
65
- if ::ActiveRecord::VERSION::MAJOR < 5
66
- ::ActiveRecord::Migrator.schema_migrations_table_name
67
- else
68
- ::ActiveRecord::SchemaMigration.table_name
45
+ private
46
+
47
+ def load_config
48
+ if self.db != :default && self.db.is_a?(Symbol) && File.file?(DatabaseCleaner::ActiveRecord.config_file_location)
49
+ connection_details = YAML::load(ERB.new(IO.read(DatabaseCleaner::ActiveRecord.config_file_location)).result)
50
+ @connection_hash = valid_config(connection_details)[self.db.to_s]
69
51
  end
70
52
  end
71
53
 
72
- def self.exclusion_condition(column_name)
73
- result = " #{column_name} <> '#{::DatabaseCleaner::ActiveRecord::Base.migration_table_name}' "
74
- if ::ActiveRecord::VERSION::MAJOR >= 5
75
- result += " AND #{column_name} <> '#{::ActiveRecord::Base.internal_metadata_table_name}' "
54
+ def valid_config(connection_file)
55
+ if !::ActiveRecord::Base.configurations.nil? && !::ActiveRecord::Base.configurations.empty?
56
+ if connection_file != ::ActiveRecord::Base.configurations
57
+ return ::ActiveRecord::Base.configurations
58
+ end
76
59
  end
77
- result
60
+ connection_file
78
61
  end
79
62
 
80
- private
81
-
82
63
  def lookup_from_connection_pool
83
64
  if ::ActiveRecord::Base.respond_to?(:descendants)
84
65
  database_name = connection_hash["database"] || connection_hash[:database]
@@ -1,107 +1,70 @@
1
1
  require 'active_record'
2
- require 'active_record/connection_adapters/abstract_adapter'
3
- require "database_cleaner/generic/truncation"
4
2
  require 'database_cleaner/active_record/truncation'
5
3
 
6
4
  module DatabaseCleaner
7
- module ConnectionAdapters
8
- module AbstractDeleteAdapter
9
- def delete_table(table_name)
10
- raise NotImplementedError
5
+ module ActiveRecord
6
+ class Deletion < Truncation
7
+ def clean
8
+ connection.disable_referential_integrity do
9
+ if pre_count? && connection.respond_to?(:pre_count_tables)
10
+ delete_tables(connection, connection.pre_count_tables(tables_to_truncate(connection)))
11
+ else
12
+ delete_tables(connection, tables_to_truncate(connection))
13
+ end
14
+ end
11
15
  end
12
- end
13
16
 
14
- module GenericDeleteAdapter
15
- def delete_table(table_name)
16
- execute("DELETE FROM #{quote_table_name(table_name)};")
17
- end
18
- end
17
+ private
19
18
 
20
- module OracleDeleteAdapter
21
- def delete_table(table_name)
22
- execute("DELETE FROM #{quote_table_name(table_name)}")
19
+ def delete_tables(connection, table_names)
20
+ table_names.each do |table_name|
21
+ delete_table(connection, table_name)
22
+ end
23
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
24
 
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
25
+ def delete_table connection, table_name
26
+ connection.execute("DELETE FROM #{connection.quote_table_name(table_name)}")
51
27
  end
52
- end
53
28
 
54
- def tables_with_new_rows(connection)
55
- stats = table_stats_query(connection)
56
- if stats != ''
57
- connection.select_values(stats)
58
- else
59
- []
29
+ def tables_to_truncate(connection)
30
+ if information_schema_exists?(connection)
31
+ @except += connection.database_cleaner_view_cache + migration_storage_names
32
+ (@only.any? ? @only : tables_with_new_rows(connection)) - @except
33
+ else
34
+ super
35
+ end
60
36
  end
61
- end
62
37
 
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
38
+ def tables_with_new_rows(connection)
39
+ stats = table_stats_query(connection)
40
+ if stats != ''
41
+ connection.select_values(stats)
42
+ else
43
+ []
44
+ end
45
+ end
68
46
 
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)"
47
+ def table_stats_query(connection)
48
+ @table_stats_query ||= build_table_stats_query(connection)
49
+ ensure
50
+ @table_stats_query = nil unless @cache_tables
78
51
  end
79
- queries.join(' UNION ALL ')
80
- end
81
52
 
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
53
+ def build_table_stats_query(connection)
54
+ tables = connection.select_values(<<-SQL)
55
+ SELECT table_name
56
+ FROM information_schema.tables
57
+ WHERE table_schema = database()
58
+ AND #{self.class.exclusion_condition('table_name')};
59
+ SQL
60
+ queries = tables.map do |table|
61
+ "(SELECT #{connection.quote(table)} FROM #{connection.quote_table_name(table)} LIMIT 1)"
90
62
  end
91
- end
92
- end
93
-
94
- class Deletion < Truncation
95
- if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
96
- include SelectiveTruncation
97
- end
63
+ queries.join(' UNION ALL ')
64
+ end
98
65
 
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
66
+ def information_schema_exists? connection
67
+ connection.adapter_name == "Mysql2"
105
68
  end
106
69
  end
107
70
  end
@@ -1,60 +1,22 @@
1
1
  require 'database_cleaner/active_record/base'
2
- require 'database_cleaner/generic/transaction'
3
2
 
4
- module DatabaseCleaner::ActiveRecord
5
- class Transaction
6
- include ::DatabaseCleaner::ActiveRecord::Base
7
- include ::DatabaseCleaner::Generic::Transaction
3
+ module DatabaseCleaner
4
+ module ActiveRecord
5
+ class Transaction < Base
6
+ def start
7
+ # Hack to make sure that the connection is properly set up before cleaning
8
+ connection_class.connection.transaction {}
8
9
 
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
10
+ connection_class.connection.begin_transaction joinable: false
25
11
  end
26
- end
27
-
28
12
 
29
- def clean
30
- connection_class.connection_pool.connections.each do |connection|
31
- next unless connection.open_transactions > 0
32
13
 
33
- if connection.respond_to?(:rollback_transaction)
14
+ def clean
15
+ connection_class.connection_pool.connections.each do |connection|
16
+ next unless connection.open_transactions > 0
34
17
  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
18
  end
51
19
  end
52
20
  end
53
-
54
- private
55
-
56
- def connection_maintains_transaction_count?
57
- ActiveRecord::VERSION::MAJOR < 4
58
- end
59
21
  end
60
22
  end
@@ -1,284 +1,249 @@
1
+ require "delegate"
1
2
  require 'active_record/base'
2
3
  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 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
4
 
18
5
  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
- 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)
6
+ module ActiveRecord
7
+ class Truncation < Base
8
+ def initialize(opts={})
9
+ if !opts.empty? && !(opts.keys - [:only, :except, :pre_count, :cache_tables]).empty?
10
+ raise ArgumentError, "The only valid options are :only, :except, :pre_count, and :cache_tables. You specified #{opts.keys.join(',')}."
44
11
  end
45
- end
46
- end
47
12
 
48
- module AbstractMysqlAdapter
49
- def truncate_table(table_name)
50
- execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
51
- end
13
+ @only = Array(opts[:only]).dup
14
+ @except = Array(opts[:except]).dup
52
15
 
53
- def truncate_tables(tables)
54
- tables.each { |t| truncate_table(t) }
16
+ @pre_count = opts[:pre_count]
17
+ @cache_tables = opts.has_key?(:cache_tables) ? !!opts[:cache_tables] : true
55
18
  end
56
19
 
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))
20
+ def clean
21
+ connection.disable_referential_integrity do
22
+ if pre_count? && connection.respond_to?(:pre_count_truncate_tables)
23
+ connection.pre_count_truncate_tables(tables_to_truncate(connection))
24
+ else
25
+ connection.truncate_tables(tables_to_truncate(connection))
26
+ end
27
+ end
60
28
  end
61
29
 
62
30
  private
63
31
 
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
32
+ def connection
33
+ @connection ||= ConnectionWrapper.new(connection_class.connection)
68
34
  end
69
35
 
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
36
+ def tables_to_truncate(connection)
37
+ if @only.none?
38
+ all_tables = cache_tables? ? connection.database_cleaner_table_cache : connection.database_tables
39
+ @only = all_tables.map { |table| table.split(".").last }
40
+ end
41
+ @except += connection.database_cleaner_view_cache + migration_storage_names
42
+ @only - @except
77
43
  end
78
44
 
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
45
+ def migration_storage_names
46
+ [
47
+ DatabaseCleaner::ActiveRecord::Base.migration_table_name,
48
+ ::ActiveRecord::Base.internal_metadata_table_name,
49
+ ]
85
50
  end
86
51
 
87
- def has_rows?(table)
88
- row_count(table) > 0
52
+ def cache_tables?
53
+ !!@cache_tables
89
54
  end
90
- end
91
55
 
92
- module IBM_DBAdapter
93
- def truncate_table(table_name)
94
- execute("TRUNCATE #{quote_table_name(table_name)} IMMEDIATE")
56
+ def pre_count?
57
+ @pre_count == true
95
58
  end
96
59
  end
97
60
 
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}';")
61
+ class ConnectionWrapper < SimpleDelegator
62
+ def initialize(connection)
63
+ extend AbstractAdapter
64
+ case connection.adapter_name
65
+ when "Mysql2"
66
+ extend AbstractMysqlAdapter
67
+ when "SQLite"
68
+ extend AbstractMysqlAdapter
69
+ extend SQLiteAdapter
70
+ when "PostgreSQL"
71
+ extend AbstractMysqlAdapter
72
+ extend PostgreSQLAdapter
103
73
  end
74
+ super(connection)
104
75
  end
105
- alias truncate_table delete_table
106
76
 
107
- def truncate_tables(tables)
108
- tables.each { |t| truncate_table(t) }
109
- end
77
+ module AbstractAdapter
78
+ # used to be called views but that can clash with gems like schema_plus
79
+ # this gem is not meant to be exposing such an extra interface any way
80
+ def database_cleaner_view_cache
81
+ @views ||= select_values("select table_name from information_schema.views where table_schema = '#{current_database}'") rescue []
82
+ end
110
83
 
111
- private
84
+ def database_cleaner_table_cache
85
+ # the adapters don't do caching (#130) but we make the assumption that the list stays the same in tests
86
+ @database_cleaner_tables ||= database_tables
87
+ end
112
88
 
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
89
+ def database_tables
90
+ tables
91
+ end
118
92
 
119
- module TruncateOrDelete
120
- def truncate_table(table_name)
121
- begin
122
- execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
93
+ def truncate_table(table_name)
94
+ execute("TRUNCATE TABLE #{quote_table_name(table_name)}")
123
95
  rescue ::ActiveRecord::StatementInvalid
124
- execute("DELETE FROM #{quote_table_name(table_name)};")
96
+ execute("DELETE FROM #{quote_table_name(table_name)}")
125
97
  end
126
- end
127
- end
128
98
 
129
- module OracleAdapter
130
- def truncate_table(table_name)
131
- execute("TRUNCATE TABLE #{quote_table_name(table_name)}")
99
+ def truncate_tables(tables)
100
+ tables.each { |t| truncate_table(t) }
101
+ end
132
102
  end
133
- end
134
103
 
135
- module PostgreSQLAdapter
136
- def db_version
137
- @db_version ||= postgresql_version
138
- end
104
+ module AbstractMysqlAdapter
105
+ def pre_count_truncate_tables(tables)
106
+ truncate_tables(pre_count_tables(tables))
107
+ end
139
108
 
140
- def cascade
141
- @cascade ||= db_version >= 80200 ? 'CASCADE' : ''
142
- end
109
+ def pre_count_tables(tables)
110
+ tables.select { |table| has_been_used?(table) }
111
+ end
143
112
 
144
- def restart_identity
145
- @restart_identity ||= db_version >= 80400 ? 'RESTART IDENTITY' : ''
146
- end
113
+ private
147
114
 
148
- def database_tables
149
- tables_with_schema
150
- end
115
+ def row_count(table)
116
+ select_value("SELECT EXISTS (SELECT 1 FROM #{quote_table_name(table)} LIMIT 1)")
117
+ end
151
118
 
152
- def truncate_table(table_name)
153
- truncate_tables([table_name])
154
- end
119
+ def auto_increment_value(table)
120
+ select_value(<<-SQL).to_i
121
+ SELECT auto_increment
122
+ FROM information_schema.tables
123
+ WHERE table_name = '#{table}'
124
+ AND table_schema = database()
125
+ SQL
126
+ end
155
127
 
156
- def truncate_tables(table_names)
157
- return if table_names.nil? || table_names.empty?
158
- execute("TRUNCATE TABLE #{table_names.map{|name| quote_table_name(name)}.join(', ')} #{restart_identity} #{cascade};")
159
- end
128
+ # This method tells us if the given table has been inserted into since its
129
+ # last truncation. Note that the table might have been populated, which
130
+ # increased the auto-increment counter, but then cleaned again such that
131
+ # it appears empty now.
132
+ def has_been_used?(table)
133
+ has_rows?(table) || auto_increment_value(table) > 1
134
+ end
160
135
 
161
- def pre_count_truncate_tables(tables, options = {:reset_ids => true})
162
- filter = options[:reset_ids] ? method(:has_been_used?) : method(:has_rows?)
163
- truncate_tables(tables.select(&filter))
136
+ def has_rows?(table)
137
+ row_count(table) > 0
138
+ end
164
139
  end
165
140
 
166
- def database_cleaner_table_cache
167
- # AR returns a list of tables without schema but then returns a
168
- # migrations table with the schema. There are other problems, too,
169
- # with using the base list. If a table exists in multiple schemas
170
- # within the search path, truncation without the schema name could
171
- # result in confusing, if not unexpected results.
172
- @database_cleaner_tables ||= tables_with_schema
173
- end
141
+ module SQLiteAdapter
142
+ def truncate_table(table_name)
143
+ super
144
+ if uses_sequence?
145
+ execute("DELETE FROM sqlite_sequence where name = '#{table_name}';")
146
+ end
147
+ end
174
148
 
175
- private
149
+ def truncate_tables(tables)
150
+ tables.each { |t| truncate_table(t) }
151
+ end
176
152
 
177
- # Returns a boolean indicating if the given table has an auto-inc number higher than 0.
178
- # Note, this is different than an empty table since an table may populated, the index increased,
179
- # but then the table is cleaned. In other words, this function tells us if the given table
180
- # was ever inserted into.
181
- def has_been_used?(table)
182
- return has_rows?(table) unless has_sequence?(table)
153
+ def pre_count_truncate_tables(tables)
154
+ truncate_tables(pre_count_tables(tables))
155
+ end
183
156
 
184
- cur_val = select_value("SELECT currval('#{table}_id_seq');").to_i rescue 0
185
- cur_val > 0
186
- end
157
+ def pre_count_tables(tables)
158
+ sequences = fetch_sequences
159
+ tables.select { |table| has_been_used?(table, sequences) }
160
+ end
187
161
 
188
- def has_sequence?(table)
189
- select_value("SELECT true FROM pg_class WHERE relname = '#{table}_id_seq';")
190
- end
162
+ private
191
163
 
192
- def has_rows?(table)
193
- select_value("SELECT true FROM #{table} LIMIT 1;")
194
- end
164
+ def fetch_sequences
165
+ return {} unless uses_sequence?
166
+ results = select_all("SELECT * FROM sqlite_sequence")
167
+ Hash[results.rows]
168
+ end
195
169
 
196
- def tables_with_schema
197
- rows = select_rows <<-_SQL
198
- SELECT schemaname || '.' || tablename
199
- FROM pg_tables
200
- WHERE
201
- tablename !~ '_prt_' AND
202
- #{::DatabaseCleaner::ActiveRecord::Base.exclusion_condition('tablename')} AND
203
- schemaname = ANY (current_schemas(false))
204
- _SQL
205
- rows.collect { |result| result.first }
206
- end
207
- end
208
- end
209
- end
170
+ def has_been_used?(table, sequences)
171
+ count = sequences.fetch(table) { row_count(table) }
172
+ count > 0
173
+ end
210
174
 
211
- module ActiveRecord
212
- module ConnectionAdapters
213
- #Apply adapter decoraters where applicable (adapter should be loaded)
214
- AbstractAdapter.class_eval { include DatabaseCleaner::ConnectionAdapters::AbstractAdapter }
175
+ def row_count(table)
176
+ select_value("SELECT EXISTS (SELECT 1 FROM #{quote_table_name(table)} LIMIT 1)")
177
+ end
215
178
 
216
- if defined?(JdbcAdapter)
217
- if defined?(OracleJdbcConnection)
218
- JdbcAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::OracleAdapter }
219
- else
220
- JdbcAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::TruncateOrDelete }
179
+ # Returns a boolean indicating if the SQLite database is using the sqlite_sequence table.
180
+ def uses_sequence?
181
+ select_value("SELECT name FROM sqlite_master WHERE type='table' AND name='sqlite_sequence';")
182
+ end
221
183
  end
222
- end
223
- AbstractMysqlAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::AbstractMysqlAdapter } if defined?(AbstractMysqlAdapter)
224
- Mysql2Adapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::AbstractMysqlAdapter } if defined?(Mysql2Adapter)
225
- MysqlAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::AbstractMysqlAdapter } if defined?(MysqlAdapter)
226
- SQLiteAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::SQLiteAdapter } if defined?(SQLiteAdapter)
227
- SQLite3Adapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::SQLiteAdapter } if defined?(SQLite3Adapter)
228
- PostgreSQLAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::PostgreSQLAdapter } if defined?(PostgreSQLAdapter)
229
- IBM_DBAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::IBM_DBAdapter } if defined?(IBM_DBAdapter)
230
- SQLServerAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::TruncateOrDelete } if defined?(SQLServerAdapter)
231
- OracleEnhancedAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::OracleAdapter } if defined?(OracleEnhancedAdapter)
232
- end
233
- end
234
184
 
235
- module DatabaseCleaner::ActiveRecord
236
- class Truncation
237
- include ::DatabaseCleaner::ActiveRecord::Base
238
- include ::DatabaseCleaner::Generic::Truncation
185
+ module PostgreSQLAdapter
186
+ def database_tables
187
+ tables_with_schema
188
+ end
239
189
 
240
- def clean
241
- connection = connection_class.connection
242
- connection.disable_referential_integrity do
243
- if pre_count? && connection.respond_to?(:pre_count_truncate_tables)
244
- connection.pre_count_truncate_tables(tables_to_truncate(connection), {:reset_ids => reset_ids?})
245
- else
246
- connection.truncate_tables(tables_to_truncate(connection))
190
+ def truncate_tables(table_names)
191
+ return if table_names.nil? || table_names.empty?
192
+ execute("TRUNCATE TABLE #{table_names.map{|name| quote_table_name(name)}.join(', ')} RESTART IDENTITY CASCADE;")
247
193
  end
248
- end
249
- end
250
194
 
251
- private
195
+ def pre_count_truncate_tables(tables)
196
+ truncate_tables(pre_count_tables(tables))
197
+ end
252
198
 
253
- def tables_to_truncate(connection)
254
- tables_in_db = cache_tables? ? connection.database_cleaner_table_cache : connection.database_tables
255
- to_reject = (@tables_to_exclude + connection.database_cleaner_view_cache)
256
- (@only || tables_in_db).reject do |table|
257
- if ( m = table.match(/([^.]+)$/) )
258
- to_reject.include?(m[1])
259
- else
260
- false
199
+ def pre_count_tables(tables)
200
+ tables.select { |table| has_been_used?(table) }
261
201
  end
262
- end
263
- end
264
202
 
265
- # overwritten
266
- def migration_storage_names
267
- result = [::DatabaseCleaner::ActiveRecord::Base.migration_table_name]
268
- result << ::ActiveRecord::Base.internal_metadata_table_name if ::ActiveRecord::VERSION::MAJOR >= 5
269
- result
270
- end
203
+ def database_cleaner_table_cache
204
+ # AR returns a list of tables without schema but then returns a
205
+ # migrations table with the schema. There are other problems, too,
206
+ # with using the base list. If a table exists in multiple schemas
207
+ # within the search path, truncation without the schema name could
208
+ # result in confusing, if not unexpected results.
209
+ @database_cleaner_tables ||= tables_with_schema
210
+ end
271
211
 
272
- def cache_tables?
273
- !!@cache_tables
274
- end
212
+ private
275
213
 
276
- def pre_count?
277
- @pre_count == true
278
- end
214
+ # Returns a boolean indicating if the given table has an auto-inc number higher than 0.
215
+ # Note, this is different than an empty table since an table may populated, the index increased,
216
+ # but then the table is cleaned. In other words, this function tells us if the given table
217
+ # was ever inserted into.
218
+ def has_been_used?(table)
219
+ return has_rows?(table) unless has_sequence?(table)
220
+
221
+ cur_val = select_value("SELECT currval('#{table}_id_seq');").to_i rescue 0
222
+ cur_val > 0
223
+ end
224
+
225
+ def has_sequence?(table)
226
+ select_value("SELECT true FROM pg_class WHERE relname = '#{table}_id_seq';")
227
+ end
279
228
 
280
- def reset_ids?
281
- @reset_ids != false
229
+ def has_rows?(table)
230
+ select_value("SELECT true FROM #{table} LIMIT 1;")
231
+ end
232
+
233
+ def tables_with_schema
234
+ rows = select_rows <<-_SQL
235
+ SELECT schemaname || '.' || tablename
236
+ FROM pg_tables
237
+ WHERE
238
+ tablename !~ '_prt_' AND
239
+ #{DatabaseCleaner::ActiveRecord::Base.exclusion_condition('tablename')} AND
240
+ schemaname = ANY (current_schemas(false))
241
+ _SQL
242
+ rows.collect { |result| result.first }
243
+ end
244
+ end
282
245
  end
246
+ private_constant :ConnectionWrapper
283
247
  end
284
248
  end
249
+
@@ -1,5 +1,5 @@
1
1
  module DatabaseCleaner
2
2
  module ActiveRecord
3
- VERSION = "2.0.0.beta"
3
+ VERSION = "2.0.0.beta2"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: database_cleaner-active_record
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.beta
4
+ version: 2.0.0.beta2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ernesto Tagwerker
@@ -9,22 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2020-04-05 00:00:00.000000000 Z
12
+ date: 2020-05-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: database_cleaner-core
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - '='
18
+ - - "~>"
19
19
  - !ruby/object:Gem::Version
20
- version: 2.0.0.beta
20
+ version: 2.0.0.beta2
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
- - - '='
25
+ - - "~>"
26
26
  - !ruby/object:Gem::Version
27
- version: 2.0.0.beta
27
+ version: 2.0.0.beta2
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: activerecord
30
30
  requirement: !ruby/object:Gem::Requirement