mirror of
https://github.com/DatabaseCleaner/database_cleaner
synced 2023-03-27 23:22:03 -04:00
initial database_cleaner-active_record gem extraction.
This commit is contained in:
parent
4c2408ffdb
commit
e65c63c939
31 changed files with 1186 additions and 15 deletions
4
Gemfile
4
Gemfile
|
@ -1 +1,5 @@
|
||||||
gemspec
|
gemspec
|
||||||
|
|
||||||
|
path "./adapters" do
|
||||||
|
gem "database_cleaner-active_record"
|
||||||
|
end
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
PATH
|
||||||
|
remote: adapters
|
||||||
|
specs:
|
||||||
|
database_cleaner-active_record (0.1.0)
|
||||||
|
|
||||||
PATH
|
PATH
|
||||||
remote: .
|
remote: .
|
||||||
specs:
|
specs:
|
||||||
|
@ -247,6 +252,7 @@ DEPENDENCIES
|
||||||
couch_potato
|
couch_potato
|
||||||
cucumber
|
cucumber
|
||||||
database_cleaner!
|
database_cleaner!
|
||||||
|
database_cleaner-active_record!
|
||||||
datamapper
|
datamapper
|
||||||
dm-migrations
|
dm-migrations
|
||||||
dm-sqlite-adapter
|
dm-sqlite-adapter
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
---
|
---
|
||||||
:major: 1
|
:major: 1
|
||||||
:minor: 7
|
:minor: 8
|
||||||
:patch: 0
|
:patch: 0
|
||||||
|
|
11
adapters/database_cleaner-active_record/.gitignore
vendored
Normal file
11
adapters/database_cleaner-active_record/.gitignore
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
/.bundle/
|
||||||
|
/.yardoc
|
||||||
|
/_yardoc/
|
||||||
|
/coverage/
|
||||||
|
/doc/
|
||||||
|
/pkg/
|
||||||
|
/spec/reports/
|
||||||
|
/tmp/
|
||||||
|
|
||||||
|
# rspec failure tracking
|
||||||
|
.rspec_status
|
3
adapters/database_cleaner-active_record/.rspec
Normal file
3
adapters/database_cleaner-active_record/.rspec
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
--format documentation
|
||||||
|
--color
|
||||||
|
--require spec_helper
|
5
adapters/database_cleaner-active_record/.travis.yml
Normal file
5
adapters/database_cleaner-active_record/.travis.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
sudo: false
|
||||||
|
language: ruby
|
||||||
|
rvm:
|
||||||
|
- 2.2.9
|
||||||
|
before_install: gem install bundler -v 1.16.1
|
8
adapters/database_cleaner-active_record/Gemfile
Normal file
8
adapters/database_cleaner-active_record/Gemfile
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
||||||
|
|
||||||
|
# Specify your gem's dependencies in database_cleaner-active_record.gemspec
|
||||||
|
gemspec
|
||||||
|
|
||||||
|
gem "database_cleaner", path: "../.."
|
78
adapters/database_cleaner-active_record/Gemfile.lock
Normal file
78
adapters/database_cleaner-active_record/Gemfile.lock
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
PATH
|
||||||
|
remote: ../..
|
||||||
|
specs:
|
||||||
|
database_cleaner (1.8.0)
|
||||||
|
|
||||||
|
PATH
|
||||||
|
remote: .
|
||||||
|
specs:
|
||||||
|
database_cleaner-active_record (0.1.0)
|
||||||
|
activerecord
|
||||||
|
database_cleaner (~> 1.8.0)
|
||||||
|
|
||||||
|
GEM
|
||||||
|
remote: https://rubygems.org/
|
||||||
|
specs:
|
||||||
|
activemodel (3.0.0)
|
||||||
|
activesupport (= 3.0.0)
|
||||||
|
builder (~> 2.1.2)
|
||||||
|
i18n (~> 0.4.1)
|
||||||
|
activerecord (3.0.0)
|
||||||
|
activemodel (= 3.0.0)
|
||||||
|
activesupport (= 3.0.0)
|
||||||
|
arel (~> 1.0.0)
|
||||||
|
tzinfo (~> 0.3.23)
|
||||||
|
activerecord-mysql2-adapter (0.0.3)
|
||||||
|
mysql2
|
||||||
|
activesupport (3.0.0)
|
||||||
|
arel (1.0.1)
|
||||||
|
activesupport (~> 3.0.0)
|
||||||
|
builder (2.1.2)
|
||||||
|
cucumber (1.2.1)
|
||||||
|
builder (>= 2.1.2)
|
||||||
|
diff-lcs (>= 1.1.3)
|
||||||
|
gherkin (~> 2.11.0)
|
||||||
|
json (>= 1.4.6)
|
||||||
|
diff-lcs (1.3)
|
||||||
|
gherkin (2.11.6)
|
||||||
|
json (>= 1.7.6)
|
||||||
|
i18n (0.4.2)
|
||||||
|
json (1.8.6)
|
||||||
|
mysql (2.9.1)
|
||||||
|
mysql2 (0.3.18)
|
||||||
|
pg (0.18.2)
|
||||||
|
rake (10.4.2)
|
||||||
|
rspec (3.7.0)
|
||||||
|
rspec-core (~> 3.7.0)
|
||||||
|
rspec-expectations (~> 3.7.0)
|
||||||
|
rspec-mocks (~> 3.7.0)
|
||||||
|
rspec-core (3.7.1)
|
||||||
|
rspec-support (~> 3.7.0)
|
||||||
|
rspec-expectations (3.7.0)
|
||||||
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
|
rspec-support (~> 3.7.0)
|
||||||
|
rspec-mocks (3.7.0)
|
||||||
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
|
rspec-support (~> 3.7.0)
|
||||||
|
rspec-support (3.7.1)
|
||||||
|
sqlite3 (1.3.10)
|
||||||
|
tzinfo (0.3.48)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
ruby
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
activerecord-mysql2-adapter
|
||||||
|
bundler (~> 1.16)
|
||||||
|
cucumber
|
||||||
|
database_cleaner!
|
||||||
|
database_cleaner-active_record!
|
||||||
|
mysql (~> 2.9.1)
|
||||||
|
mysql2
|
||||||
|
pg
|
||||||
|
rake (~> 10.0)
|
||||||
|
rspec (~> 3.0)
|
||||||
|
sqlite3
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
1.16.1
|
21
adapters/database_cleaner-active_record/LICENSE.txt
Normal file
21
adapters/database_cleaner-active_record/LICENSE.txt
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2009 Ben Mabey
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
339
adapters/database_cleaner-active_record/README.md
Normal file
339
adapters/database_cleaner-active_record/README.md
Normal file
|
@ -0,0 +1,339 @@
|
||||||
|
# Database Cleaner for ActiveRecord
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/DatabaseCleaner/database_cleaner-active_record.svg?branch=master)](https://travis-ci.org/DatabaseCleaner/database_cleaner-active_record)
|
||||||
|
[![Code Climate](https://codeclimate.com/github/DatabaseCleaner/database_cleaner-active_record/badges/gpa.svg)](https://codeclimate.com/github/DatabaseCleaner/database_cleaner-active_record)
|
||||||
|
|
||||||
|
Database Cleaner is a set of strategies for cleaning your database in Ruby.
|
||||||
|
|
||||||
|
The original use case was to ensure a clean state during tests.
|
||||||
|
Each strategy is a small amount of code but is code that is usually needed in any ruby app that is testing with a database.
|
||||||
|
|
||||||
|
## Gem Setup
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# Gemfile
|
||||||
|
group :test do
|
||||||
|
gem 'database_cleaner-active_record'
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Supported Strategies
|
||||||
|
|
||||||
|
Here is an overview of the supported strategies:
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Truncation</th>
|
||||||
|
<th>Transaction</th>
|
||||||
|
<th>Deletion</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td> Yes</td>
|
||||||
|
<td> <b>Yes</b></td>
|
||||||
|
<td> Yes</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
(Default strategy is denoted in bold)
|
||||||
|
|
||||||
|
For support or to discuss development please use the [Google Group](http://groups.google.com/group/database_cleaner).
|
||||||
|
|
||||||
|
## What strategy is fastest?
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
An easier, but slower, solution is to use the `:truncation` or `:deletion` strategy.
|
||||||
|
|
||||||
|
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](http://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886).
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
If you are using ActiveRecord then take a look at the [additional options](#additional-activerecord-options-for-truncation) available for `:truncation`.
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'database_cleaner/active_record'
|
||||||
|
|
||||||
|
DatabaseCleaner.strategy = :truncation
|
||||||
|
|
||||||
|
# then, whenever you need to clean the DB
|
||||||
|
DatabaseCleaner.clean
|
||||||
|
```
|
||||||
|
|
||||||
|
With the `:truncation` strategy you can also pass in options, for example:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
DatabaseCleaner.strategy = :truncation, only: %w[widgets dogs some_other_table]
|
||||||
|
```
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
DatabaseCleaner.strategy = :truncation, except: %w[widgets]
|
||||||
|
```
|
||||||
|
|
||||||
|
(I should point out the truncation strategy will never truncate your schema_migrations table.)
|
||||||
|
|
||||||
|
Some strategies need to be started before tests are run (for example the `:transaction` strategy needs to know to open up a transaction). This can be accomplished by calling `DatabaseCleaner.start` at the beginning of the run, or by running the tests inside a block to `Database.cleaning`. So you would have:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'database_cleaner/active_record'
|
||||||
|
|
||||||
|
DatabaseCleaner.strategy = :transaction
|
||||||
|
|
||||||
|
DatabaseCleaner.start # usually this is called in setup of a test
|
||||||
|
|
||||||
|
dirty_the_db
|
||||||
|
|
||||||
|
DatabaseCleaner.clean # cleanup of the test
|
||||||
|
|
||||||
|
# OR
|
||||||
|
|
||||||
|
DatabaseCleaner.cleaning do
|
||||||
|
dirty_the_db
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
At times you may want to do a single clean with one strategy.
|
||||||
|
|
||||||
|
For example, you may want to start the process by truncating all the tables, but then use the faster transaction strategy the remaining time. To accomplish this you can say:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'database_cleaner/active_record'
|
||||||
|
|
||||||
|
DatabaseCleaner.clean_with :truncation
|
||||||
|
|
||||||
|
DatabaseCleaner.strategy = :transaction
|
||||||
|
|
||||||
|
# then make the DatabaseCleaner.start and DatabaseCleaner.clean calls appropriately
|
||||||
|
```
|
||||||
|
|
||||||
|
### Additional ActiveRecord options for Truncation
|
||||||
|
|
||||||
|
The following options are available for ActiveRecord's `:truncation` strategy _only_ for MySQL and Postgres.
|
||||||
|
|
||||||
|
* `: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))
|
||||||
|
* `: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`.
|
||||||
|
|
||||||
|
The following option is available for ActiveRecord's `:truncation` and `:deletion` strategy for any DB.
|
||||||
|
|
||||||
|
* `: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`.
|
||||||
|
|
||||||
|
|
||||||
|
### RSpec Example
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
RSpec.configure do |config|
|
||||||
|
|
||||||
|
config.before(:suite) do
|
||||||
|
DatabaseCleaner.strategy = :transaction
|
||||||
|
DatabaseCleaner.clean_with(:truncation)
|
||||||
|
end
|
||||||
|
|
||||||
|
config.around(:each) do |example|
|
||||||
|
DatabaseCleaner.cleaning do
|
||||||
|
example.run
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### RSpec with Capybara Example
|
||||||
|
|
||||||
|
You'll typically discover a feature spec is incorrectly using transaction
|
||||||
|
instead of truncation strategy when the data created in the spec is not
|
||||||
|
visible in the app-under-test.
|
||||||
|
|
||||||
|
A frequently occurring example of this is when, after creating a user in a
|
||||||
|
spec, the spec mysteriously fails to login with the user. This happens because
|
||||||
|
the user is created inside of an uncommitted transaction on one database
|
||||||
|
connection, while the login attempt is made using a separate database
|
||||||
|
connection. This separate database connection cannot access the
|
||||||
|
uncommitted user data created over the first database connection due to
|
||||||
|
transaction isolation.
|
||||||
|
|
||||||
|
For feature specs using a Capybara driver for an external
|
||||||
|
JavaScript-capable browser (in practice this is all drivers except
|
||||||
|
`:rack_test`), the Rack app under test and the specs do not share a
|
||||||
|
database connection.
|
||||||
|
|
||||||
|
When a spec and app-under-test do not share a database connection,
|
||||||
|
you'll likely need to use the truncation strategy instead of the
|
||||||
|
transaction strategy.
|
||||||
|
|
||||||
|
See the suggested config below to temporarily enable truncation strategy
|
||||||
|
for affected feature specs only. This config continues to use transaction
|
||||||
|
strategy for all other specs.
|
||||||
|
|
||||||
|
It's also recommended to use `append_after` to ensure `DatabaseCleaner.clean`
|
||||||
|
runs *after* the after-test cleanup `capybara/rspec` installs.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
require 'capybara/rspec'
|
||||||
|
|
||||||
|
#...
|
||||||
|
|
||||||
|
RSpec.configure do |config|
|
||||||
|
|
||||||
|
config.use_transactional_fixtures = false
|
||||||
|
|
||||||
|
config.before(:suite) do
|
||||||
|
if config.use_transactional_fixtures?
|
||||||
|
raise(<<-MSG)
|
||||||
|
Delete line `config.use_transactional_fixtures = true` from rails_helper.rb
|
||||||
|
(or set it to false) to prevent uncommitted transactions being used in
|
||||||
|
JavaScript-dependent specs.
|
||||||
|
|
||||||
|
During testing, the app-under-test that the browser driver connects to
|
||||||
|
uses a different database connection to the database connection used by
|
||||||
|
the spec. The app's database connection would not be able to access
|
||||||
|
uncommitted transaction data setup over the spec's database connection.
|
||||||
|
MSG
|
||||||
|
end
|
||||||
|
DatabaseCleaner.clean_with(:truncation)
|
||||||
|
end
|
||||||
|
|
||||||
|
config.before(:each) do
|
||||||
|
DatabaseCleaner.strategy = :transaction
|
||||||
|
end
|
||||||
|
|
||||||
|
config.before(:each, type: :feature) do
|
||||||
|
# :rack_test driver's Rack app under test shares database connection
|
||||||
|
# with the specs, so continue to use transaction strategy for speed.
|
||||||
|
driver_shares_db_connection_with_specs = Capybara.current_driver == :rack_test
|
||||||
|
|
||||||
|
if !driver_shares_db_connection_with_specs
|
||||||
|
# Driver is probably for an external browser with an app
|
||||||
|
# under test that does *not* share a database connection with the
|
||||||
|
# specs, so use truncation strategy.
|
||||||
|
DatabaseCleaner.strategy = :truncation
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
config.before(:each) do
|
||||||
|
DatabaseCleaner.start
|
||||||
|
end
|
||||||
|
|
||||||
|
config.append_after(:each) do
|
||||||
|
DatabaseCleaner.clean
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Minitest Example
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
DatabaseCleaner.strategy = :transaction
|
||||||
|
|
||||||
|
class Minitest::Spec
|
||||||
|
before :each do
|
||||||
|
DatabaseCleaner.start
|
||||||
|
end
|
||||||
|
|
||||||
|
after :each do
|
||||||
|
DatabaseCleaner.clean
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# with the minitest-around gem, this may be used instead:
|
||||||
|
class Minitest::Spec
|
||||||
|
around do |tests|
|
||||||
|
DatabaseCleaner.cleaning(&tests)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cucumber Example
|
||||||
|
|
||||||
|
If you're using Cucumber with Rails, just use the generator that ships with cucumber-rails, and that will create all the code you need to integrate DatabaseCleaner into your Rails project.
|
||||||
|
|
||||||
|
Otherwise, to add DatabaseCleaner to your project by hand, create a file `features/support/database_cleaner.rb` that looks like this:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
begin
|
||||||
|
require 'database_cleaner/active_record'
|
||||||
|
require 'database_cleaner/cucumber'
|
||||||
|
|
||||||
|
DatabaseCleaner.strategy = :truncation
|
||||||
|
rescue NameError
|
||||||
|
raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it."
|
||||||
|
end
|
||||||
|
|
||||||
|
Around do |scenario, block|
|
||||||
|
DatabaseCleaner.cleaning(&block)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
This should cover the basics of tear down between scenarios and keeping your database clean.
|
||||||
|
|
||||||
|
### Configuration options
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>ORM</th>
|
||||||
|
<th>How to access</th>
|
||||||
|
<th>Notes</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td> Active Record </td>
|
||||||
|
<td> <code>DatabaseCleaner[:active_record]</code></td>
|
||||||
|
<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>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
## Common Errors
|
||||||
|
|
||||||
|
#### DatabaseCleaner is trying to use the wrong ORM
|
||||||
|
|
||||||
|
DatabaseCleaner has an autodetect mechanism where if you do not explicitly define your ORM it will use the first ORM it can detect that is loaded.
|
||||||
|
|
||||||
|
Since ActiveRecord is the most common ORM used that is the first one checked for.
|
||||||
|
|
||||||
|
Sometimes other libraries (e.g. ActiveAdmin) will load other ORMs (e.g. ActiveRecord) even though you are using a different ORM. This will result in DatabaseCleaner trying to use the wrong ORM (e.g. ActiveRecord) unless you explicitly define your ORM like so:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# How to setup your ORM explicitly
|
||||||
|
DatabaseCleaner[:mongoid].strategy = :truncation
|
||||||
|
```
|
||||||
|
|
||||||
|
### STDERR is being flooded when using Postgres
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
To silence these warnings set the following log level in your `postgresql.conf` file:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
client_min_messages = warning
|
||||||
|
```
|
||||||
|
|
||||||
|
For ActiveRecord, you add the following parameter in your database.yml file:
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
test:
|
||||||
|
adapter: postgresql
|
||||||
|
# ...
|
||||||
|
min_messages: WARNING
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
In rare cases DatabaseCleaner will encounter errors that it will log. By default it uses STDOUT set to the ERROR level but you can configure this to use whatever Logger you desire.
|
||||||
|
|
||||||
|
Here's an example of using the `Rails.logger` in `env.rb`:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
DatabaseCleaner.logger = Rails.logger
|
||||||
|
```
|
||||||
|
|
||||||
|
## COPYRIGHT
|
||||||
|
|
||||||
|
See [LICENSE] for details.
|
6
adapters/database_cleaner-active_record/Rakefile
Normal file
6
adapters/database_cleaner-active_record/Rakefile
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
require "bundler/gem_tasks"
|
||||||
|
require "rspec/core/rake_task"
|
||||||
|
|
||||||
|
RSpec::Core::RakeTask.new(:spec)
|
||||||
|
|
||||||
|
task :default => :spec
|
14
adapters/database_cleaner-active_record/bin/console
Executable file
14
adapters/database_cleaner-active_record/bin/console
Executable file
|
@ -0,0 +1,14 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
require "bundler/setup"
|
||||||
|
require "database_cleaner/active_record"
|
||||||
|
|
||||||
|
# You can add fixtures and/or initialization code here to make experimenting
|
||||||
|
# with your gem easier. You can also use a different console, if you like.
|
||||||
|
|
||||||
|
# (If you use this, don't forget to add pry to your Gemfile!)
|
||||||
|
# require "pry"
|
||||||
|
# Pry.start
|
||||||
|
|
||||||
|
require "irb"
|
||||||
|
IRB.start(__FILE__)
|
8
adapters/database_cleaner-active_record/bin/setup
Executable file
8
adapters/database_cleaner-active_record/bin/setup
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
IFS=$'\n\t'
|
||||||
|
set -vx
|
||||||
|
|
||||||
|
bundle install
|
||||||
|
|
||||||
|
# Do any other automated setup that you need to do here
|
|
@ -0,0 +1,42 @@
|
||||||
|
|
||||||
|
lib = File.expand_path("../lib", __FILE__)
|
||||||
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||||
|
require "database_cleaner/active_record/version"
|
||||||
|
|
||||||
|
Gem::Specification.new do |spec|
|
||||||
|
spec.name = "database_cleaner-active_record"
|
||||||
|
spec.version = DatabaseCleaner::ActiveRecord::VERSION
|
||||||
|
spec.authors = ["Ernesto Tagwerker"]
|
||||||
|
spec.email = ["ernesto@ombulabs.com"]
|
||||||
|
|
||||||
|
spec.summary = "Strategies for cleaning databases using ActiveRecord. Can be used to ensure a clean state for testing."
|
||||||
|
spec.description = "Strategies for cleaning databases using ActiveRecord. Can be used to ensure a clean state for testing."
|
||||||
|
spec.homepage = "https://github.com/DatabaseCleaner/database_cleaner-active_record"
|
||||||
|
spec.license = "MIT"
|
||||||
|
|
||||||
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
||||||
|
f.match(%r{^(test|spec|features)/})
|
||||||
|
end
|
||||||
|
spec.bindir = "exe"
|
||||||
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
||||||
|
spec.require_paths = ["lib"]
|
||||||
|
|
||||||
|
spec.add_dependency "database_cleaner", "~> 1.8.0"
|
||||||
|
spec.add_dependency "activerecord"
|
||||||
|
|
||||||
|
spec.add_development_dependency "rake", "~> 10.0"
|
||||||
|
spec.add_development_dependency "bundler", "~> 1.16"
|
||||||
|
spec.add_development_dependency "rspec", "~> 3.0"
|
||||||
|
spec.add_development_dependency "cucumber"
|
||||||
|
|
||||||
|
unless RUBY_PLATFORM =~ /java/
|
||||||
|
spec.add_development_dependency 'mysql', '~> 2.9.1'
|
||||||
|
spec.add_development_dependency 'mysql2'
|
||||||
|
spec.add_development_dependency "activerecord-mysql2-adapter"
|
||||||
|
spec.add_development_dependency 'pg'
|
||||||
|
spec.add_development_dependency "sqlite3-ruby" if RUBY_VERSION < "1.9"
|
||||||
|
spec.add_development_dependency "sqlite3" if RUBY_VERSION >= "1.9"
|
||||||
|
else
|
||||||
|
spec.add_development_dependency "activerecord-jdbc-adapter"
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1 @@
|
||||||
|
require "database_cleaner/active_record"
|
|
@ -0,0 +1,5 @@
|
||||||
|
require 'database_cleaner'
|
||||||
|
require 'database_cleaner/active_record/deletion'
|
||||||
|
require 'database_cleaner/active_record/transaction'
|
||||||
|
require 'database_cleaner/active_record/truncation'
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
require 'active_record'
|
||||||
|
require 'database_cleaner/generic/base'
|
||||||
|
require 'erb'
|
||||||
|
|
||||||
|
module DatabaseCleaner
|
||||||
|
module ActiveRecord
|
||||||
|
|
||||||
|
def self.available_strategies
|
||||||
|
%w[truncation transaction deletion]
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.config_file_location=(path)
|
||||||
|
@config_file_location = path
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.config_file_location
|
||||||
|
@config_file_location ||= "#{DatabaseCleaner.app_root}/config/database.yml"
|
||||||
|
end
|
||||||
|
|
||||||
|
module Base
|
||||||
|
include ::DatabaseCleaner::Generic::Base
|
||||||
|
|
||||||
|
attr_accessor :connection_hash
|
||||||
|
|
||||||
|
def db=(desired_db)
|
||||||
|
@db = desired_db
|
||||||
|
load_config
|
||||||
|
end
|
||||||
|
|
||||||
|
def db
|
||||||
|
@db ||= super
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_config
|
||||||
|
if self.db != :default && self.db.is_a?(Symbol) && File.file?(ActiveRecord.config_file_location)
|
||||||
|
connection_details = YAML::load(ERB.new(IO.read(ActiveRecord.config_file_location)).result)
|
||||||
|
@connection_hash = valid_config(connection_details)[self.db.to_s]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_config(connection_file)
|
||||||
|
if !::ActiveRecord::Base.configurations.nil? && !::ActiveRecord::Base.configurations.empty?
|
||||||
|
if connection_file != ::ActiveRecord::Base.configurations
|
||||||
|
return ::ActiveRecord::Base.configurations
|
||||||
|
end
|
||||||
|
end
|
||||||
|
connection_file
|
||||||
|
end
|
||||||
|
|
||||||
|
def connection_class
|
||||||
|
@connection_class ||= if db && !db.is_a?(Symbol)
|
||||||
|
db
|
||||||
|
elsif connection_hash
|
||||||
|
lookup_from_connection_pool || establish_connection
|
||||||
|
else
|
||||||
|
::ActiveRecord::Base
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.migration_table_name
|
||||||
|
if ::ActiveRecord::VERSION::MAJOR < 5
|
||||||
|
::ActiveRecord::Migrator.schema_migrations_table_name
|
||||||
|
else
|
||||||
|
::ActiveRecord::SchemaMigration.table_name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.exclusion_condition(column_name)
|
||||||
|
result = " #{column_name} <> '#{::DatabaseCleaner::ActiveRecord::Base.migration_table_name}' "
|
||||||
|
if ::ActiveRecord::VERSION::MAJOR >= 5
|
||||||
|
result += " AND #{column_name} <> '#{::ActiveRecord::Base.internal_metadata_table_name}' "
|
||||||
|
end
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def lookup_from_connection_pool
|
||||||
|
if ::ActiveRecord::Base.respond_to?(:descendants)
|
||||||
|
database_name = connection_hash["database"] || connection_hash[:database]
|
||||||
|
models = ::ActiveRecord::Base.descendants
|
||||||
|
models.select(&:connection_pool).detect { |m| m.connection_pool.spec.config[:database] == database_name }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def establish_connection
|
||||||
|
::ActiveRecord::Base.establish_connection(connection_hash)
|
||||||
|
::ActiveRecord::Base
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,108 @@
|
||||||
|
require 'active_record'
|
||||||
|
require 'active_record/connection_adapters/abstract_adapter'
|
||||||
|
require "database_cleaner/generic/truncation"
|
||||||
|
require 'database_cleaner/active_record/truncation'
|
||||||
|
|
||||||
|
module DatabaseCleaner
|
||||||
|
module ConnectionAdapters
|
||||||
|
module AbstractDeleteAdapter
|
||||||
|
def delete_table(table_name)
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module GenericDeleteAdapter
|
||||||
|
def delete_table(table_name)
|
||||||
|
execute("DELETE FROM #{quote_table_name(table_name)};")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module OracleDeleteAdapter
|
||||||
|
def delete_table(table_name)
|
||||||
|
execute("DELETE FROM #{quote_table_name(table_name)}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module ActiveRecord
|
||||||
|
module ConnectionAdapters
|
||||||
|
AbstractAdapter.class_eval { include DatabaseCleaner::ConnectionAdapters::AbstractDeleteAdapter }
|
||||||
|
|
||||||
|
JdbcAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(JdbcAdapter)
|
||||||
|
AbstractMysqlAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(AbstractMysqlAdapter)
|
||||||
|
Mysql2Adapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(Mysql2Adapter)
|
||||||
|
SQLiteAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(SQLiteAdapter)
|
||||||
|
SQLite3Adapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(SQLite3Adapter)
|
||||||
|
PostgreSQLAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(PostgreSQLAdapter)
|
||||||
|
IBM_DBAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(IBM_DBAdapter)
|
||||||
|
SQLServerAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(SQLServerAdapter)
|
||||||
|
OracleEnhancedAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::OracleDeleteAdapter } if defined?(OracleEnhancedAdapter)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module DatabaseCleaner::ActiveRecord
|
||||||
|
module SelectiveTruncation
|
||||||
|
def tables_to_truncate(connection)
|
||||||
|
if information_schema_exists?(connection)
|
||||||
|
(@only || tables_with_new_rows(connection)) - @tables_to_exclude
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def tables_with_new_rows(connection)
|
||||||
|
stats = table_stats_query(connection)
|
||||||
|
if stats != ''
|
||||||
|
connection.select_values(stats)
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def table_stats_query(connection)
|
||||||
|
@table_stats_query ||= build_table_stats_query(connection)
|
||||||
|
ensure
|
||||||
|
@table_stats_query = nil unless @cache_tables
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_table_stats_query(connection)
|
||||||
|
tables = connection.select_values(<<-SQL)
|
||||||
|
SELECT table_name
|
||||||
|
FROM information_schema.tables
|
||||||
|
WHERE table_schema = database()
|
||||||
|
AND #{::DatabaseCleaner::ActiveRecord::Base.exclusion_condition('table_name')};
|
||||||
|
SQL
|
||||||
|
queries = tables.map do |table|
|
||||||
|
"(SELECT #{connection.quote(table)} FROM #{connection.quote_table_name(table)} LIMIT 1)"
|
||||||
|
end
|
||||||
|
queries.join(' UNION ALL ')
|
||||||
|
end
|
||||||
|
|
||||||
|
def information_schema_exists? connection
|
||||||
|
return false unless connection.is_a? ActiveRecord::ConnectionAdapters::Mysql2Adapter
|
||||||
|
@information_schema_exists ||=
|
||||||
|
begin
|
||||||
|
connection.execute("SELECT 1 FROM information_schema.tables")
|
||||||
|
true
|
||||||
|
rescue
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Deletion < Truncation
|
||||||
|
if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
|
||||||
|
include SelectiveTruncation
|
||||||
|
end
|
||||||
|
|
||||||
|
def clean
|
||||||
|
connection = connection_class.connection
|
||||||
|
connection.disable_referential_integrity do
|
||||||
|
tables_to_truncate(connection).each do |table_name|
|
||||||
|
connection.delete_table table_name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,59 @@
|
||||||
|
require 'database_cleaner/active_record/base'
|
||||||
|
require 'database_cleaner/generic/transaction'
|
||||||
|
|
||||||
|
module DatabaseCleaner::ActiveRecord
|
||||||
|
class Transaction
|
||||||
|
include ::DatabaseCleaner::ActiveRecord::Base
|
||||||
|
include ::DatabaseCleaner::Generic::Transaction
|
||||||
|
|
||||||
|
def start
|
||||||
|
# Hack to make sure that the connection is properly setup for
|
||||||
|
# the clean code.
|
||||||
|
connection_class.connection.transaction{ }
|
||||||
|
|
||||||
|
if connection_maintains_transaction_count?
|
||||||
|
if connection_class.connection.respond_to?(:increment_open_transactions)
|
||||||
|
connection_class.connection.increment_open_transactions
|
||||||
|
else
|
||||||
|
connection_class.__send__(:increment_open_transactions)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if connection_class.connection.respond_to?(:begin_transaction)
|
||||||
|
connection_class.connection.begin_transaction :joinable => false
|
||||||
|
else
|
||||||
|
connection_class.connection.begin_db_transaction
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def clean
|
||||||
|
connection_class.connection_pool.connections.each do |connection|
|
||||||
|
next unless connection.open_transactions > 0
|
||||||
|
|
||||||
|
if connection.respond_to?(:rollback_transaction)
|
||||||
|
connection.rollback_transaction
|
||||||
|
else
|
||||||
|
connection.rollback_db_transaction
|
||||||
|
end
|
||||||
|
|
||||||
|
# The below is for handling after_commit hooks.. see https://github.com/bmabey/database_cleaner/issues/99
|
||||||
|
if connection.respond_to?(:rollback_transaction_records, true)
|
||||||
|
connection.send(:rollback_transaction_records, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
if connection_maintains_transaction_count?
|
||||||
|
if connection.respond_to?(:decrement_open_transactions)
|
||||||
|
connection.decrement_open_transactions
|
||||||
|
else
|
||||||
|
connection_class.__send__(:decrement_open_transactions)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def connection_maintains_transaction_count?
|
||||||
|
ActiveRecord::VERSION::MAJOR < 4
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,274 @@
|
||||||
|
require 'active_record/base'
|
||||||
|
require 'database_cleaner/active_record/base'
|
||||||
|
require 'active_record/connection_adapters/abstract_adapter'
|
||||||
|
|
||||||
|
#Load available connection adapters
|
||||||
|
%w(
|
||||||
|
abstract_mysql_adapter postgresql_adapter sqlite3_adapter mysql_adapter mysql2_adapter oracle_enhanced_adapter
|
||||||
|
).each do |known_adapter|
|
||||||
|
begin
|
||||||
|
require "active_record/connection_adapters/#{known_adapter}"
|
||||||
|
rescue LoadError
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
require "database_cleaner/generic/truncation"
|
||||||
|
require 'database_cleaner/active_record/base'
|
||||||
|
|
||||||
|
module DatabaseCleaner
|
||||||
|
module ConnectionAdapters
|
||||||
|
|
||||||
|
module AbstractAdapter
|
||||||
|
# used to be called views but that can clash with gems like schema_plus
|
||||||
|
# this gem is not meant to be exposing such an extra interface any way
|
||||||
|
def database_cleaner_view_cache
|
||||||
|
@views ||= select_values("select table_name from information_schema.views where table_schema = '#{current_database}'") rescue []
|
||||||
|
end
|
||||||
|
|
||||||
|
def database_cleaner_table_cache
|
||||||
|
# the adapters don't do caching (#130) but we make the assumption that the list stays the same in tests
|
||||||
|
@database_cleaner_tables ||= ::ActiveRecord::VERSION::MAJOR >= 5 ? data_sources : tables
|
||||||
|
end
|
||||||
|
|
||||||
|
def truncate_table(table_name)
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
|
||||||
|
def truncate_tables(tables)
|
||||||
|
tables.each do |table_name|
|
||||||
|
self.truncate_table(table_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module AbstractMysqlAdapter
|
||||||
|
def truncate_table(table_name)
|
||||||
|
execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
|
||||||
|
end
|
||||||
|
|
||||||
|
def truncate_tables(tables)
|
||||||
|
tables.each { |t| truncate_table(t) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def pre_count_truncate_tables(tables, options = {:reset_ids => true})
|
||||||
|
filter = options[:reset_ids] ? method(:has_been_used?) : method(:has_rows?)
|
||||||
|
truncate_tables(tables.select(&filter))
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def row_count(table)
|
||||||
|
# Patch for MysqlAdapter with ActiveRecord 3.2.7 later
|
||||||
|
# select_value("SELECT 1") #=> "1"
|
||||||
|
select_value("SELECT EXISTS (SELECT 1 FROM #{quote_table_name(table)} LIMIT 1)").to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
def auto_increment_value(table)
|
||||||
|
select_value(<<-SQL).to_i
|
||||||
|
SELECT auto_increment
|
||||||
|
FROM information_schema.tables
|
||||||
|
WHERE table_name = '#{table}'
|
||||||
|
AND table_schema = database()
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
|
# This method tells us if the given table has been inserted into since its
|
||||||
|
# last truncation. Note that the table might have been populated, which
|
||||||
|
# increased the auto-increment counter, but then cleaned again such that
|
||||||
|
# it appears empty now.
|
||||||
|
def has_been_used?(table)
|
||||||
|
has_rows?(table) || auto_increment_value(table) > 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_rows?(table)
|
||||||
|
row_count(table) > 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module IBM_DBAdapter
|
||||||
|
def truncate_table(table_name)
|
||||||
|
execute("TRUNCATE #{quote_table_name(table_name)} IMMEDIATE")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module SQLiteAdapter
|
||||||
|
def delete_table(table_name)
|
||||||
|
execute("DELETE FROM #{quote_table_name(table_name)};")
|
||||||
|
if uses_sequence
|
||||||
|
execute("DELETE FROM sqlite_sequence where name = '#{table_name}';")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
alias truncate_table delete_table
|
||||||
|
|
||||||
|
def truncate_tables(tables)
|
||||||
|
tables.each { |t| truncate_table(t) }
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Returns a boolean indicating if the SQLite database is using the sqlite_sequence table.
|
||||||
|
def uses_sequence
|
||||||
|
select_value("SELECT name FROM sqlite_master WHERE type='table' AND name='sqlite_sequence';")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module TruncateOrDelete
|
||||||
|
def truncate_table(table_name)
|
||||||
|
begin
|
||||||
|
execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
|
||||||
|
rescue ::ActiveRecord::StatementInvalid
|
||||||
|
execute("DELETE FROM #{quote_table_name(table_name)};")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module OracleAdapter
|
||||||
|
def truncate_table(table_name)
|
||||||
|
execute("TRUNCATE TABLE #{quote_table_name(table_name)}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module PostgreSQLAdapter
|
||||||
|
def db_version
|
||||||
|
@db_version ||= postgresql_version
|
||||||
|
end
|
||||||
|
|
||||||
|
def cascade
|
||||||
|
@cascade ||= db_version >= 80200 ? 'CASCADE' : ''
|
||||||
|
end
|
||||||
|
|
||||||
|
def restart_identity
|
||||||
|
@restart_identity ||= db_version >= 80400 ? 'RESTART IDENTITY' : ''
|
||||||
|
end
|
||||||
|
|
||||||
|
def truncate_table(table_name)
|
||||||
|
truncate_tables([table_name])
|
||||||
|
end
|
||||||
|
|
||||||
|
def truncate_tables(table_names)
|
||||||
|
return if table_names.nil? || table_names.empty?
|
||||||
|
execute("TRUNCATE TABLE #{table_names.map{|name| quote_table_name(name)}.join(', ')} #{restart_identity} #{cascade};")
|
||||||
|
end
|
||||||
|
|
||||||
|
def pre_count_truncate_tables(tables, options = {:reset_ids => true})
|
||||||
|
filter = options[:reset_ids] ? method(:has_been_used?) : method(:has_rows?)
|
||||||
|
truncate_tables(tables.select(&filter))
|
||||||
|
end
|
||||||
|
|
||||||
|
def database_cleaner_table_cache
|
||||||
|
# AR returns a list of tables without schema but then returns a
|
||||||
|
# migrations table with the schema. There are other problems, too,
|
||||||
|
# with using the base list. If a table exists in multiple schemas
|
||||||
|
# within the search path, truncation without the schema name could
|
||||||
|
# result in confusing, if not unexpected results.
|
||||||
|
@database_cleaner_tables ||= tables_with_schema
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Returns a boolean indicating if the given table has an auto-inc number higher than 0.
|
||||||
|
# Note, this is different than an empty table since an table may populated, the index increased,
|
||||||
|
# but then the table is cleaned. In other words, this function tells us if the given table
|
||||||
|
# was ever inserted into.
|
||||||
|
def has_been_used?(table)
|
||||||
|
return has_rows?(table) unless has_sequence?(table)
|
||||||
|
|
||||||
|
cur_val = select_value("SELECT currval('#{table}_id_seq');").to_i rescue 0
|
||||||
|
cur_val > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_sequence?(table)
|
||||||
|
select_value("SELECT true FROM pg_class WHERE relname = '#{table}_id_seq';")
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_rows?(table)
|
||||||
|
select_value("SELECT true FROM #{table} LIMIT 1;")
|
||||||
|
end
|
||||||
|
|
||||||
|
def tables_with_schema
|
||||||
|
rows = select_rows <<-_SQL
|
||||||
|
SELECT schemaname || '.' || tablename
|
||||||
|
FROM pg_tables
|
||||||
|
WHERE
|
||||||
|
tablename !~ '_prt_' AND
|
||||||
|
#{::DatabaseCleaner::ActiveRecord::Base.exclusion_condition('tablename')} AND
|
||||||
|
schemaname = ANY (current_schemas(false))
|
||||||
|
_SQL
|
||||||
|
rows.collect { |result| result.first }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module ActiveRecord
|
||||||
|
module ConnectionAdapters
|
||||||
|
#Apply adapter decoraters where applicable (adapter should be loaded)
|
||||||
|
AbstractAdapter.class_eval { include DatabaseCleaner::ConnectionAdapters::AbstractAdapter }
|
||||||
|
|
||||||
|
if defined?(JdbcAdapter)
|
||||||
|
if defined?(OracleJdbcConnection)
|
||||||
|
JdbcAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::OracleAdapter }
|
||||||
|
else
|
||||||
|
JdbcAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::TruncateOrDelete }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
AbstractMysqlAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::AbstractMysqlAdapter } if defined?(AbstractMysqlAdapter)
|
||||||
|
Mysql2Adapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::AbstractMysqlAdapter } if defined?(Mysql2Adapter)
|
||||||
|
MysqlAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::AbstractMysqlAdapter } if defined?(MysqlAdapter)
|
||||||
|
SQLiteAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::SQLiteAdapter } if defined?(SQLiteAdapter)
|
||||||
|
SQLite3Adapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::SQLiteAdapter } if defined?(SQLite3Adapter)
|
||||||
|
PostgreSQLAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::PostgreSQLAdapter } if defined?(PostgreSQLAdapter)
|
||||||
|
IBM_DBAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::IBM_DBAdapter } if defined?(IBM_DBAdapter)
|
||||||
|
SQLServerAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::TruncateOrDelete } if defined?(SQLServerAdapter)
|
||||||
|
OracleEnhancedAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::OracleAdapter } if defined?(OracleEnhancedAdapter)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module DatabaseCleaner::ActiveRecord
|
||||||
|
class Truncation
|
||||||
|
include ::DatabaseCleaner::ActiveRecord::Base
|
||||||
|
include ::DatabaseCleaner::Generic::Truncation
|
||||||
|
|
||||||
|
def clean
|
||||||
|
connection = connection_class.connection
|
||||||
|
connection.disable_referential_integrity do
|
||||||
|
if pre_count? && connection.respond_to?(:pre_count_truncate_tables)
|
||||||
|
connection.pre_count_truncate_tables(tables_to_truncate(connection), {:reset_ids => reset_ids?})
|
||||||
|
else
|
||||||
|
connection.truncate_tables(tables_to_truncate(connection))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def tables_to_truncate(connection)
|
||||||
|
tables_in_db = cache_tables? ? connection.database_cleaner_table_cache : connection.tables
|
||||||
|
to_reject = (@tables_to_exclude + connection.database_cleaner_view_cache)
|
||||||
|
(@only || tables_in_db).reject do |table|
|
||||||
|
if ( m = table.match(/([^.]+)$/) )
|
||||||
|
to_reject.include?(m[1])
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# overwritten
|
||||||
|
def migration_storage_names
|
||||||
|
[::DatabaseCleaner::ActiveRecord::Base.migration_table_name]
|
||||||
|
end
|
||||||
|
|
||||||
|
def cache_tables?
|
||||||
|
!!@cache_tables
|
||||||
|
end
|
||||||
|
|
||||||
|
def pre_count?
|
||||||
|
@pre_count == true
|
||||||
|
end
|
||||||
|
|
||||||
|
def reset_ids?
|
||||||
|
@reset_ids != false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
module DatabaseCleaner
|
||||||
|
module ActiveRecord
|
||||||
|
VERSION = "0.1.0"
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,9 @@
|
||||||
|
RSpec.describe DatabaseCleaner::ActiveRecord do
|
||||||
|
it "has a version number" do
|
||||||
|
expect(DatabaseCleaner::ActiveRecord::VERSION).not_to be nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does something useful" do
|
||||||
|
expect(false).to eq(true)
|
||||||
|
end
|
||||||
|
end
|
14
adapters/database_cleaner-active_record/spec/spec_helper.rb
Normal file
14
adapters/database_cleaner-active_record/spec/spec_helper.rb
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
require "bundler/setup"
|
||||||
|
require "database_cleaner/active_record"
|
||||||
|
|
||||||
|
RSpec.configure do |config|
|
||||||
|
# Enable flags like --only-failures and --next-failure
|
||||||
|
config.example_status_persistence_file_path = ".rspec_status"
|
||||||
|
|
||||||
|
# Disable RSpec exposing methods globally on `Module` and `main`
|
||||||
|
config.disable_monkey_patching!
|
||||||
|
|
||||||
|
config.expect_with :rspec do |c|
|
||||||
|
c.syntax = :expect
|
||||||
|
end
|
||||||
|
end
|
0
adapters/database_cleaner-active_record/tmp/.keep
Normal file
0
adapters/database_cleaner-active_record/tmp/.keep
Normal file
|
@ -10,6 +10,7 @@ require 'rspec/expectations'
|
||||||
|
|
||||||
DB_DIR = "#{File.dirname(__FILE__)}/../../db"
|
DB_DIR = "#{File.dirname(__FILE__)}/../../db"
|
||||||
|
|
||||||
|
use_gems = ENV['USE_GEMS']
|
||||||
orm = ENV['ORM']
|
orm = ENV['ORM']
|
||||||
another_orm = ENV['ANOTHER_ORM']
|
another_orm = ENV['ANOTHER_ORM']
|
||||||
strategy = ENV['STRATEGY']
|
strategy = ENV['STRATEGY']
|
||||||
|
@ -20,16 +21,26 @@ ENV['REDIS_URL'] = config['test']['url']
|
||||||
ENV['REDIS_URL_ONE'] = config['one']['url']
|
ENV['REDIS_URL_ONE'] = config['one']['url']
|
||||||
ENV['REDIS_URL_TWO'] = config['two']['url']
|
ENV['REDIS_URL_TWO'] = config['two']['url']
|
||||||
|
|
||||||
|
require "active_support/core_ext/string/inflections"
|
||||||
|
|
||||||
if orm && strategy
|
if orm && strategy
|
||||||
$:.unshift(File.dirname(__FILE__) + '/../../../lib')
|
if use_gems
|
||||||
require 'database_cleaner'
|
require "database_cleaner-#{orm.underscore}"
|
||||||
require 'database_cleaner/cucumber'
|
else
|
||||||
|
$:.unshift(File.dirname(__FILE__) + '/../../../lib')
|
||||||
|
require "database_cleaner"
|
||||||
|
end
|
||||||
require "#{File.dirname(__FILE__)}/../../lib/#{orm.downcase}_models"
|
require "#{File.dirname(__FILE__)}/../../lib/#{orm.downcase}_models"
|
||||||
|
|
||||||
if another_orm
|
if another_orm
|
||||||
|
if use_gems
|
||||||
|
require "database_cleaner-#{another_orm.underscore}"
|
||||||
|
end
|
||||||
require "#{File.dirname(__FILE__)}/../../lib/#{another_orm.downcase}_models"
|
require "#{File.dirname(__FILE__)}/../../lib/#{another_orm.downcase}_models"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
require 'database_cleaner/cucumber'
|
||||||
|
|
||||||
if multiple_db
|
if multiple_db
|
||||||
DatabaseCleaner.app_root = "#{File.dirname(__FILE__)}/../.."
|
DatabaseCleaner.app_root = "#{File.dirname(__FILE__)}/../.."
|
||||||
orm_sym = orm.gsub(/(.)([A-Z]+)/,'\1_\2').downcase.to_sym
|
orm_sym = orm.gsub(/(.)([A-Z]+)/,'\1_\2').downcase.to_sym
|
||||||
|
|
|
@ -3,6 +3,19 @@ Feature: database cleaning
|
||||||
As a developer
|
As a developer
|
||||||
I want to have my database in a clean state
|
I want to have my database in a clean state
|
||||||
|
|
||||||
|
Scenario Outline: ruby app with adapter gems
|
||||||
|
Given I am using <ORM> from its adapter gem
|
||||||
|
And the <Strategy> cleaning strategy
|
||||||
|
|
||||||
|
When I run my scenarios that rely on a clean database
|
||||||
|
Then I should see all green
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
| ORM | Strategy |
|
||||||
|
| ActiveRecord | transaction |
|
||||||
|
| ActiveRecord | truncation |
|
||||||
|
| ActiveRecord | deletion |
|
||||||
|
|
||||||
Scenario Outline: ruby app
|
Scenario Outline: ruby app
|
||||||
Given I am using <ORM>
|
Given I am using <ORM>
|
||||||
And the <Strategy> cleaning strategy
|
And the <Strategy> cleaning strategy
|
||||||
|
|
|
@ -3,6 +3,17 @@ Feature: database cleaning
|
||||||
As a developer
|
As a developer
|
||||||
I want to have my database in a clean state with default strategy
|
I want to have my database in a clean state with default strategy
|
||||||
|
|
||||||
|
Scenario Outline: ruby app with adapter gems
|
||||||
|
Given I am using <ORM> from its adapter gem
|
||||||
|
And the default cleaning strategy
|
||||||
|
|
||||||
|
When I run my scenarios that rely on a clean database
|
||||||
|
Then I should see all green
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
| ORM |
|
||||||
|
| ActiveRecord |
|
||||||
|
|
||||||
Scenario Outline: ruby app
|
Scenario Outline: ruby app
|
||||||
Given I am using <ORM>
|
Given I am using <ORM>
|
||||||
And the default cleaning strategy
|
And the default cleaning strategy
|
||||||
|
|
|
@ -3,6 +3,19 @@ Feature: multiple database cleaning
|
||||||
As a developer
|
As a developer
|
||||||
I want to have my databases in a clean state
|
I want to have my databases in a clean state
|
||||||
|
|
||||||
|
Scenario Outline: ruby app with adapter gems
|
||||||
|
Given I am using <ORM> from its adapter gem
|
||||||
|
And the <Strategy> cleaning strategy
|
||||||
|
|
||||||
|
When I run my scenarios that rely on clean databases
|
||||||
|
Then I should see all green
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
| ORM | Strategy |
|
||||||
|
| ActiveRecord | truncation |
|
||||||
|
| ActiveRecord | deletion |
|
||||||
|
| ActiveRecord | transaction |
|
||||||
|
|
||||||
Scenario Outline: ruby app
|
Scenario Outline: ruby app
|
||||||
Given I am using <ORM>
|
Given I am using <ORM>
|
||||||
And the <Strategy> cleaning strategy
|
And the <Strategy> cleaning strategy
|
||||||
|
@ -11,12 +24,13 @@ Feature: multiple database cleaning
|
||||||
Then I should see all green
|
Then I should see all green
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
| ORM | Strategy |
|
| ORM | Strategy |
|
||||||
| ActiveRecord | truncation |
|
| ActiveRecord | truncation |
|
||||||
| ActiveRecord | deletion |
|
| ActiveRecord | deletion |
|
||||||
| DataMapper | truncation |
|
| DataMapper | truncation |
|
||||||
| Sequel | truncation |
|
| Sequel | truncation |
|
||||||
| MongoMapper | truncation |
|
| MongoMapper | truncation |
|
||||||
| DataMapper | transaction |
|
| DataMapper | transaction |
|
||||||
| ActiveRecord | transaction |
|
| ActiveRecord | transaction |
|
||||||
| Sequel | transaction |
|
| Sequel | transaction |
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,12 @@ Feature: database cleaning using multiple ORMs
|
||||||
As a developer
|
As a developer
|
||||||
I want to have my database in a clean state
|
I want to have my database in a clean state
|
||||||
|
|
||||||
|
# Scenario Outline: ruby app with adapter gems
|
||||||
|
# Given I am using <ORM1> and <ORM2> from their adapter gems
|
||||||
|
|
||||||
|
# When I run my scenarios that rely on clean databases using multiple orms
|
||||||
|
# Then I should see all green
|
||||||
|
|
||||||
Scenario Outline: ruby app
|
Scenario Outline: ruby app
|
||||||
Given I am using <ORM1> and <ORM2>
|
Given I am using <ORM1> and <ORM2>
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
orms_pattern = /(ActiveRecord|DataMapper|Sequel|MongoMapper|Mongoid|CouchPotato|Redis|Ohm|Neo4j)/
|
orms_pattern = /(ActiveRecord|DataMapper|Sequel|MongoMapper|Mongoid|CouchPotato|Redis|Ohm|Neo4j)/
|
||||||
|
|
||||||
Given /^I am using #{orms_pattern}$/ do |orm|
|
Given /^I am using #{orms_pattern}( from its adapter gem)?$/ do |orm, from_gems|
|
||||||
@feature_runner = FeatureRunner.new
|
@feature_runner = FeatureRunner.new
|
||||||
|
@feature_runner.use_gems = !!from_gems
|
||||||
@feature_runner.orm = orm
|
@feature_runner.orm = orm
|
||||||
end
|
end
|
||||||
|
|
||||||
Given /^I am using #{orms_pattern} and #{orms_pattern}$/ do |orm1,orm2|
|
Given /^I am using #{orms_pattern} and #{orms_pattern}( from their adapter gems)?$/ do |orm1, orm2, from_gems|
|
||||||
@feature_runner = FeatureRunner.new
|
@feature_runner = FeatureRunner.new
|
||||||
|
@feature_runner.use_gems = !!from_gems
|
||||||
@feature_runner.orm = orm1
|
@feature_runner.orm = orm1
|
||||||
@feature_runner.another_orm = orm2
|
@feature_runner.another_orm = orm2
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
class FeatureRunner
|
class FeatureRunner
|
||||||
|
attr_accessor :use_gems
|
||||||
attr_accessor :orm
|
attr_accessor :orm
|
||||||
attr_accessor :another_orm
|
attr_accessor :another_orm
|
||||||
attr_accessor :multiple_databases
|
attr_accessor :multiple_databases
|
||||||
|
@ -15,6 +16,7 @@ class FeatureRunner
|
||||||
Dir.chdir(full_dir) do
|
Dir.chdir(full_dir) do
|
||||||
|
|
||||||
|
|
||||||
|
ENV['USE_GEMS'] = use_gems ? "true" : nil
|
||||||
ENV['ORM'] = orm
|
ENV['ORM'] = orm
|
||||||
ENV['STRATEGY'] = strategy
|
ENV['STRATEGY'] = strategy
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue