database_cleaner-active_record 2.0.0.beta → 2.0.0.beta2

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