├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .rspec ├── .ruby-version.sample ├── ADAPTERS.md ├── CONTRIBUTE.markdown ├── Gemfile ├── Guardfile ├── History.rdoc ├── LICENSE ├── README.markdown ├── Rakefile ├── bin └── setup ├── cucumber.yml ├── database_cleaner-core.gemspec ├── database_cleaner.gemspec ├── examples ├── config │ ├── database.yml.example │ └── redis.yml ├── db │ └── sqlite_databases_go_here ├── features │ ├── example.feature │ ├── example_multiple_db.feature │ ├── example_multiple_orm.feature │ ├── step_definitions │ │ ├── activerecord_steps.rb │ │ ├── redis_steps.rb │ │ └── translation_steps.rb │ └── support │ │ └── env.rb └── lib │ ├── activerecord_models.rb │ └── redis_models.rb ├── features ├── cleaning.feature ├── cleaning_default_strategy.feature ├── cleaning_multiple_dbs.feature ├── cleaning_multiple_orms.feature ├── step_definitions │ └── database_cleaner_steps.rb └── support │ └── env.rb ├── lib ├── database_cleaner-core.rb ├── database_cleaner.rb └── database_cleaner │ ├── cleaner.rb │ ├── cleaners.rb │ ├── core.rb │ ├── cucumber.rb │ ├── deprecation.rb │ ├── null_strategy.rb │ ├── safeguard.rb │ ├── spec.rb │ ├── spec │ ├── database_helper.rb │ └── shared_examples.rb │ ├── strategy.rb │ └── version.rb ├── spec ├── database_cleaner │ ├── cleaner_spec.rb │ ├── cleaners_spec.rb │ ├── null_strategy_spec.rb │ ├── safeguard_spec.rb │ └── strategy_spec.rb ├── rcov.opts ├── spec_helper.rb └── support │ ├── active_record_helper.rb │ ├── data_mapper_helper.rb │ ├── example.database.yml │ ├── sample.config.yml │ └── sequel_helper.rb └── tmp └── .keep /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | ruby-version: 12 | - '3.3' 13 | - '3.2' 14 | - '3.1' 15 | - '3.0' 16 | - '2.7' 17 | - '2.6' 18 | - '2.5' 19 | 20 | services: 21 | redis: 22 | image: redis 23 | options: >- 24 | --health-cmd "redis-cli ping" 25 | --health-interval 10s 26 | --health-timeout 5s 27 | --health-retries 5 28 | ports: 29 | - 6379:6379 30 | 31 | steps: 32 | - uses: actions/checkout@v3 33 | - name: Set up Ruby ${{ matrix.ruby-version }} 34 | uses: ruby/setup-ruby@v1 35 | with: 36 | ruby-version: ${{ matrix.ruby-version }} 37 | bundler-cache: true # 'bundle install' and cache 38 | - name: Run tests 39 | run: bundle exec rake 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw? 2 | .DS_Store 3 | coverage 4 | pkg 5 | .bundle 6 | bundled_gems/ 7 | vendor/ 8 | examples/db/*.db 9 | examples/config/database.yml 10 | spec/support/config.yml 11 | tmp/* 12 | !tmp/.keep 13 | Gemfile.lock 14 | .rbenv-version 15 | .rvmrc 16 | .ruby-version 17 | .vagrant 18 | .idea/ 19 | .byebug_history 20 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation 3 | --require spec_helper 4 | --order rand 5 | -------------------------------------------------------------------------------- /.ruby-version.sample: -------------------------------------------------------------------------------- 1 | 2.1.2@db 2 | -------------------------------------------------------------------------------- /ADAPTERS.md: -------------------------------------------------------------------------------- 1 | # WORK IN PROGRESS - PLEASE DO NOT FOLLOW THESE INSTRUCTIONS UNTIL v2.0.0 FINAL IS RELEASED! 2 | 3 | ## How to add a new adapter 4 | 5 | _Note the following tutorial is for database_cleaner version 2 and above_ 6 | 7 | Every adapter is a separate gem, the first step in the creation of an adapter is to create a new gem. 8 | 9 | ### Naming 10 | Please follow the [Rubygems convention for gem naming](https://guides.rubygems.org/name-your-gem/). The namespace you will be working within is `DatabaseCleaner::Namespace` where Namespace is the name of the ORM you are creation an adapter for. 11 | For example, `database_cleaner-active_record` provides `DatabaseCleaner::ActiveRecord`, and `database_cleaner-redis` provides `DatabaseCleaner::Redis`, etc. 12 | 13 | ### Bootstrapping a gem 14 | We will use `bundle` to bootstrap the new gem. This will produce all the initial files needed. 15 | 16 | ``` 17 | bundle gem database_cleaner-orm_name 18 | ``` 19 | #### Modifying .gemspec 20 | You need to add a couple of dependecies to `.gemspec`: 21 | * `database_cleaner-core` 22 | * Adapter you're creating this gem for 23 | 24 | ``` 25 | spec.add_dependency "database_cleaner-core" 26 | spec.add_dependency "orm_name", "some version if required" 27 | ``` 28 | 29 | ### File structure 30 | 31 | Inside the `lib/database_cleaner/orm_name` directory, you will need to create a few files: 32 | 33 | * Separate files for each strategy you have 34 | 35 | The file structure you end up with will look something like this 36 | 37 | ``` 38 | \-lib 39 | \-database_cleaner 40 | \- orm_name 41 | \- truncation.rb 42 | \- deletion.rb 43 | \- transaction.rb 44 | \- version.rb 45 | \- orm_name.rb 46 | ``` 47 | 48 | #### orm_name.rb 49 | 50 | File `orm_name.rb` **must** do the following: 51 | * require `database_cleaner-core` 52 | * require all the strategies 53 | * configure `DatabaseCleaner` with the default strategy for the ORM. 54 | 55 | So, in the end you will end up with a file that might look something like this: 56 | 57 | ```ruby 58 | # lib/database_cleaner/orm_name.rb 59 | 60 | require 'database_cleaner/orm_name/version' 61 | require 'database_cleaner/core' 62 | require 'database_cleaner/orm_name/transaction' 63 | require 'database_cleaner/orm_name/truncation' 64 | require 'database_cleaner/orm_name/deletion' 65 | 66 | DatabaseCleaner[:orm_name].strategy = :transaction 67 | ``` 68 | 69 | ### Strategy classes 70 | 71 | Each strategy class **must** inherit from `DatabaseCleaner::Strategy`. 72 | 73 | Each strategy **must** have the following instance methods: 74 | * `#clean` -- where the cleaning happens 75 | 76 | Optionally, depending on how your strategy works you may also need to define 77 | * `#start` -- if your strategy is transactional, this is where you would start the database transaction that `#clean` later rolls back. 78 | 79 | Given that we're creating a strategy for truncation, you may end up with something like the following class: 80 | 81 | ```ruby 82 | # lib/database_cleaner/orm_name/truncation.rb 83 | 84 | require 'database_cleaner/strategy' 85 | require 'orm' 86 | 87 | module DatabaseCleaner 88 | module OrmName 89 | class Truncation < Strategy 90 | def clean 91 | # actual database cleaning code goes here 92 | ORM.truncate_all_tables! 93 | end 94 | end 95 | end 96 | end 97 | ``` 98 | 99 | That's about it for the code needed to create your own adapter! 100 | 101 | ### Testing 102 | 103 | To make sure that your new adapter adheres to the Database Cleaner API, database_cleaner-core provides an RSpec shared example. This shared example only checks to make sure all the right methods exist. You will still want to write tests to verify that the cleaning actually works as you expect! 104 | 105 | ```ruby 106 | # spec/database_cleaner/orm_name_spec.rb 107 | 108 | require 'database_cleaner/orm_name' 109 | require 'database_cleaner/spec' 110 | 111 | RSpec.describe DatabaseCleaner::OrmName do 112 | it_should_behave_like "a database_cleaner adapter" 113 | end 114 | ``` 115 | 116 | ### What's next 117 | 118 | Now you should be all set up with your very own database_cleaner ORM adapter! 119 | Also, don't forget to take a look at the already created adapters, if you encounter any problems. 120 | 121 | When you are done with your adapter gem, only a few things left to do 122 | * Create a repository with your code 123 | * Push code to rubygems 124 | * Open a PR to add your adapter to the [list](https://github.com/DatabaseCleaner/database_cleaner#list-of-adapters) 125 | -------------------------------------------------------------------------------- /CONTRIBUTE.markdown: -------------------------------------------------------------------------------- 1 | # Guidelines for contributing 2 | 3 | ## 1. Fork & Clone 4 | 5 | Since you probably don't have rights to the main repo, you should Fork it (big 6 | button up top). After that, clone your fork locally and optionally add an 7 | upstream: 8 | 9 | git remote add upstream git@github.com:DatabaseCleaner/database_cleaner.git 10 | 11 | ## 2. Make sure the tests run fine 12 | 13 | - `bundle install` 14 | - Copy `spec/support/sample.config.yml` to `spec/support/config.yml` and edit it 15 | - Run the tests with `bundle exec rspec` 16 | 17 | Note that if you don't have all the supported databases installed and running, 18 | some tests will fail. 19 | 20 | ## 3. Prepare your contribution 21 | 22 | This is all up to you but a few points should be kept in mind: 23 | 24 | - Please write tests for your contribution 25 | - Make sure that previous tests still pass 26 | - Push it to a branch of your fork 27 | - Submit a pull request 28 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec name: "database_cleaner-core" 4 | 5 | gem "byebug" 6 | gem "database_cleaner-active_record", git: "https://github.com/DatabaseCleaner/database_cleaner-active_record" 7 | gem "database_cleaner-redis", git: "https://github.com/DatabaseCleaner/database_cleaner-redis" 8 | 9 | group :test do 10 | gem "simplecov", require: false 11 | gem "codecov", require: false 12 | end 13 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | guard :rspec, cmd: "bundle exec rspec" do 2 | watch(%r{^spec/.+_spec\.rb$}) 3 | watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } 4 | watch("spec/spec_helper.rb") { "spec" } 5 | end 6 | -------------------------------------------------------------------------------- /History.rdoc: -------------------------------------------------------------------------------- 1 | == Development (unreleased) 2 | 3 | == 2.1.0 2024-10-24 4 | 5 | === Changes 6 | * Add support for trilogy: https://github.com/DatabaseCleaner/database_cleaner/pull/707 7 | === Chores 8 | * Start testing with Ruby 3.3 in CI: https://github.com/DatabaseCleaner/database_cleaner/pull/710 9 | * Point CI badge to GitHub Actions: https://github.com/DatabaseCleaner/database_cleaner/pull/706 10 | === Breaking changes 11 | * Skip conventional sqlite:// URLs from safeguards: https://github.com/DatabaseCleaner/database_cleaner/pull/715 12 | 13 | == 2.0.2 2023-03-10 14 | 15 | == Bugfixes 16 | * Dependency configuration -- relax dependencies with database_cleaner-active_record: https://github.com/DatabaseCleaner/database_cleaner/pull/699 17 | 18 | == 2.0.1 2021-02-04 19 | 20 | == Bugfixes 21 | * Regression: allow_remote_database_url and url_allowlist not working anymore: https://github.com/DatabaseCleaner/database_cleaner/pull/671 22 | 23 | == 2.0.0 2021-01-31 24 | 25 | === Changes 26 | * Rename `url_whitelist` to `url_allowlist` 27 | * Allowlist now supports regular expressions 28 | * Fixed Ruby 2.7 deprecation warnings 29 | 30 | === Breaking changes 31 | * Failed checks against the allowlist now raise `UrlNotAllowed` rather than `NotWhitelistedUrl` 32 | 33 | == 2.0.0.beta2 2020-05-30 34 | 35 | === Features 36 | * New API for ORM Adapter gems: https://github.com/DatabaseCleaner/database_cleaner/pull/644 37 | 38 | === Breaking changes 39 | * Rename :connection configuration option to :db for consistency: https://github.com/DatabaseCleaner/database_cleaner/pull/650 40 | * Remove all #orm= setter methods: https://github.com/DatabaseCleaner/database_cleaner/pull/643/files 41 | * drop support for Ruby 2.4 which is EOL as of 2020-03-31: https://github.com/DatabaseCleaner/database_cleaner/pull/635 42 | 43 | == 2.0.0.beta 2020-04-05 44 | 45 | === Breaking changes 46 | * Replace old shared RSpec examples with new "database_cleaner adapter" example: https://github.com/DatabaseCleaner/database_cleaner/pull/629 47 | * split gem into database_cleaner-core and database_cleaner metagem. 48 | * Support Ruby versions 2.4, 2.5, 2.6, and 2.7, and drop support for older Rubies. 49 | * remove all deprecated code and get the specs passing again. 50 | * Split off all adapter gems into their own repos: https://github.com/DatabaseCleaner/database_cleaner/pull/620 51 | 52 | == 1.99.0 2021-01-31 53 | 54 | == Changes 55 | * Remove unnecessary dependency on database_cleaner-mongo from database_cleaner-mongoid: @botandrose 56 | * Enable the :cache_tables option for the mongo truncation strategy, and default to true: https://github.com/DatabaseCleaner/database_cleaner/pull/646" 57 | * Introduce deletion aliases for truncation strategies for mongo, mongoid, and redis adapters. https://github.com/DatabaseCleaner/database_cleaner/pull/654 58 | * Add new :db orm configuration key, for consistency with #db and #db=. https://github.com/DatabaseCleaner/database_cleaner/pull/649 59 | 60 | == Deprecations 61 | * Deprecate all #orm= setter methods: https://github.com/DatabaseCleaner/database_cleaner/pull/643 62 | * Deprecate non-functional :reset_ids option in ActiveRecord truncation strategy: https://github.com/DatabaseCleaner/database_cleaner/issues/559 63 | * Deprecate mongo truncation's `:cache_tables => true` option in favor of `false`, to prep for caching removal in v2.0: https://github.com/DatabaseCleaner/database_cleaner/pull/646" 64 | * Deprecate redis truncation's #url method in favor of #db: @botandrose 65 | * Deprecate mongo, mongoid, and redis truncation strategies in favor of deletion. https://github.com/DatabaseCleaner/database_cleaner/pull/654 66 | * Deprecate :connection and :model configuration options in favor of :db for consistency: https://github.com/DatabaseCleaner/database_cleaner/pull/650 67 | 68 | == Bugfixes 69 | * Fix deprecation warning about `DatabaseCleaner.connections` to recommend a better alternative: https://github.com/DatabaseCleaner/database_cleaner/pull/656 70 | 71 | == 1.8.5 2020-05-04 72 | 73 | === Bug Fixes 74 | * Fix :mongo strategy: https://github.com/DatabaseCleaner/database_cleaner/pull/645 75 | 76 | == 1.8.4 2020-04-02 77 | 78 | === Bug Fixes 79 | * Fix false positive deprecation warnings on Windows: https://github.com/DatabaseCleaner/database_cleaner/pull/633 80 | 81 | == 1.8.3 2020-02-18 82 | 83 | === Bug Fixes 84 | * Fix performance issue of DatabaseCleaner::Base#orm_module: https://github.com/DatabaseCleaner/database_cleaner/pull/625 85 | 86 | == 1.8.2 2020-02-01 87 | 88 | === Bug Fixes 89 | * Fix database_cleaner-ohm autodetected adapter loading: https://github.com/DatabaseCleaner/database_cleaner/pull/619 90 | * Fix database_cleaner-mongo_mapper autodetected adapter loading: @botandrose 91 | * Fix database_cleaner-mongoid autodetected adapter loading: https://github.com/DatabaseCleaner/database_cleaner/pull/617 92 | * Exclude ar_internal_metadata from truncation on Rails 5: https://github.com/DatabaseCleaner/database_cleaner/pull/588 93 | 94 | === Changes 95 | * Deprecate ohm adapter: https://github.com/DatabaseCleaner/database_cleaner/pull/619 96 | 97 | == 1.8.1 2020-01-30 98 | 99 | === Bug Fixes 100 | * Remove undeclared active_support dependency: https://github.com/DatabaseCleaner/database_cleaner/pull/612 101 | 102 | == 1.8.0 2020-01-29 103 | 104 | === Bug Fixes 105 | * Fix MySQL deprecation warnings with Rails 5: https://github.com/DatabaseCleaner/database_cleaner/pull/574 106 | * Fix MySQL truncation with `pre_count: true`: https://github.com/DatabaseCleaner/database_cleaner/pull/498 107 | * Fix primary key sequence resetting in Sequel with Postgres and SQLite: https://github.com/DatabaseCleaner/database_cleaner/pull/538/files 108 | * ActiveRecord truncation adapter doesn't work with Oracle: https://github.com/DatabaseCleaner/database_cleaner/pull/542 109 | 110 | === Changes 111 | * Extract ORM adapters into gems: https://github.com/DatabaseCleaner/database_cleaner/pull/560 112 | * Allow postgres:///dbname as a local url: https://github.com/DatabaseCleaner/database_cleaner/pull/569 113 | * Add an optional URL whitelist safeguard: https://github.com/DatabaseCleaner/database_cleaner/pull/526 114 | * Add `local` tld to safeguard check: https://github.com/DatabaseCleaner/database_cleaner/pull/547 115 | * Speed up ActiveRecord deletion strategy: https://github.com/DatabaseCleaner/database_cleaner/pull/534 116 | * Consider `sqlite:` database urls to be local: https://github.com/DatabaseCleaner/database_cleaner/pull/529 117 | 118 | == 1.7.0 2018-04-19 119 | 120 | === Bug Fixes 121 | * Remove unnecessary folders from gem: https://github.com/DatabaseCleaner/database_cleaner/pull/508 122 | * Properly quote table names: https://github.com/DatabaseCleaner/database_cleaner/pull/501 123 | * Use more idiomatic Ruby in README: https://github.com/DatabaseCleaner/database_cleaner/pull/510 124 | * Return ::ActiveRecord::Base from `establish_connection`: https://github.com/DatabaseCleaner/database_cleaner/pull/399 125 | 126 | === Changes 127 | * Safeguard against running in production or running against a remote database: https://github.com/DatabaseCleaner/database_cleaner/pull/521 128 | 129 | == 1.6.2 2017-10-29 130 | 131 | === Bug Fixes 132 | * ActiveRecord::Base namespace patch: https://github.com/DatabaseCleaner/database_cleaner/pull/490 133 | * Better exclusion condition based on Rails version: https://github.com/DatabaseCleaner/database_cleaner/pull/487 134 | 135 | === Changes 136 | * Better documentation. Typos were fixed. Sequel deletion is supported: https://github.com/DatabaseCleaner/database_cleaner/pull/500 137 | 138 | == 1.6.1 2017-05-09 139 | 140 | === Bug Fixes 141 | * Deletion strategy fix for ActiveRecord (@kawamoto) 142 | 143 | == 1.6.0 2017-05-04 144 | 145 | === New Features/Changes 146 | * Rails 5.1 support: Remove deprecation warning (@activefx) 147 | * MySQL 5.6+ InnoDB support (@ahorek) 148 | * Better documentation (fixed typo) (@hoshinotsuyoshi) 149 | 150 | === Bug Fixes 151 | * Fix Redis db option (@soylent) 152 | * Make NullStrategy API-complete (@anicholson) 153 | 154 | == 1.5.3 2016-04-22 155 | 156 | === Bug Fixes 157 | * @P9GIT fixed issue #436 158 | 159 | == 1.5.2 2016-04-17 160 | 161 | === New Features/Changes 162 | * Use default_client with mongoid 5.0 (@stjhimy) 163 | * Added comparable support for strategies (@st0012) 164 | * Better README instructions that suggest `append_after` (@jrochkind) 165 | * Removed deprecation warnings for Rails 5.0+ (@pschambacher) 166 | * Upgrade to RSpec 2.14 (@skalee) 167 | 168 | === Bug Fixes 169 | * @dmitrypol fixed issue #409 170 | * @schmierkov fixed the Travis builds with PR #416 171 | * @shosti fixed issue #345 172 | * @kylev fixed issue #379 173 | * @skalee fixed the Travis builds for Neo4j with PR #433 174 | 175 | == 1.5.1 2015-09-05 176 | 177 | == Bug Fix 178 | * Added mongo2 missing files to the gemspec. (@geerzo) 179 | 180 | == 1.5.0 2015-09-02 181 | 182 | === New Features/Changes 183 | * Use ensure within the cleaning method. (@cema-sp) 184 | * Restored mysql2 + jruby support. (@ahorek) 185 | * Added required ruby version to gemspec. (@garethrees) 186 | * Added support for Mongoid 5.0 and Mongo Ruby Driver 2.0. (@jprincipe) 187 | * Added support for additional Neo4j::Session.open options. (@brienw) 188 | * And a bunch of code style and README improvements 189 | 190 | === Bug Fixes 191 | * Fixed truncation for MongoDB 3. (@andreale) 192 | * Fixed YAML error when building gem. (@joshnesbitt) 193 | * Fixed deletion strategy for JDBC MySQL. (@DanElbert) 194 | * Fixed open transactions for multiple connections. (@claptimes5) 195 | 196 | == 1.4.1 2015-03-09 197 | * Support for deletion with Sequel. (@cyberdelia) 198 | * Use ActiveRecord::Base configuration when different from config file. (@wendy0402) 199 | * Removed dependency of Mongo. (@codegourmet) 200 | * Fixed issue with tables schema prefix in Postgres. (@phoenixek12) 201 | * Use ruby 1.8 syntax. (@daniel-g) 202 | * Added license to gemspec. (@chrismar035) 203 | * Improved coverage for migrations table. (@jonallured) 204 | 205 | == 1.4.0 2014-12-17 206 | 207 | === New Features/Changes 208 | * Support for Neo4j. (@dpisarewski) 209 | * Support for multiple connections on Mongoid. (@nyarly) 210 | 211 | === Better Performance 212 | * Using the deletion strategy with Mysql now only deletes those tables which have had records added to them. (@MadRabbit) 213 | * Add support for pre_count on Sequel with Mysql. (@vrinek) 214 | * Cache collection names in mongo's truncation strategy. (@nyarly) 215 | 216 | === Bug Fixes 217 | * Fix undefined method error with DataMapper SQLite adaptor. (@lanej) 218 | * Fully define Mysql2 adaptor constant. (@jgonera) 219 | * Don't truncate schema tables in Postgres. (@billywatson) 220 | * Fix issue where Moped cleaner was missing collections with 'system' in their name. (@MartinNowak) 221 | 222 | == 1.3.0 2014-05-23 223 | 224 | === New Features/Changes 225 | * Introduced `DatabaseCleaner::cleaning` method that takes a block. (@ethco) 226 | * Improved Sequel support and added more tests (@ethco, @rhunter) 227 | 228 | === Bug Fixes 229 | 230 | * Fixed an issue with the `Transaction` strategy and Active Record where application-level transactions 231 | are not rolledback correctly. (Godfrey Chan) 232 | * activerecord-oracle_enhanced-adapter now works again (#259, @sockmonk) 233 | 234 | == 1.2.0 2013-10-09 235 | 236 | A huge thanks goes to @tommeier for fixing the bug with class loading that was cuasing the wrong adapters 237 | to be used in certain cases. 238 | 239 | === New Features/Changes 240 | 241 | * Upgraded RSpec to remove deprecation warnings (John Rowe) 242 | * Caching of tables to truncate is now optional (@marcoow) 243 | 244 | === Bug Fixes 245 | 246 | * Use class_eval loading of superclasses to ensure right version of class is patched. (Tom Meier, Joel Nimety, Ernesto Tagwerker) 247 | * Add truncate_tables method to SQLiteAdapter. (Chris Mo) 248 | * Fixes missing #uses_sequence invokation in adapter classes for sqlite and sqlite3 (Lefteris Laskaridis) 249 | 250 | 251 | == 1.1.1 2013-08-01 252 | 253 | === Bug Fixes 254 | 255 | * Fixes typo in Postgres superclass (POSTGRE_ADAPTER_PARENT > POSTGRES_ADAPTER_PARENT) (Joel Nimety) 256 | 257 | == 1.1.0 2013-08-01 258 | 259 | === New Features/Changes 260 | 261 | * schema_migrations table name is now retrieved from ActiveRecord (Kyle Stevens) 262 | * Autoloading logic is now exposed, see PR #212 for details (Jeff Felchner) 263 | 264 | === Bug Fixes 265 | 266 | * Deletion strategy works again on MySQL, had to roll back multiple statements patch. 267 | * Fix MySqlAdapter superclass bug via class_eval loading of superclasses (Tom Meier) 268 | * Sequel strategy fix dealing with symbol/string mismatch on table names. (Fred Wu) 269 | 270 | == 1.0.1 2013-05-13 271 | 272 | * Patch release to fix broken gemspec file. Sorry folks! 273 | 274 | == 1.0.0 2013-05-13 275 | 276 | === New Features/Changes 277 | 278 | * Dropping support for Ruby 1.8.x; Only 1.9.x and beyond will be supported going forward. 279 | * Now supporting and testing against ruby 2.0.x. 280 | * Adds support for AR 4.0 by using `begin_transaction` (David Chelimsky and Steve Madsen) 281 | * Adds Rails 4 support for SQLite3Adapter 282 | * Suppport for Moped when used without Mongoid (Cyprian Kowalczyk) 283 | * Redis & Ohm support (Hengbin Qiu, James Conroy-Finn) 284 | 285 | * CI Improvements (Jan Vlnas, Murahashi Sanemat Kenichi, Samer Masry, Jordan Hollinger) 286 | * README/Documentation improvements (Marcelo Cajueiro, Donald Ball, TJ Chambers, Nick Huanca, Justin Edwards, Ryota Arai) 287 | 288 | === Bug Fixes 289 | 290 | * Fixes transaction errors when using `after_commit` hooks in AR. 291 | * Fixes truncation error with SQLite (Daniel White) 292 | * Fixes `pre_count` logic in AR Postgres. (Jordan Hollinger) 293 | * Sequel fix to normalize all table names to strings. (Lauri Peltola) 294 | * #clean_with now works with multiple connections. (John Ferlito) 295 | * Always start a AR transaction to prevent nil errors in AR when rolling back (John Hampton, M.Shibuya) 296 | 297 | == 0.9.1 2012-10-11 298 | 299 | (0.9.0 was released first but was yanked due to bad gemspec) 300 | 301 | === New Features 302 | 303 | * New options for AR :truncation for speed. See README for details. (Stanislaw Pankevich) 304 | * view caching works with the schema_plus gem loaded 305 | * ActiveRecord::ConnectionAdapters::AbstractAdapter#views was renamed to an internal name 306 | * ActiveRecord truncation strategy caches the list of tables #130 (Petteri Räty) 307 | * Caches AR DB connections which speeds up cleaning with multiple DBs and allows for transation strategy. 308 | * MongoDB :truncation strategy (wihtout use of additional library like Mogoid). #138 (Christopher Darrell & Richard Luther/@sidereel) 309 | * Add Sequel support for multiple migration storage names in #148 (Jack Chu) 310 | * Multiple database support for Mongoid 3 #149 (Luke Francl) 311 | 312 | === Bug Fixes 313 | 314 | * :deletion for AR Postgres in JRuby now works #140 (Heiko Seebach) 315 | * Rescue LoadError when AR adapters not available. #145 (Garrow Bedrossian) 316 | * Fixes DatabaseCleaner::[] to cache cleaners. 317 | 318 | == 0.8.0 2012-06-02 319 | 320 | * Faster truncation strategy for ActiveRecord with MySQL or PostgreSQL 321 | * Upgrade to RSpec 2 322 | * Support for Mongoid 3/Moped (Andrew Bennett) 323 | * Postgres Adapter no longer generates invalid SQL when no tables provided. (Michael-Keith Bernard) 324 | 325 | == 0.7.2 2012-03-21 326 | 327 | * Proper Mysql2Adapter superclass fix. (Jonathan Viney) 328 | * Sequel::Transaction works with latest Sequel. (David Barri) 329 | * Documenation fixes/improvements. (David Barri, Ben Mabey, Kevin Moore) 330 | 331 | == 0.7.1 2012-01-15 332 | 333 | === New Features 334 | 335 | * Support for Rails 3.2. (David Demaree) 336 | 337 | === Bugfixes 338 | 339 | * Truncation resets the id count on SQLite. (Jordan Hollinger) 340 | * AR delete strategy now disables referential integrity. (Ben Mabey) 341 | * Fixes Postgres adapter for JRuby. (Dmytrii Nagirniak, Uģis Ozols) 342 | * Documenation fixes. (Josh Rendek, Joshua Flanagan) 343 | * Fixes bad error message when no database is specified for AR. (issue #72, Ben Mabey) 344 | 345 | 346 | == 0.7.0 2011-11-12 347 | 348 | === New Features 349 | 350 | * Sequel Support (Corin Langosch) 351 | * Updates DataMapper strategies to work with DataMapper 1.1 (Xavier Shay and Anthony Williams) 352 | * for AR and PSQL, truncate all tables with one command, improving performance due to avoiding cascades (Leonid Shevtsov) 353 | 354 | === Bugfixes 355 | 356 | * Avoids trying to load the ':default' ActiveRecord config. #72 (Ben Mabey) 357 | 358 | == 0.6.7 2011-04-21 359 | 360 | === Bugfixes 361 | 362 | * Explicity require ERB. (Vít Ondruch) 363 | * Cache DB connections, fixes referential integrity bug when using multiple DBs. (John Ferlito) 364 | 365 | == 0.6.6 2011-03-16 366 | 367 | === Bugfixes 368 | 369 | * Don't modify the array passed in with the :except key. (Eric Wollesen) 370 | * Fixes version checking for postgresql. (Greg Barnett) 371 | 372 | == 0.6.5 2011-03-08 373 | 374 | === Bugfixes 375 | 376 | * When truncating in postgresql (>= 8.4) sequences are now reset. (Greg Barnett) 377 | * Fixes the MongoDB truncation so non system collections starting with 'system' are not excluded for truncation. (Dmitry Naumov) 378 | 379 | == 0.6.4 2011-02-21 380 | 381 | === Bugfixes 382 | 383 | * Avoids trying to drop views in Postgres. (Bernerd Schaefer) 384 | 385 | == 0.6.3 2011-02-09 386 | 387 | === New Features 388 | 389 | * Configurable logger to aid in debugging database cleaner. (Marty Haught) 390 | 391 | == 0.6.2 2011-02-04 392 | 393 | === New Features 394 | 395 | * Support IBM_DB Adapter for table truncation. This is for DB2 >= 9.7 (GH-39 Samer Abukhait) 396 | 397 | === Bugfixes 398 | 399 | * Reversed GH-41 after larger community discussion. Mongo indexes are no longer dropped. (Ben Mabey) 400 | * Truncation strategy works on SqlServer tables with FKs. (GH-33, Hugo Freire) 401 | 402 | == 0.6.1 2011-01-27 403 | 404 | === New Features 405 | 406 | * Default strategies for all ORM libs are defined. (GH-36, GH-38 Prem Sichanugrist) 407 | * Add a NullStrategy. (GH-6 Ben Mabey) 408 | 409 | === Bugfixes 410 | 411 | * Mongo colletion indexes are dropped for collections being removed. (GH-41 Ben Mabey) 412 | * Exclude database views from tables_to_truncate, if the connection adapter 413 | supports reading from the ANSI standard information_schema views. (GH-25 Samer Abukhait) 414 | * ORM types can be specified in string format and not mysteriously blowup. (GH-26 Ben Mabey) 415 | * Do not remove MongoDB reserved system collections. (GH-24 Ches Martin) 416 | 417 | == 0.6.0 2010-10-25 - The Multi-ORM/Connection Release 418 | 419 | This release has the often asked for functionality of being able to clean 420 | multiple databases within the same project. This involves being able to 421 | clean databases managed by the same ORM (i.e. different connections) and 422 | also being able to clean databases managed by distinct ORMs. So, for 423 | example you can now use DatabaseCleaner on a project that has ActiveRecord 424 | and Mongoid to help ensure all DBs all in a clean state. Please see the 425 | README for more information. The old API has been preserved so this release 426 | is backwards compatible. 427 | 428 | This release is a result of Jon Rowe's hard work. Many thanks to Jon for all 429 | of the hours and effort he put into making this feature request a reality. 430 | 431 | === New Features 432 | 433 | * Ability to clean multiple database connections managed by the same ORM. (Jon Rowe) 434 | * Ability to clean multiple DBs managed by different ORMs in same project. (Jon Rowe) 435 | * Allows for the ActiveRecord config file (database.yml) to contain ERB and process it. (Fletcher Nichol) 436 | * Mysql2Adapter support. (Kamal Fariz Mahyuddin and John Ferlito) 437 | * Deletion strategy for ActiveRecord (Mikl Kurkov) 438 | 439 | === Bugfixes 440 | 441 | * Updates the DataMapper truncation strategy to version 0.10.3. (Robert Rouse) 442 | * Addresses Ruby 1.9 and 1.8 differences causing a bug in the AR PostgreSQLAdapter truncation strategy. (GH-14, James B. Byrne) 443 | * Fixes syntax error that MySQL was throwing during DataMapper truncation. (Blake Gentry) 444 | * Fixes truncation for PostgreSQL (Bodaniel Jeanes and Gabriel Sobrinho) 445 | * Workaround for superclass mismatches for the ActiveRecord-jdbc-adapter (Toms Mikoss) 446 | 447 | == 0.5.2 448 | 449 | === Bugfixes 450 | 451 | * Removes extraneous puts call from configuration.rb. (Ben Mabey) 452 | 453 | == 0.5.1 - The Mongoid Release 454 | 455 | This release also attempts to fix AR for Rails 3 support. I have seen mixed reviews on this. Some people 456 | claim the fixes allow for use in Rails3 while others have not had good luck with it. I plan on reworking 457 | the way AR support is added so that it is more friendly with how Rails 3 uses autoload. 458 | 459 | === New features 460 | 461 | * Clean and clean_with methods are now aliased to clean! and clean_with!. (Ben Mabey) 462 | * Mongoid Support! (Sidney Burks) 463 | 464 | === Bugfixes 465 | 466 | * Check PostgreSQL version >= 8.2 before using TRUNCATE CASCADE (James B. Byrne) 467 | * Correct superclass is used in ActiveRecord connection adapters. (johnathan, Aslak Hellesoy, Ben Mabey) 468 | 469 | == 0.5.0 2010-02-22 - The CouchPotato Release 470 | 471 | === New features 472 | 473 | * Basic truncation support for CouchPotato / CouchDB. (Martin Rehfeld) 474 | * SQLite3 on JRuby will fall back to delete if truncate doesn't work. (Darrin Holst) 475 | * JDBC is used for ActiveRecord automaticaly when JRuby is detected. (Darrin Holst) 476 | 477 | === Bufixes 478 | 479 | * MongoMapper truncation strategy now works with :only and :except options. (Ben Mabey) 480 | 481 | == 0.4.3 2010-01-17 482 | 483 | === New features 484 | 485 | * Truncation for ActiveRecord oracle_enhanced adapter. (Edgars Beigarts) 486 | 487 | == 0.4.2 2010-01-12 488 | 489 | === Bufixes 490 | 491 | * Datamapper truncation now uses 'select' instead of deprecated the 'query' method. (Steve Tooke) 492 | 493 | == 0.4.1 2010-01-07 494 | 495 | === Bufixes 496 | 497 | * Postgres tables with FKs now truncate (added TRUNCADE CASCADE) using Datamapper. (Ben Mabey) 498 | 499 | == 0.4.0 2009-12-23 (The MongoMapper Edition) 500 | 501 | === New features 502 | 503 | * MongoMapper support for the truncation strategy. (Aubrey Holland) 504 | 505 | == 0.3.0 2009-12-20 506 | 507 | === New features 508 | 509 | * DataMapper 0.10.0 Compatible. (Martin Gamsjaeger) 510 | 511 | === Bufixes 512 | 513 | * Postgres tables with FKs now truncate (added TRUNCADE CASCADE). (Vika - yozhyk on github) 514 | 515 | == 0.2.3 2009-05-30 516 | 517 | === New features 518 | 519 | * Support for SQL Server truncation (Adam Meehan) 520 | 521 | == 0.2.2 2009-05-08 522 | 523 | === Bufixes 524 | 525 | * Added proper gemspec description and summary. (Ben Mabey, thanks to Martin Gamsjaeger) 526 | 527 | === New features 528 | 529 | == 0.2.1 2009-05-08 530 | 531 | === Bufixes 532 | 533 | * Removed extraneous TruncationBase class definition. (Ben Mabey) 534 | 535 | == 0.2.0 2009-05-08 - The Datamapper Release 536 | 537 | === New features 538 | 539 | * DataMapper strategies (Martin Gamsjaeger) 540 | * Transaction 541 | * Truncation - working SQLite3, MySQL adapters. Experimental Postgres adapter (not tested). 542 | 543 | == 0.1.3 2009-04-30 544 | 545 | === New features 546 | 547 | * PostgresSQLAdapter for AR to support the truncation strategy. (Alberto Perdomo) 548 | 549 | === Bufixes 550 | 551 | * Added missing quotes around table names in truncation calls. (Michael MacDonald) 552 | 553 | == 0.1.2 2009-03-05 554 | 555 | === New features 556 | 557 | * JDBC Adapter to enable AR truncation strategy to work. (Kamal Fariz Mahyuddin) 558 | 559 | == 0.1.1 2009-03-04 - Initial Release (Ben Mabey) 560 | 561 | * Basic infrastructure 562 | * Features, RSpec code examples 563 | * ActiveRecord strategies 564 | * Truncation - with MySQL, and SQLite3 adapters. 565 | * Transaction - wrap your modifications and roll them back. 566 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Ben Mabey 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # Database Cleaner 2 | 3 | [![Build Status](https://github.com/DatabaseCleaner/database_cleaner/actions/workflows/ci.yml/badge.svg)](https://github.com/DatabaseCleaner/database_cleaner/actions/workflows/ci.yml) 4 | [![Code Climate](https://codeclimate.com/github/DatabaseCleaner/database_cleaner/badges/gpa.svg)](https://codeclimate.com/github/DatabaseCleaner/database_cleaner) 5 | [![codecov](https://codecov.io/gh/DatabaseCleaner/database_cleaner/branch/main/graph/badge.svg)](https://codecov.io/gh/DatabaseCleaner/database_cleaner) 6 | ![Gem Version](https://badge.fury.io/rb/database_cleaner.svg) 7 | [![SemVer](https://api.dependabot.com/badges/compatibility_score?dependency-name=database_cleaner&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=database_cleaner&package-manager=bundler&version-scheme=semver) 8 | 9 | Database Cleaner is a set of gems containing strategies for cleaning your database in Ruby. 10 | 11 | The original use case was to ensure a clean state during tests. 12 | 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. 13 | 14 | ## Gem Setup 15 | 16 | Instead of using the `database_cleaner` gem directly, each ORM has its own gem. Most projects will only need the `database_cleaner-active_record` gem: 17 | 18 | ```ruby 19 | # Gemfile 20 | group :test do 21 | gem 'database_cleaner-active_record' 22 | end 23 | ``` 24 | 25 | If you are using multiple ORMs, just load multiple gems: 26 | 27 | 28 | ```ruby 29 | # Gemfile 30 | group :test do 31 | gem 'database_cleaner-active_record' 32 | gem 'database_cleaner-redis' 33 | end 34 | ``` 35 | ## List of adapters 36 | 37 | Here is an overview of the databases and ORMs supported by each adapter: 38 | 39 | MySQL, PostgreSQL, SQLite, etc 40 | * [database_cleaner-active_record](https://github.com/DatabaseCleaner/database_cleaner-active_record) 41 | * [database_cleaner-sequel](https://github.com/DatabaseCleaner/database_cleaner-sequel) 42 | 43 | MongoDB 44 | * [database_cleaner-mongo](https://github.com/DatabaseCleaner/database_cleaner-mongo) 45 | * [database_cleaner-mongoid](https://github.com/DatabaseCleaner/database_cleaner-mongoid) 46 | 47 | Redis 48 | * [database_cleaner-redis](https://github.com/DatabaseCleaner/database_cleaner-redis) 49 | 50 | More details on available configuration options can be found in the README for the specific adapter gem that you're using. 51 | 52 | For support or to discuss development please use the [Google Group](https://groups.google.com/group/database_cleaner). 53 | 54 | ### Discontinued adapters 55 | 56 | The following adapters have been discontinued. Please let us know on the [Google Group](https://groups.google.com/group/database_cleaner) if you think one of these should be resurrected! 57 | 58 | * [database_cleaner-data_mapper](https://github.com/DatabaseCleaner/database_cleaner-data_mapper) 59 | * [database_cleaner-couch_potato](https://github.com/DatabaseCleaner/database_cleaner-couch_potato) 60 | * [database_cleaner-mongo_mapper](https://github.com/DatabaseCleaner/database_cleaner-mongo_mapper) 61 | * [database_cleaner-moped](https://github.com/DatabaseCleaner/database_cleaner-moped) 62 | * [database_cleaner-neo4j](https://github.com/DatabaseCleaner/database_cleaner-neo4j) 63 | 64 | ## How to use 65 | 66 | ```ruby 67 | require 'database_cleaner/active_record' 68 | 69 | DatabaseCleaner.strategy = :truncation 70 | 71 | # then, whenever you need to clean the DB 72 | DatabaseCleaner.clean 73 | ``` 74 | 75 | With the `:truncation` strategy you can also pass in options, for example: 76 | 77 | ```ruby 78 | DatabaseCleaner.strategy = [:truncation, only: %w[widgets dogs some_other_table]] 79 | ``` 80 | 81 | ```ruby 82 | DatabaseCleaner.strategy = [:truncation, except: %w[widgets]] 83 | ``` 84 | 85 | (I should point out the truncation strategy will never truncate your schema_migrations table.) 86 | 87 | 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 `DatabaseCleaner.cleaning`. So you would have: 88 | 89 | ```ruby 90 | require 'database_cleaner/active_record' 91 | 92 | DatabaseCleaner.strategy = :transaction 93 | 94 | DatabaseCleaner.start # usually this is called in setup of a test 95 | 96 | dirty_the_db 97 | 98 | DatabaseCleaner.clean # cleanup of the test 99 | 100 | # OR 101 | 102 | DatabaseCleaner.cleaning do 103 | dirty_the_db 104 | end 105 | ``` 106 | 107 | At times you may want to do a single clean with one strategy. 108 | 109 | 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: 110 | 111 | ```ruby 112 | require 'database_cleaner/active_record' 113 | 114 | DatabaseCleaner.clean_with :truncation 115 | 116 | DatabaseCleaner.strategy = :transaction 117 | 118 | # then make the DatabaseCleaner.start and DatabaseCleaner.clean calls appropriately 119 | ``` 120 | 121 | ## What strategy is fastest? 122 | 123 | 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. 124 | 125 | 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. 126 | 127 | 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). 128 | 129 | An easier, but slower, solution is to use the `:truncation` or `:deletion` strategy. 130 | 131 | So what is fastest out of `:deletion` and `:truncation`? Well, it depends on your table structure and what percentage of tables you populate in an average test. The reasoning is out of the scope of this README but here is a [good SO answer on this topic for Postgres](https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886). 132 | 133 | 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. 134 | 135 | If you are using ActiveRecord then take a look at the [additional options](https://github.com/DatabaseCleaner/database_cleaner-active_record#strategy-configuration-options) available for `:truncation`. 136 | 137 | Database Cleaner also includes a `null` strategy (that does no cleaning at all) which can be used with any ORM library. 138 | You can also explicitly use it by setting your strategy to `nil`. 139 | 140 | ## Test Framework Examples 141 | 142 | ### RSpec Example 143 | 144 | ```ruby 145 | RSpec.configure do |config| 146 | 147 | config.before(:suite) do 148 | DatabaseCleaner.strategy = :transaction 149 | DatabaseCleaner.clean_with(:truncation) 150 | end 151 | 152 | config.around(:each) do |example| 153 | DatabaseCleaner.cleaning do 154 | example.run 155 | end 156 | end 157 | 158 | end 159 | ``` 160 | 161 | ### RSpec with Capybara Example 162 | 163 | You'll typically discover a feature spec is incorrectly using transaction 164 | instead of truncation strategy when the data created in the spec is not 165 | visible in the app-under-test. 166 | 167 | A frequently occurring example of this is when, after creating a user in a 168 | spec, the spec mysteriously fails to login with the user. This happens because 169 | the user is created inside of an uncommitted transaction on one database 170 | connection, while the login attempt is made using a separate database 171 | connection. This separate database connection cannot access the 172 | uncommitted user data created over the first database connection due to 173 | transaction isolation. 174 | 175 | For feature specs using a Capybara driver for an external 176 | JavaScript-capable browser (in practice this is all drivers except 177 | `:rack_test`), the Rack app under test and the specs do not share a 178 | database connection. 179 | 180 | When a spec and app-under-test do not share a database connection, 181 | you'll likely need to use the truncation strategy instead of the 182 | transaction strategy. 183 | 184 | See the suggested config below to temporarily enable truncation strategy 185 | for affected feature specs only. This config continues to use transaction 186 | strategy for all other specs. 187 | 188 | It's also recommended to use `append_after` to ensure `DatabaseCleaner.clean` 189 | runs *after* the after-test cleanup `capybara/rspec` installs. 190 | 191 | ```ruby 192 | require 'capybara/rspec' 193 | 194 | #... 195 | 196 | RSpec.configure do |config| 197 | 198 | config.use_transactional_fixtures = false 199 | 200 | config.before(:suite) do 201 | if config.use_transactional_fixtures? 202 | raise(<<-MSG) 203 | Delete line `config.use_transactional_fixtures = true` from rails_helper.rb 204 | (or set it to false) to prevent uncommitted transactions being used in 205 | JavaScript-dependent specs. 206 | 207 | During testing, the app-under-test that the browser driver connects to 208 | uses a different database connection to the database connection used by 209 | the spec. The app's database connection would not be able to access 210 | uncommitted transaction data setup over the spec's database connection. 211 | MSG 212 | end 213 | DatabaseCleaner.clean_with(:truncation) 214 | end 215 | 216 | config.before(:each) do 217 | DatabaseCleaner.strategy = :transaction 218 | end 219 | 220 | config.before(:each, type: :feature) do 221 | # :rack_test driver's Rack app under test shares database connection 222 | # with the specs, so continue to use transaction strategy for speed. 223 | driver_shares_db_connection_with_specs = Capybara.current_driver == :rack_test 224 | 225 | unless driver_shares_db_connection_with_specs 226 | # Driver is probably for an external browser with an app 227 | # under test that does *not* share a database connection with the 228 | # specs, so use truncation strategy. 229 | DatabaseCleaner.strategy = :truncation 230 | end 231 | end 232 | 233 | config.before(:each) do 234 | DatabaseCleaner.start 235 | end 236 | 237 | config.append_after(:each) do 238 | DatabaseCleaner.clean 239 | end 240 | 241 | end 242 | ``` 243 | 244 | 245 | ### Minitest Example 246 | 247 | ```ruby 248 | DatabaseCleaner.strategy = :transaction 249 | 250 | class Minitest::Spec 251 | before :each do 252 | DatabaseCleaner.start 253 | end 254 | 255 | after :each do 256 | DatabaseCleaner.clean 257 | end 258 | end 259 | 260 | # with the minitest-around gem, this may be used instead: 261 | class Minitest::Spec 262 | around do |tests| 263 | DatabaseCleaner.cleaning(&tests) 264 | end 265 | end 266 | ``` 267 | 268 | ### Cucumber Example 269 | 270 | 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. 271 | 272 | Otherwise, to add DatabaseCleaner to your project by hand, create a file `features/support/database_cleaner.rb` that looks like this: 273 | 274 | ```ruby 275 | require 'database_cleaner/active_record' 276 | 277 | DatabaseCleaner.strategy = :truncation 278 | 279 | Around do |scenario, block| 280 | DatabaseCleaner.cleaning(&block) 281 | end 282 | ``` 283 | 284 | This should cover the basics of tear down between scenarios and keeping your database clean. 285 | 286 | For more examples see the section ["Why?"](#why). 287 | 288 | ## How to use with multiple ORMs 289 | 290 | Sometimes you need to use multiple ORMs in your application. 291 | 292 | You can use DatabaseCleaner to clean multiple ORMs, and multiple databases for those ORMs. 293 | 294 | ```ruby 295 | require 'database_cleaner/active_record' 296 | require 'database_cleaner/mongo_mapper' 297 | 298 | # How to specify particular orms 299 | DatabaseCleaner[:active_record].strategy = :transaction 300 | DatabaseCleaner[:mongo_mapper].strategy = :truncation 301 | 302 | # How to specify particular databases 303 | DatabaseCleaner[:active_record, db: :two] 304 | 305 | # You may also pass in the model directly: 306 | DatabaseCleaner[:active_record, db: ModelWithDifferentConnection] 307 | ``` 308 | 309 | Usage beyond that remains the same with `DatabaseCleaner.start` calling any setup on the different configured databases, and `DatabaseCleaner.clean` executing afterwards. 310 | 311 | ## Why? 312 | 313 | One of my motivations for writing this library was to have an easy way to turn on what Rails calls "transactional_fixtures" in my non-rails ActiveRecord projects. 314 | 315 | After copying and pasting code to do this several times I decided to package it up as a gem and save everyone a bit of time. 316 | 317 | ## Safeguards 318 | 319 | DatabaseCleaner comes with safeguards against: 320 | 321 | * Running in production (checking for `ENV`, `APP_ENV`, `RACK_ENV`, and `RAILS_ENV`) 322 | * Running against a remote database (checking for a `DATABASE_URL` that does not include `localhost`, `.local` or `127.0.0.1`) 323 | 324 | Both safeguards can be disabled separately as follows. 325 | 326 | Using environment variables: 327 | 328 | ``` 329 | export DATABASE_CLEANER_ALLOW_PRODUCTION=true 330 | export DATABASE_CLEANER_ALLOW_REMOTE_DATABASE_URL=true 331 | ``` 332 | 333 | In Ruby: 334 | 335 | ```ruby 336 | DatabaseCleaner.allow_production = true 337 | DatabaseCleaner.allow_remote_database_url = true 338 | ``` 339 | 340 | In Ruby, a URL allowlist can be specified. When specified, DatabaseCleaner will only allow `DATABASE_URL` to be equal 341 | to one of the values specified in the url allowlist like so: 342 | 343 | ```ruby 344 | DatabaseCleaner.url_allowlist = ['postgres://postgres@localhost', 'postgres://foo@bar'] 345 | ``` 346 | 347 | Allowlist elements are matched with case equality (`===`), so regular expressions or procs may be used: 348 | 349 | ```ruby 350 | DatabaseCleaner.url_allowlist = [ 351 | %r{^postgres://postgres@localhost}, # match any db with this prefix 352 | proc {|uri| URI.parse(uri).user == "test" } # match any db authenticating with the 'test' user 353 | ] 354 | ``` 355 | 356 | ## CHANGELOG 357 | 358 | See [HISTORY](History.rdoc) for details. 359 | 360 | ## COPYRIGHT 361 | 362 | See [LICENSE](LICENSE) for details. 363 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # testing 2 | require "rspec/core/rake_task" 3 | RSpec::Core::RakeTask.new(:spec) 4 | require "cucumber/rake/task" 5 | Cucumber::Rake::Task.new(:features) 6 | task :default => [:spec, :features] 7 | 8 | # releasing 9 | require "rake/clean" 10 | CLOBBER.include "pkg" 11 | require "bundler/gem_helper" 12 | Bundler::GemHelper.install_tasks name: :database_cleaner 13 | Bundler::GemHelper.install_tasks name: :"database_cleaner-core" 14 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle check || bundle install 7 | cp spec/support/sample.config.yml spec/support/config.yml 8 | -------------------------------------------------------------------------------- /cucumber.yml: -------------------------------------------------------------------------------- 1 | default: features 2 | -------------------------------------------------------------------------------- /database_cleaner-core.gemspec: -------------------------------------------------------------------------------- 1 | require_relative "./lib/database_cleaner/version" 2 | 3 | Gem::Specification.new do |spec| 4 | spec.name = "database_cleaner-core" 5 | spec.version = DatabaseCleaner::VERSION 6 | spec.authors = ["Ben Mabey", "Ernesto Tagwerker"] 7 | spec.email = ["ernesto@ombulabs.com"] 8 | 9 | spec.summary = "Strategies for cleaning databases. Can be used to ensure a clean slate for testing." 10 | spec.description = "Strategies for cleaning databases. Can be used to ensure a clean slate for testing." 11 | spec.homepage = "https://github.com/DatabaseCleaner/database_cleaner" 12 | spec.license = "MIT" 13 | 14 | spec.files = `git ls-files -z`.split("\x0").reject do |f| 15 | f.match(%r{^(test|spec|features|examples)/}) 16 | end - ["lib/database_cleaner.rb"] # should only exist in database_cleaner gem 17 | spec.bindir = "exe" 18 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_development_dependency "rake" 22 | spec.add_development_dependency "bundler" 23 | spec.add_development_dependency 'guard-rspec' 24 | spec.add_development_dependency "listen" 25 | spec.add_development_dependency "rspec" 26 | 27 | spec.add_development_dependency "cucumber", "~>3.0" 28 | spec.add_development_dependency "activesupport" 29 | spec.add_development_dependency "database_cleaner-active_record" 30 | spec.add_development_dependency "sqlite3" 31 | spec.add_development_dependency "database_cleaner-redis" 32 | end 33 | -------------------------------------------------------------------------------- /database_cleaner.gemspec: -------------------------------------------------------------------------------- 1 | require_relative "./lib/database_cleaner/version" 2 | 3 | Gem::Specification.new do |spec| 4 | spec.name = "database_cleaner" 5 | spec.version = DatabaseCleaner::VERSION 6 | spec.authors = ["Ben Mabey", "Ernesto Tagwerker", "Micah Geisel"] 7 | spec.email = ["ernesto@ombulabs.com"] 8 | 9 | spec.summary = "Strategies for cleaning databases. Can be used to ensure a clean slate for testing." 10 | spec.description = "Strategies for cleaning databases. Can be used to ensure a clean slate for testing." 11 | spec.homepage = "https://github.com/DatabaseCleaner/database_cleaner" 12 | spec.license = "MIT" 13 | 14 | spec.metadata["changelog_uri"] = "https://github.com/DatabaseCleaner/database_cleaner/blob/main/History.rdoc" 15 | 16 | spec.files = ["lib/database_cleaner.rb"] 17 | spec.require_paths = ["lib"] 18 | 19 | spec.add_dependency "database_cleaner-active_record", ">= 2", "< 3" 20 | end 21 | -------------------------------------------------------------------------------- /examples/config/database.yml.example: -------------------------------------------------------------------------------- 1 | #This is an example of what database.yml *should* look like (when I wrote it) 2 | #The real database.yml is generated automatically by the active record model lib (so it can be correct) 3 | two: 4 | adapter: sqlite3 5 | database: /path/to/examples/features/support/../../db/activerecord_two.db 6 | one: 7 | adapter: sqlite3 8 | database: /path/to/examples/features/support/../../db/activerecord_one.db 9 | -------------------------------------------------------------------------------- /examples/config/redis.yml: -------------------------------------------------------------------------------- 1 | test: 2 | url: 'redis://localhost:6379/0' 3 | 4 | one: 5 | url: 'redis://localhost:6379/1' 6 | 7 | two: 8 | url: 'redis://localhost:6379/2' 9 | -------------------------------------------------------------------------------- /examples/db/sqlite_databases_go_here: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DatabaseCleaner/database_cleaner/f4780bcc119d666a75001261f3eae5423a00a530/examples/db/sqlite_databases_go_here -------------------------------------------------------------------------------- /examples/features/example.feature: -------------------------------------------------------------------------------- 1 | Feature: example 2 | In order to test DatabaseCleaner 3 | Here are some scenarios that rely on the DB being clean! 4 | 5 | Scenario: dirty the db 6 | When I create a widget 7 | Then I should see 1 widget 8 | 9 | Scenario: assume a clean db 10 | When I create a widget 11 | Then I should see 1 widget 12 | -------------------------------------------------------------------------------- /examples/features/example_multiple_db.feature: -------------------------------------------------------------------------------- 1 | Feature: example 2 | In order to test DataBase Cleaner 3 | Here are some scenarios that rely on the DB being clean! 4 | 5 | # Background: 6 | # Given I have setup DatabaseCleaner to clean multiple databases 7 | # 8 | Scenario: dirty the db 9 | When I create a widget in one db 10 | And I create a widget in another db 11 | Then I should see 1 widget in one db 12 | And I should see 1 widget in another db 13 | 14 | Scenario: assume a clean db 15 | When I create a widget in one db 16 | Then I should see 1 widget in one db 17 | And I should see 0 widget in another db 18 | 19 | Scenario: assume a clean db 20 | When I create a widget in another db 21 | Then I should see 0 widget in one db 22 | And I should see 1 widget in another db 23 | -------------------------------------------------------------------------------- /examples/features/example_multiple_orm.feature: -------------------------------------------------------------------------------- 1 | Feature: example 2 | In order to test DataBase Cleaner 3 | Here are some scenarios that rely on the DB being clean! 4 | 5 | # Background: 6 | # Given I have setup DatabaseCleaner to clean multiple orms 7 | 8 | Scenario: dirty the db 9 | When I create a widget in one orm 10 | And I create a widget in another orm 11 | Then I should see 1 widget in one orm 12 | And I should see 1 widget in another orm 13 | 14 | Scenario: assume a clean db 15 | When I create a widget in one orm 16 | Then I should see 1 widget in one orm 17 | And I should see 0 widget in another orm 18 | 19 | Scenario: assume a clean db 20 | When I create a widget in another orm 21 | Then I should see 0 widget in one orm 22 | And I should see 1 widget in another orm 23 | -------------------------------------------------------------------------------- /examples/features/step_definitions/activerecord_steps.rb: -------------------------------------------------------------------------------- 1 | Given /^I have setup database cleaner to clean multiple databases using activerecord$/ do 2 | #DatabaseCleaner 3 | # require "#{File.dirname(__FILE__)}/../../../lib/datamapper_models" 4 | # 5 | # DatabaseCleaner[:active_record, db: :one].strategy = :truncation 6 | # DatabaseCleaner[:active_record, db: :two].strategy = :truncation 7 | end 8 | 9 | When /^I create a widget using activerecord$/ do 10 | ActiveRecordWidget.create! 11 | end 12 | 13 | Then /^I should see ([\d]+) widget using activerecord$/ do |widget_count| 14 | expect(ActiveRecordWidget.count).to eq widget_count.to_i 15 | end 16 | 17 | When /^I create a widget in one db using activerecord$/ do 18 | ActiveRecordWidgetUsingDatabaseOne.create! 19 | end 20 | 21 | When /^I create a widget in another db using activerecord$/ do 22 | ActiveRecordWidgetUsingDatabaseTwo.create! 23 | end 24 | 25 | Then /^I should see ([\d]+) widget in one db using activerecord$/ do |widget_count| 26 | expect(ActiveRecordWidgetUsingDatabaseOne.count).to eq widget_count.to_i 27 | end 28 | 29 | Then /^I should see ([\d]+) widget in another db using activerecord$/ do |widget_count| 30 | expect(ActiveRecordWidgetUsingDatabaseTwo.count).to eq widget_count.to_i 31 | end 32 | -------------------------------------------------------------------------------- /examples/features/step_definitions/redis_steps.rb: -------------------------------------------------------------------------------- 1 | Given /^I have setup database cleaner to clean multiple databases using redis$/ do 2 | #DatabaseCleaner 3 | # require "#{File.dirname(__FILE__)}/../../../lib/redis_models" 4 | # 5 | # DatabaseCleaner[:redis, db: ENV['REDIS_URL_ONE']].strategy = :truncation 6 | # DatabaseCleaner[:redis, db: ENV['REDIS_URL_TWO']].strategy = :truncation 7 | end 8 | 9 | When /^I create a widget using redis$/ do 10 | RedisWidget.create! 11 | end 12 | 13 | Then /^I should see ([\d]+) widget using redis$/ do |widget_count| 14 | expect(RedisWidget.count).to eq widget_count.to_i 15 | end 16 | 17 | When /^I create a widget in one db using redis$/ do 18 | RedisWidgetUsingDatabaseOne.create! 19 | end 20 | 21 | When /^I create a widget in another db using redis$/ do 22 | RedisWidgetUsingDatabaseTwo.create! 23 | end 24 | 25 | Then /^I should see ([\d]+) widget in one db using redis$/ do |widget_count| 26 | expect(RedisWidgetUsingDatabaseOne.count).to eq widget_count.to_i 27 | end 28 | 29 | Then /^I should see ([\d]+) widget in another db using redis$/ do |widget_count| 30 | expect(RedisWidgetUsingDatabaseTwo.count).to eq widget_count.to_i 31 | end 32 | -------------------------------------------------------------------------------- /examples/features/step_definitions/translation_steps.rb: -------------------------------------------------------------------------------- 1 | When /^I create a widget$/ do 2 | step "I create a widget using #{ENV['ORM'].downcase}" 3 | end 4 | 5 | Then /^I should see 1 widget$/ do 6 | step "I should see 1 widget using #{ENV['ORM'].downcase}" 7 | end 8 | 9 | When /^I create a widget in one orm$/ do 10 | step "I create a widget using #{ENV['ORM'].downcase}" 11 | end 12 | 13 | When /^I create a widget in another orm$/ do 14 | step "I create a widget using #{ENV['ANOTHER_ORM'].downcase}" 15 | end 16 | 17 | Then /^I should see 1 widget in one orm$/ do 18 | step "I should see 1 widget using #{ENV['ORM'].downcase}" 19 | end 20 | 21 | Then /^I should see 1 widget in another orm$/ do 22 | step "I should see 1 widget using #{ENV['ANOTHER_ORM'].downcase}" 23 | end 24 | 25 | Then /^I should see 0 widget in another orm$/ do 26 | step "I should see 0 widget using #{ENV['ANOTHER_ORM'].downcase}" 27 | end 28 | 29 | Then /^I should see 0 widget in one orm$/ do 30 | step "I should see 0 widget using #{ENV['ORM'].downcase}" 31 | end 32 | 33 | When /^I create a widget in one db$/ do 34 | step "I create a widget in one db using #{ENV['ORM'].downcase}" 35 | end 36 | 37 | When /^I create a widget in another db$/ do 38 | step "I create a widget in another db using #{ENV['ORM'].downcase}" 39 | end 40 | 41 | Then /^I should see 1 widget in one db$/ do 42 | step "I should see 1 widget in one db using #{ENV['ORM'].downcase}" 43 | end 44 | 45 | Then /^I should see 1 widget in another db$/ do 46 | step "I should see 1 widget in another db using #{ENV['ORM'].downcase}" 47 | end 48 | 49 | Then /^I should see 0 widget in another db$/ do 50 | step "I should see 0 widget in another db using #{ENV['ORM'].downcase}" 51 | end 52 | 53 | Then /^I should see 0 widget in one db$/ do 54 | step "I should see 0 widget in one db using #{ENV['ORM'].downcase}" 55 | end 56 | -------------------------------------------------------------------------------- /examples/features/support/env.rb: -------------------------------------------------------------------------------- 1 | require "bundler/setup" 2 | require "byebug" 3 | require "rspec/expectations" 4 | 5 | orm = ENV['ORM'] 6 | another_orm = ENV['ANOTHER_ORM'] 7 | strategy = ENV['STRATEGY'] 8 | multiple_db = ENV['MULTIPLE_DBS'] 9 | 10 | if ENV['COVERAGE'] == 'true' 11 | require "simplecov" 12 | simple_cov_key = "Inner Cucumber with #{[orm, another_orm, strategy, multiple_db].compact.join(", ")}" 13 | SimpleCov.command_name simple_cov_key 14 | 15 | if ENV['CI'] == 'true' 16 | require 'codecov' 17 | SimpleCov.formatter = SimpleCov::Formatter::Codecov 18 | puts "required codecov" 19 | end 20 | 21 | SimpleCov.start 22 | puts "required simplecov" 23 | end 24 | 25 | DB_DIR = "#{File.dirname(__FILE__)}/../../db" 26 | 27 | config = YAML::load(File.open("#{File.dirname(__FILE__)}/../../config/redis.yml")) 28 | ENV['REDIS_URL'] = config['test']['url'] 29 | ENV['REDIS_URL_ONE'] = config['one']['url'] 30 | ENV['REDIS_URL_TWO'] = config['two']['url'] 31 | 32 | require "active_support/core_ext/string/inflections" 33 | 34 | if orm && strategy 35 | require "#{File.dirname(__FILE__)}/../../lib/#{orm.downcase}_models" 36 | require "database_cleaner-#{orm.underscore}" 37 | 38 | if another_orm 39 | require "#{File.dirname(__FILE__)}/../../lib/#{another_orm.downcase}_models" 40 | require "database_cleaner-#{another_orm.underscore}" 41 | end 42 | 43 | require 'database_cleaner/cucumber' 44 | 45 | if multiple_db 46 | orm_sym = orm.gsub(/(.)([A-Z]+)/,'\1_\2').downcase.to_sym 47 | 48 | case orm_sym 49 | when :redis 50 | cleaner = DatabaseCleaner[orm_sym, db: ENV['REDIS_URL_ONE']] 51 | cleaner.strategy = strategy.to_sym unless strategy == "default" 52 | cleaner = DatabaseCleaner[orm_sym, db: ENV['REDIS_URL_TWO']] 53 | cleaner.strategy = strategy.to_sym unless strategy == "default" 54 | when :active_record 55 | cleaner = DatabaseCleaner[:active_record, db: ActiveRecordWidgetUsingDatabaseOne] 56 | cleaner.strategy = strategy.to_sym unless strategy == "default" 57 | cleaner = DatabaseCleaner[:active_record, db: ActiveRecordWidgetUsingDatabaseTwo] 58 | cleaner.strategy = strategy.to_sym unless strategy == "default" 59 | end 60 | 61 | elsif another_orm 62 | cleaner = DatabaseCleaner[ orm.gsub(/(.)([A-Z]+)/,'\1_\2').downcase.to_sym] 63 | cleaner.strategy = strategy.to_sym unless strategy == "default" 64 | cleaner = DatabaseCleaner[another_orm.gsub(/(.)([A-Z]+)/,'\1_\2').downcase.to_sym] 65 | cleaner.strategy = strategy.to_sym unless strategy == "default" 66 | else 67 | DatabaseCleaner.strategy = strategy.to_sym unless strategy == "default" 68 | end 69 | 70 | else 71 | raise "Run 'ORM=ActiveRecord|Redis [ANOTHER_ORM=...] [MULTIPLE_DBS=true] STRATEGY=transaction|truncation|default cucumber examples/features'" 72 | end 73 | -------------------------------------------------------------------------------- /examples/lib/activerecord_models.rb: -------------------------------------------------------------------------------- 1 | require 'active_record' 2 | databases_config = { 3 | "one" => {"adapter" => "#{"jdbc" if defined?(JRUBY_VERSION)}sqlite3", "database" => "#{DB_DIR}/activerecord_one.db"}, 4 | "two" => {"adapter" => "#{"jdbc" if defined?(JRUBY_VERSION)}sqlite3", "database" => "#{DB_DIR}/activerecord_two.db"} 5 | } 6 | 7 | File.open("#{File.dirname(__FILE__)}/../config/database.yml", 'w') do |file| 8 | file.write(YAML.dump(databases_config)) 9 | end 10 | 11 | ["two","one"].each do |db| 12 | ActiveRecord::Base.establish_connection(databases_config[db]) 13 | ActiveRecord::Base.connection.execute('DROP TABLE IF EXISTS "active_record_widgets"') 14 | ActiveRecord::Base.connection.execute('DROP TABLE IF EXISTS "active_record_widget_using_database_ones"') 15 | ActiveRecord::Base.connection.execute('DROP TABLE IF EXISTS "active_record_widget_using_database_twos"') 16 | 17 | ActiveRecord::Schema.define(:version => 1) do 18 | create_table :active_record_widgets do |t| 19 | t.string :name 20 | end 21 | 22 | create_table :active_record_widget_using_database_ones do |t| 23 | t.string :name 24 | end 25 | 26 | create_table :active_record_widget_using_database_twos do |t| 27 | t.string :name 28 | end 29 | end 30 | end 31 | 32 | class ActiveRecordWidget < ActiveRecord::Base 33 | end 34 | 35 | class ActiveRecordWidgetUsingDatabaseOne < ActiveRecord::Base 36 | establish_connection(:adapter => "#{"jdbc" if defined?(JRUBY_VERSION)}sqlite3", :database => "#{DB_DIR}/activerecord_one.db") 37 | end 38 | 39 | class ActiveRecordWidgetUsingDatabaseTwo < ActiveRecord::Base 40 | establish_connection(:adapter => "#{"jdbc" if defined?(JRUBY_VERSION)}sqlite3", :database => "#{DB_DIR}/activerecord_two.db") 41 | end 42 | -------------------------------------------------------------------------------- /examples/lib/redis_models.rb: -------------------------------------------------------------------------------- 1 | require 'redis' 2 | 3 | class RedisWidget 4 | def self.redis 5 | threaded ||= Redis.new 6 | end 7 | 8 | def self.threaded 9 | Thread.current[self.class.to_s] ||= {} 10 | end 11 | 12 | def initialize(options = {}) 13 | options = options.dup 14 | @name = options[:name] 15 | end 16 | 17 | def connection 18 | self.class.redis 19 | end 20 | 21 | def save 22 | unless connection.get(self.class.to_s + ':id') 23 | @id = 0 24 | connection.set(self.class.to_s + ':id', @id) 25 | end 26 | @id = connection.incr(self.class.to_s + ':id') 27 | connection.set(self.class.to_s + ':%d:name' % @id, @name) 28 | end 29 | 30 | def self.count 31 | self.redis.keys(self.to_s + '*name').size 32 | end 33 | 34 | def self.create! 35 | new(:name => 'some widget').save 36 | end 37 | end 38 | 39 | class RedisWidgetUsingDatabaseOne < RedisWidget 40 | def self.redis 41 | threaded[self.class.to_s] ||= Redis.new :url => ENV['REDIS_URL_ONE'] 42 | end 43 | 44 | def self.create! 45 | new(:name => 'a widget using database one').save 46 | end 47 | end 48 | 49 | class RedisWidgetUsingDatabaseTwo < RedisWidget 50 | def self.redis 51 | threaded[self.class.to_s] ||= Redis.new :url => ENV['REDIS_URL_TWO'] 52 | end 53 | 54 | def self.create! 55 | new(:name => 'a widget using database two').save 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /features/cleaning.feature: -------------------------------------------------------------------------------- 1 | Feature: database cleaning 2 | In order to ease example and feature writing 3 | As a developer 4 | I want to have my database in a clean state 5 | 6 | Scenario Outline: ruby app with adapter gems 7 | Given I am using from its adapter gem 8 | And the cleaning strategy 9 | 10 | When I run my scenarios that rely on a clean database 11 | Then I should see all green 12 | 13 | Examples: 14 | | ORM | Strategy | 15 | | ActiveRecord | transaction | 16 | | ActiveRecord | truncation | 17 | | ActiveRecord | deletion | 18 | | Redis | deletion | 19 | -------------------------------------------------------------------------------- /features/cleaning_default_strategy.feature: -------------------------------------------------------------------------------- 1 | Feature: database cleaning 2 | In order to ease example and feature writing 3 | As a developer 4 | I want to have my database in a clean state with default strategy 5 | 6 | Scenario Outline: ruby app with adapter gems 7 | Given I am using from its adapter gem 8 | And the default cleaning strategy 9 | 10 | When I run my scenarios that rely on a clean database 11 | Then I should see all green 12 | 13 | Examples: 14 | | ORM | 15 | | ActiveRecord | 16 | | Redis | 17 | 18 | -------------------------------------------------------------------------------- /features/cleaning_multiple_dbs.feature: -------------------------------------------------------------------------------- 1 | Feature: multiple database cleaning 2 | In order to ease example and feature writing 3 | As a developer 4 | I want to have my databases in a clean state 5 | 6 | Scenario Outline: ruby app with adapter gems 7 | Given I am using from its adapter gem 8 | And the cleaning strategy 9 | 10 | When I run my scenarios that rely on clean databases 11 | Then I should see all green 12 | 13 | Examples: 14 | | ORM | Strategy | 15 | | ActiveRecord | truncation | 16 | | ActiveRecord | deletion | 17 | | ActiveRecord | transaction | 18 | | Redis | deletion | 19 | 20 | -------------------------------------------------------------------------------- /features/cleaning_multiple_orms.feature: -------------------------------------------------------------------------------- 1 | Feature: database cleaning using multiple ORMs 2 | In order to ease example and feature writing 3 | As a developer 4 | I want to have my database in a clean state 5 | 6 | Scenario Outline: ruby app with adapter gems 7 | Given I am using and from their adapter gems 8 | When I run my scenarios that rely on clean databases using multiple orms 9 | Then I should see all green 10 | 11 | Examples: 12 | | ORM1 | ORM2 | 13 | | ActiveRecord | Redis | 14 | | Redis | ActiveRecord | 15 | 16 | -------------------------------------------------------------------------------- /features/step_definitions/database_cleaner_steps.rb: -------------------------------------------------------------------------------- 1 | orms_pattern = /(ActiveRecord|Redis)/ 2 | 3 | Given /^I am using #{orms_pattern} from its adapter gem$/ do |orm| 4 | @feature_runner = FeatureRunner.new 5 | @feature_runner.orm = orm 6 | end 7 | 8 | Given /^I am using #{orms_pattern} and #{orms_pattern} from their adapter gems$/ do |orm1, orm2| 9 | @feature_runner = FeatureRunner.new 10 | @feature_runner.orm = orm1 11 | @feature_runner.another_orm = orm2 12 | end 13 | 14 | Given /^the (.+) cleaning strategy$/ do |strategy| 15 | @feature_runner.strategy = strategy 16 | end 17 | 18 | When "I run my scenarios that rely on a clean database" do 19 | @feature_runner.go 'example' 20 | end 21 | 22 | When "I run my scenarios that rely on clean databases" do 23 | @feature_runner.multiple_databases = true 24 | @feature_runner.go 'example_multiple_db' 25 | end 26 | 27 | When "I run my scenarios that rely on clean databases using multiple orms" do 28 | @feature_runner.go 'example_multiple_orm' 29 | end 30 | 31 | Then "I should see all green" do 32 | fail "Feature failed with :#{@feature_runner.output}" unless @feature_runner.exit_status == 0 33 | end 34 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/expectations' 2 | 3 | if ENV['COVERAGE'] == 'true' 4 | require "simplecov" 5 | 6 | if ENV['CI'] == 'true' 7 | require 'codecov' 8 | SimpleCov.formatter = SimpleCov::Formatter::Codecov 9 | puts "required codecov" 10 | end 11 | 12 | SimpleCov.start 13 | puts "required simplecov" 14 | end 15 | 16 | class FeatureRunner 17 | attr_accessor :orm 18 | attr_accessor :another_orm 19 | attr_accessor :multiple_databases 20 | attr_accessor :strategy 21 | attr_accessor :exit_status 22 | attr_accessor :output 23 | 24 | def strategy 25 | @strategy ||= "default" 26 | end 27 | 28 | def go(feature) 29 | command = "" 30 | command << "ORM=#{orm} " if orm 31 | command << "STRATEGY=#{strategy} " if strategy 32 | command << "ANOTHER_ORM=#{another_orm} " if another_orm 33 | command << "MULTIPLE_DBS=#{true} " if multiple_databases 34 | command << "cucumber --strict examples/features/#{feature}.feature --require examples/features/support --require examples/features/step_definitions" 35 | # puts command 36 | 37 | self.output = `#{command}` 38 | 39 | self.exit_status = $?.exitstatus 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/database_cleaner-core.rb: -------------------------------------------------------------------------------- 1 | require "database_cleaner/core" 2 | -------------------------------------------------------------------------------- /lib/database_cleaner.rb: -------------------------------------------------------------------------------- 1 | require "database_cleaner/active_record" 2 | -------------------------------------------------------------------------------- /lib/database_cleaner/cleaner.rb: -------------------------------------------------------------------------------- 1 | require 'database_cleaner/null_strategy' 2 | require 'database_cleaner/strategy' 3 | require 'forwardable' 4 | 5 | module DatabaseCleaner 6 | class UnknownStrategySpecified < ArgumentError; end 7 | 8 | class Cleaner 9 | def self.available_strategies(orm_module) 10 | # introspect publically available constants for descendents of Strategy to get list of strategies 11 | # ignore classes named Base, because its a common name for a shared base class that adds ORM access stuff to Strategy before being inherited by final concrete class 12 | # if you're writing an adapter and this method is falsely returning an internal constant that isn't a valid strategy, consider making it private with Module#private_constant. 13 | orm_module.constants.select do |constant_name| 14 | ancestors = orm_module.const_get(constant_name).ancestors rescue [] 15 | ancestors.include?(DatabaseCleaner::Strategy) 16 | end.map do |constant_name| 17 | underscore(constant_name).to_sym 18 | end - [:base] 19 | end 20 | 21 | include Comparable 22 | 23 | def <=>(other) 24 | [orm, db] <=> [other.orm, other.db] 25 | end 26 | 27 | def initialize(orm, db: nil) 28 | @orm = orm 29 | self.db = db 30 | end 31 | 32 | attr_reader :orm 33 | 34 | def db=(desired_db) 35 | @db = self.strategy_db = desired_db 36 | end 37 | 38 | def db 39 | @db ||= :default 40 | end 41 | 42 | def strategy=(args) 43 | strategy, *strategy_args = args 44 | @strategy = if strategy.is_a?(Symbol) 45 | create_strategy(*args) 46 | elsif strategy_args.empty? 47 | strategy 48 | else 49 | raise ArgumentError, "You must provide a strategy object, or a symbol for a known strategy along with initialization params." 50 | end 51 | 52 | set_strategy_db @strategy, db 53 | end 54 | 55 | def strategy 56 | @strategy ||= NullStrategy.new 57 | end 58 | 59 | extend Forwardable 60 | delegate [:start, :clean, :cleaning] => :strategy 61 | 62 | def clean_with(*args) 63 | strategy = create_strategy(*args) 64 | set_strategy_db strategy, db 65 | strategy.clean 66 | strategy 67 | end 68 | 69 | private 70 | 71 | def strategy_db=(desired_db) 72 | set_strategy_db(strategy, desired_db) 73 | end 74 | 75 | def set_strategy_db(strategy, desired_db) 76 | if strategy.respond_to? :db= 77 | strategy.db = desired_db 78 | elsif desired_db != :default 79 | raise ArgumentError, "You must provide a strategy object that supports non default databases when you specify a database" 80 | end 81 | end 82 | 83 | def create_strategy(*args) 84 | strategy, *strategy_args = args 85 | orm_strategy(strategy).new(*strategy_args) 86 | end 87 | 88 | def orm_strategy(strategy) 89 | strategy_module_name = camelize(strategy) 90 | orm_module.const_get(strategy_module_name) 91 | rescue NameError 92 | available_strategies = self.class.available_strategies(orm_module) 93 | raise UnknownStrategySpecified, "The '#{strategy}' strategy does not exist for the #{orm} ORM! Available strategies: #{available_strategies.join(', ')}" 94 | end 95 | 96 | def orm_module 97 | orm_module_name = camelize(orm) 98 | DatabaseCleaner.const_get(orm_module_name) 99 | end 100 | 101 | # copied from ActiveSupport to avoid adding it as a dependency 102 | 103 | def camelize(term) 104 | string = term.to_s 105 | string = string.sub(/^[a-z\d]*/) { |match| match.capitalize } 106 | string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" } 107 | string.gsub!("/", "::") 108 | string 109 | end 110 | 111 | def self.underscore(camel_cased_word) 112 | return camel_cased_word unless /[A-Z-]|::/.match?(camel_cased_word) 113 | word = camel_cased_word.to_s.gsub("::", "/") 114 | word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2') 115 | word.gsub!(/([a-z\d])([A-Z])/, '\1_\2') 116 | word.tr!("-", "_") 117 | word.downcase! 118 | word 119 | end 120 | private_class_method :underscore 121 | end 122 | end 123 | -------------------------------------------------------------------------------- /lib/database_cleaner/cleaners.rb: -------------------------------------------------------------------------------- 1 | require 'database_cleaner/cleaner' 2 | require 'database_cleaner/safeguard' 3 | 4 | module DatabaseCleaner 5 | class Cleaners < Hash 6 | def initialize hash={} 7 | super.replace(hash) 8 | end 9 | 10 | # FIXME this method conflates creation with lookup... both a command and a query. yuck. 11 | def [](orm, **opts) 12 | raise ArgumentError if orm.nil? 13 | fetch([orm, opts]) { add_cleaner(orm, **opts) } 14 | end 15 | 16 | # It returns a hash with all the strategies associated with 17 | # all the cleaners. 18 | # 19 | # For example: 20 | # 21 | # ``` 22 | # cleaners.strategy 23 | # => { 24 | # :active_record_1 => :truncation, 25 | # :active_record_2 => :truncation, 26 | # :data_mapper_1 => :truncation 27 | # } 28 | # ``` 29 | # 30 | # @return [Hash] 31 | def strategy 32 | transform_values(&:strategy) 33 | end 34 | 35 | def strategy=(strategy) 36 | values.each { |cleaner| cleaner.strategy = strategy } 37 | remove_duplicates 38 | end 39 | 40 | def start 41 | Safeguard.new.run 42 | values.each { |cleaner| cleaner.start } 43 | end 44 | 45 | def clean 46 | Safeguard.new.run 47 | values.each { |cleaner| cleaner.clean } 48 | end 49 | 50 | def cleaning(&inner_block) 51 | Safeguard.new.run 52 | values.inject(inner_block) do |curr_block, cleaner| 53 | proc { cleaner.cleaning(&curr_block) } 54 | end.call 55 | end 56 | 57 | def clean_with(*args) 58 | Safeguard.new.run 59 | values.each { |cleaner| cleaner.clean_with(*args) } 60 | end 61 | 62 | private 63 | 64 | def add_cleaner(orm, **opts) 65 | self[[orm, opts]] = Cleaner.new(orm, **opts) 66 | end 67 | 68 | def remove_duplicates 69 | replace(reduce(Cleaners.new) do |cleaners, (key, value)| 70 | cleaners[key] = value unless cleaners.values.include?(value) 71 | cleaners 72 | end) 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/database_cleaner/core.rb: -------------------------------------------------------------------------------- 1 | require 'database_cleaner/version' 2 | require 'database_cleaner/cleaners' 3 | require 'forwardable' 4 | 5 | module DatabaseCleaner 6 | class << self 7 | extend Forwardable 8 | delegate [ 9 | :[], 10 | :strategy, 11 | :strategy=, 12 | :start, 13 | :clean, 14 | :clean_with, 15 | :cleaning, 16 | ] => :cleaners 17 | 18 | attr_accessor :allow_remote_database_url, :allow_production, :url_allowlist 19 | 20 | alias :url_whitelist :url_allowlist 21 | alias :url_whitelist= :url_allowlist= 22 | 23 | def cleaners 24 | @cleaners ||= Cleaners.new 25 | end 26 | attr_writer :cleaners 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/database_cleaner/cucumber.rb: -------------------------------------------------------------------------------- 1 | Around do |scenario, block| 2 | DatabaseCleaner.cleaning(&block) 3 | end 4 | -------------------------------------------------------------------------------- /lib/database_cleaner/deprecation.rb: -------------------------------------------------------------------------------- 1 | module DatabaseCleaner 2 | def deprecate message 3 | method = caller.first[/\d+:in [`'](.*)'$/, 1].to_sym 4 | @@deprecator ||= Deprecator.new 5 | @@deprecator.deprecate method, message 6 | end 7 | module_function :deprecate 8 | 9 | class Deprecator 10 | def initialize 11 | @methods_already_warned = {} 12 | end 13 | 14 | def deprecate method, message 15 | return if @methods_already_warned.key?(method) 16 | $stderr.puts message 17 | @methods_already_warned[method] = true 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/database_cleaner/null_strategy.rb: -------------------------------------------------------------------------------- 1 | module DatabaseCleaner 2 | class NullStrategy 3 | def start 4 | # no-op 5 | end 6 | 7 | def db=(db) 8 | # no-op 9 | end 10 | 11 | def clean 12 | # no-op 13 | end 14 | 15 | def cleaning(&block) 16 | # no-op 17 | yield 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/database_cleaner/safeguard.rb: -------------------------------------------------------------------------------- 1 | require "uri" 2 | 3 | module DatabaseCleaner 4 | class Safeguard 5 | class Error < Exception 6 | class RemoteDatabaseUrl < Error 7 | def initialize 8 | super("ENV['DATABASE_URL'] is set to a remote URL. Please refer to https://github.com/DatabaseCleaner/database_cleaner#safeguards") 9 | end 10 | end 11 | 12 | class ProductionEnv < Error 13 | def initialize(env) 14 | super("ENV['#{env}'] is set to production. Please refer to https://github.com/DatabaseCleaner/database_cleaner#safeguards") 15 | end 16 | end 17 | 18 | class UrlNotAllowed < Error 19 | def initialize 20 | super("ENV['DATABASE_URL'] is set to a URL that is not on the allowlist. Please refer to https://github.com/DatabaseCleaner/database_cleaner#safeguards") 21 | end 22 | end 23 | end 24 | 25 | class AllowedUrl 26 | def run 27 | return if skip? 28 | raise Error::UrlNotAllowed if database_url_not_allowed? 29 | end 30 | 31 | private 32 | 33 | def database_url_not_allowed? 34 | !DatabaseCleaner.url_allowlist.any? {|allowed| allowed === ENV['DATABASE_URL'] } 35 | end 36 | 37 | def skip? 38 | !DatabaseCleaner.url_allowlist 39 | end 40 | end 41 | 42 | 43 | class RemoteDatabaseUrl 44 | LOCAL = %w(localhost 127.0.0.1) 45 | 46 | def run 47 | return if skip? 48 | raise Error::RemoteDatabaseUrl if given? 49 | end 50 | 51 | private 52 | 53 | def given? 54 | remote?(ENV['DATABASE_URL']) 55 | end 56 | 57 | def remote?(url) 58 | return false unless url 59 | parsed = URI.parse(url) 60 | return false if parsed.scheme == 'sqlite' || parsed.scheme == 'sqlite3' 61 | 62 | host = parsed.host 63 | return false if host.nil? || host.empty? 64 | return false if LOCAL.include?(host) 65 | return false if host.end_with? '.local' 66 | true 67 | end 68 | 69 | def skip? 70 | ENV['DATABASE_CLEANER_ALLOW_REMOTE_DATABASE_URL'] || 71 | DatabaseCleaner.allow_remote_database_url || 72 | DatabaseCleaner.url_allowlist 73 | end 74 | end 75 | 76 | class Production 77 | KEYS = %w(ENV APP_ENV RACK_ENV RAILS_ENV) 78 | 79 | def run 80 | raise Error::ProductionEnv.new(key) if !skip? && given? 81 | end 82 | 83 | private 84 | 85 | def given? 86 | !!key 87 | end 88 | 89 | def key 90 | @key ||= KEYS.detect { |key| ENV[key] == 'production' } 91 | end 92 | 93 | def skip? 94 | ENV['DATABASE_CLEANER_ALLOW_PRODUCTION'] || 95 | DatabaseCleaner.allow_production 96 | end 97 | end 98 | 99 | CHECKS = [ 100 | RemoteDatabaseUrl, 101 | Production, 102 | AllowedUrl 103 | ] 104 | 105 | def run 106 | CHECKS.each { |const| const.new.run } 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /lib/database_cleaner/spec.rb: -------------------------------------------------------------------------------- 1 | require "database_cleaner/spec/database_helper" 2 | require "database_cleaner/spec/shared_examples" 3 | -------------------------------------------------------------------------------- /lib/database_cleaner/spec/database_helper.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | 3 | module DatabaseCleaner 4 | module Spec 5 | class DatabaseHelper < Struct.new(:db) 6 | def self.with_all_dbs &block 7 | %w[mysql2 sqlite3 postgres trilogy].map(&:to_sym).each do |db| 8 | yield new(db) 9 | end 10 | end 11 | 12 | def setup 13 | create_db 14 | establish_connection 15 | load_schema 16 | end 17 | 18 | attr_reader :connection 19 | 20 | def teardown 21 | drop_db 22 | end 23 | 24 | private 25 | 26 | def establish_connection(config = default_config) 27 | raise NotImplementedError 28 | end 29 | 30 | def create_db 31 | if db == :sqlite3 32 | # NO-OP 33 | elsif db == :postgres 34 | establish_connection default_config.merge('database' => 'postgres') 35 | connection.execute "CREATE DATABASE #{default_config['database']}" rescue nil 36 | else 37 | establish_connection default_config.merge("database" => nil) 38 | connection.execute "CREATE DATABASE IF NOT EXISTS #{default_config['database']}" 39 | end 40 | end 41 | 42 | def load_schema 43 | id_column = case db 44 | when :sqlite3 45 | "id INTEGER PRIMARY KEY AUTOINCREMENT" 46 | when :mysql2, :trilogy 47 | "id INTEGER PRIMARY KEY AUTO_INCREMENT" 48 | when :postgres 49 | "id SERIAL PRIMARY KEY" 50 | end 51 | connection.execute <<-SQL 52 | CREATE TABLE IF NOT EXISTS users ( 53 | #{id_column}, 54 | name INTEGER 55 | ); 56 | SQL 57 | 58 | connection.execute <<-SQL 59 | CREATE TABLE IF NOT EXISTS agents ( 60 | name INTEGER 61 | ); 62 | SQL 63 | end 64 | 65 | def drop_db 66 | if db == :sqlite3 67 | begin 68 | File.unlink(db_config['sqlite3']['database']) 69 | rescue Errno::ENOENT 70 | end 71 | elsif db == :postgres 72 | # FIXME 73 | connection.execute "DROP TABLE IF EXISTS users" 74 | connection.execute "DROP TABLE IF EXISTS agents" 75 | else 76 | connection.execute "DROP DATABASE IF EXISTS #{default_config['database']}" 77 | end 78 | end 79 | 80 | def db_config 81 | config_path = 'spec/support/config.yml' 82 | @db_config ||= YAML.load(IO.read(config_path)) 83 | end 84 | 85 | def default_config 86 | db_config[db.to_s] 87 | end 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /lib/database_cleaner/spec/shared_examples.rb: -------------------------------------------------------------------------------- 1 | require "database_cleaner/cleaner" 2 | 3 | RSpec.shared_examples_for "a database_cleaner strategy" do 4 | it { is_expected.to respond_to(:db) } 5 | it { is_expected.to respond_to(:db=) } 6 | it { is_expected.to respond_to(:start) } 7 | it { is_expected.to respond_to(:clean) } 8 | it { is_expected.to respond_to(:cleaning) } 9 | end 10 | 11 | RSpec.shared_examples_for "a database_cleaner adapter" do 12 | describe 'all strategies should adhere to a database_cleaner strategy interface' do 13 | DatabaseCleaner::Cleaner.available_strategies(described_class).each do |strategy| 14 | subject { described_class.const_get(strategy.to_s.capitalize).new } 15 | 16 | it_behaves_like 'a database_cleaner strategy' 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/database_cleaner/strategy.rb: -------------------------------------------------------------------------------- 1 | # Abstract strategy class for orm adapter gems to subclass 2 | 3 | module DatabaseCleaner 4 | class Strategy 5 | # Override this method if the strategy accepts options 6 | def initialize(options=nil) 7 | if options 8 | name = self.class.name.sub("DatabaseCleaner::","").sub("::"," ") # e.g. "ActiveRecord Transaction" 9 | raise ArgumentError, "No options are available for the #{name} strategy." 10 | end 11 | end 12 | 13 | def db 14 | @db ||= :default 15 | end 16 | attr_writer :db 17 | 18 | # Override this method to start a database transaction if the strategy uses them 19 | def start 20 | end 21 | 22 | # Override this method with the actual cleaning procedure. Its the only mandatory method implementation. 23 | def clean 24 | raise NotImplementedError 25 | end 26 | 27 | def cleaning(&block) 28 | begin 29 | start 30 | yield 31 | ensure 32 | clean 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/database_cleaner/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DatabaseCleaner 4 | VERSION = "2.1.0" 5 | end 6 | -------------------------------------------------------------------------------- /spec/database_cleaner/cleaner_spec.rb: -------------------------------------------------------------------------------- 1 | module DatabaseCleaner 2 | RSpec.describe Cleaner do 3 | describe "comparison" do 4 | it "should be equal if orm and db are the same" do 5 | one = Cleaner.new(:active_record, db: :default) 6 | two = Cleaner.new(:active_record, db: :default) 7 | 8 | expect(one).to eq two 9 | expect(two).to eq one 10 | end 11 | 12 | it "should not be equal if orm are not the same" do 13 | one = Cleaner.new(:mongo_id, db: :default) 14 | two = Cleaner.new(:active_record, db: :default) 15 | 16 | expect(one).not_to eq two 17 | expect(two).not_to eq one 18 | end 19 | 20 | it "should not be equal if db are not the same" do 21 | one = Cleaner.new(:active_record, db: :default) 22 | two = Cleaner.new(:active_record, db: :other) 23 | 24 | expect(one).not_to eq two 25 | expect(two).not_to eq one 26 | end 27 | end 28 | 29 | describe "initialization" do 30 | context "db specified" do 31 | subject(:cleaner) { Cleaner.new(:active_record, db: :my_db) } 32 | 33 | it "should store db from :db in params hash" do 34 | expect(cleaner.db).to eq :my_db 35 | end 36 | end 37 | 38 | describe "orm" do 39 | it "should store orm" do 40 | cleaner = Cleaner.new(:a_orm) 41 | expect(cleaner.orm).to eq :a_orm 42 | end 43 | 44 | it "raises ArgumentError when no orm is specified" do 45 | expect { Cleaner.new }.to raise_error(ArgumentError) 46 | end 47 | end 48 | end 49 | 50 | describe "db" do 51 | subject(:cleaner) { Cleaner.new(:orm) } 52 | 53 | it "should default to :default" do 54 | expect(cleaner.db).to eq :default 55 | end 56 | 57 | it "should return any stored db value" do 58 | cleaner.db = :test_db 59 | expect(cleaner.db).to eq :test_db 60 | end 61 | end 62 | 63 | describe "db=" do 64 | subject(:cleaner) { Cleaner.new(:orm) } 65 | 66 | context "when strategy supports db specification" do 67 | it "should pass db down to its current strategy" do 68 | expect(cleaner.strategy).to receive(:db=).with(:a_new_db) 69 | cleaner.db = :a_new_db 70 | end 71 | end 72 | 73 | context "when strategy doesn't support db specification" do 74 | let(:strategy) { double(respond_to?: false) } 75 | 76 | before { cleaner.strategy = strategy } 77 | 78 | it "doesn't pass the default db down to it" do 79 | expect(strategy).to_not receive(:db=) 80 | cleaner.db = :default 81 | end 82 | 83 | it "should raise an argument error when db isn't default" do 84 | expect { cleaner.db = :test }.to raise_error ArgumentError 85 | end 86 | end 87 | end 88 | 89 | describe "clean_with" do 90 | subject(:cleaner) { Cleaner.new(:active_record) } 91 | 92 | let(:strategy_class) { Class.new(DatabaseCleaner::Strategy) } 93 | let(:strategy) { strategy_class.new } 94 | before { allow(strategy_class).to receive(:new).and_return(strategy) } 95 | 96 | before do 97 | stub_const "DatabaseCleaner::ActiveRecord::Truncation", strategy_class 98 | end 99 | 100 | it "should pass all arguments to strategy initializer" do 101 | expect(strategy_class).to receive(:new).with(:dollar, :amet, { ipsum: "random" }).and_return(strategy) 102 | expect(strategy).to receive(:clean) 103 | cleaner.clean_with :truncation, :dollar, :amet, ipsum: "random" 104 | end 105 | 106 | it "should invoke clean on the created strategy" do 107 | expect(strategy).to receive(:clean) 108 | cleaner.clean_with :truncation 109 | end 110 | 111 | it "should return the created strategy" do 112 | expect(strategy).to receive(:clean) 113 | expect(cleaner.clean_with(:truncation)).to eq strategy 114 | end 115 | end 116 | 117 | describe "strategy=" do 118 | subject(:cleaner) { Cleaner.new(:active_record) } 119 | 120 | let(:strategy_class) { Class.new(DatabaseCleaner::Strategy) } 121 | let(:orm_module) { Module.new } 122 | 123 | let(:available_strategies) { DatabaseCleaner::Cleaner.available_strategies(orm_module) } 124 | 125 | before do 126 | stub_const "DatabaseCleaner::ActiveRecord", orm_module 127 | stub_const "DatabaseCleaner::ActiveRecord::Truncation", strategy_class 128 | stub_const "DatabaseCleaner::ActiveRecord::MyStrategy", strategy_class 129 | # stub consts that shouldn't show up in strategy list 130 | stub_const "DatabaseCleaner::ActiveRecord::VERSION", "2.0.0" 131 | stub_const "DatabaseCleaner::ActiveRecord::Helpers", Module.new 132 | stub_const "DatabaseCleaner::ActiveRecord::Base", Class.new(strategy_class) 133 | stub_const "DatabaseCleaner::ActiveRecord::ExtendedBase", Class.new(strategy_class) 134 | orm_module.private_constant :ExtendedBase 135 | end 136 | 137 | it "should look up and create a the named strategy for the current ORM" do 138 | cleaner.strategy = :truncation 139 | expect(cleaner.strategy).to be_a(strategy_class) 140 | end 141 | 142 | it "should look up and create a custom strategy for the current ORM" do 143 | cleaner.strategy = :my_strategy 144 | expect(cleaner.strategy).to be_a(strategy_class) 145 | end 146 | 147 | it "should proxy params with symbolised strategies in an array" do 148 | expect(strategy_class).to receive(:new).with({ param: "one" }) 149 | cleaner.strategy = [:truncation, param: "one"] 150 | end 151 | 152 | it "should proxy params with symbolised strategies in a separate hash" do 153 | expect(strategy_class).to receive(:new).with({ param: "one" }) 154 | cleaner.strategy = :truncation, { param: "one" } 155 | end 156 | 157 | it "should accept strategy objects" do 158 | strategy = double 159 | cleaner.strategy = strategy 160 | expect(cleaner.strategy).to eq strategy 161 | end 162 | 163 | it "should raise argument error when params given with strategy object" do 164 | expect do 165 | cleaner.strategy = double, { param: "one" } 166 | end.to raise_error ArgumentError 167 | end 168 | 169 | it "should attempt to set strategy db" do 170 | strategy = double 171 | expect(strategy).to receive(:db=).with(:default) 172 | cleaner.strategy = strategy 173 | end 174 | 175 | it "raises UnknownStrategySpecified on a bad strategy, and lists available strategies" do 176 | expect { cleaner.strategy = :horrible_plan }.to \ 177 | raise_error(UnknownStrategySpecified, "The 'horrible_plan' strategy does not exist for the active_record ORM! Available strategies: #{available_strategies.join(', ')}") 178 | end 179 | end 180 | 181 | describe "strategy" do 182 | subject(:cleaner) { Cleaner.new(:a_orm) } 183 | 184 | it "returns a null strategy when strategy is not set and undetectable" do 185 | expect(cleaner.strategy).to be_a(DatabaseCleaner::NullStrategy) 186 | end 187 | end 188 | 189 | describe "proxy methods" do 190 | subject(:cleaner) { Cleaner.new(:orm) } 191 | 192 | let(:strategy) { double(:strategy) } 193 | 194 | before { cleaner.strategy = strategy } 195 | 196 | describe "start" do 197 | it "should proxy start to the strategy" do 198 | expect(strategy).to receive(:start) 199 | cleaner.start 200 | end 201 | end 202 | 203 | describe "clean" do 204 | it "should proxy clean to the strategy" do 205 | expect(strategy).to receive(:clean) 206 | cleaner.clean 207 | end 208 | end 209 | 210 | describe "cleaning" do 211 | it "should proxy cleaning to the strategy" do 212 | expect(strategy).to receive(:cleaning) 213 | cleaner.cleaning { } 214 | end 215 | end 216 | end 217 | end 218 | end 219 | -------------------------------------------------------------------------------- /spec/database_cleaner/cleaners_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe DatabaseCleaner::Cleaners do 2 | subject(:cleaners) { described_class.new } 3 | 4 | context "orm specification" do 5 | it "should not accept nil orms" do 6 | expect { cleaners[nil] }.to raise_error(ArgumentError) 7 | end 8 | 9 | it "should accept :active_record" do 10 | cleaner = cleaners[:active_record] 11 | expect(cleaner).to be_a(DatabaseCleaner::Cleaner) 12 | expect(cleaner.orm).to eq :active_record 13 | expect(cleaners.values).to eq [cleaner] 14 | end 15 | 16 | it "should accept multiple orm's" do 17 | cleaners_values = [cleaners[:couch_potato], cleaners[:data_mapper]] 18 | expect(cleaners.values.map(&:orm)).to eq [:couch_potato, :data_mapper] 19 | expect(cleaners.values).to eq cleaners_values 20 | end 21 | 22 | it "should accept a db parameter and store it" do 23 | cleaner = cleaners[:active_record, db: :first_db] 24 | expect(cleaner).to be_a(DatabaseCleaner::Cleaner) 25 | expect(cleaner.orm).to eq :active_record 26 | expect(cleaner.db).to eq :first_db 27 | end 28 | 29 | it "should accept multiple dbs for a single orm" do 30 | cleaners[:data_mapper, db: :first_db] 31 | cleaners[:data_mapper, db: :second_db] 32 | expect(cleaners.values.map(&:orm)).to eq [:data_mapper, :data_mapper] 33 | expect(cleaners.values.map(&:db)).to eq [:first_db, :second_db] 34 | end 35 | 36 | it "should accept multiple dbs and multiple orms" do 37 | cleaners[:data_mapper, db: :first_db ] 38 | cleaners[:active_record, db: :second_db] 39 | cleaners[:active_record, db: :first_db ] 40 | cleaners[:data_mapper, db: :second_db] 41 | expect(cleaners.values.map(&:orm)).to eq [:data_mapper, :active_record, :active_record, :data_mapper] 42 | expect(cleaners.values.map(&:db)).to eq [:first_db, :second_db, :first_db, :second_db] 43 | end 44 | 45 | it "should retrieve a db rather than create a new one" do 46 | class_double("DatabaseCleaner::ActiveRecord").as_stubbed_const 47 | strategy_class = class_double("DatabaseCleaner::ActiveRecord::Truncation").as_stubbed_const 48 | allow(strategy_class).to receive(:new) 49 | 50 | cleaner = cleaners[:active_record] 51 | cleaners[:active_record].strategy = :truncation 52 | expect(cleaners[:active_record]).to equal cleaner 53 | end 54 | end 55 | 56 | context "top level api methods" do 57 | context "single orm single db" do 58 | let!(:cleaner) { cleaners[:active_record] } 59 | 60 | it "should proxy strategy=" do 61 | stratagem = double("stratagem") 62 | expect(cleaner).to receive(:strategy=).with(stratagem) 63 | cleaners.strategy = stratagem 64 | end 65 | 66 | it "should proxy start" do 67 | expect(cleaner).to receive(:start) 68 | cleaners.start 69 | end 70 | 71 | it "should proxy clean" do 72 | expect(cleaner).to receive(:clean) 73 | cleaners.clean 74 | end 75 | 76 | it 'should proxy cleaning' do 77 | expect(cleaner).to receive(:cleaning) 78 | cleaners.cleaning { } 79 | end 80 | 81 | it "should proxy clean_with" do 82 | stratagem = double("stratgem") 83 | expect(cleaner).to receive(:clean_with).with(stratagem, {}) 84 | cleaners.clean_with stratagem, {} 85 | end 86 | end 87 | 88 | context "multiple cleaners" do 89 | # these are relatively simple, all we need to do is make sure all cleaners are cleaned/started/cleaned_with appropriately. 90 | context "simple proxy methods" do 91 | 92 | let(:active_record) { double("active_mock") } 93 | let(:data_mapper) { double("data_mock") } 94 | 95 | subject(:cleaners) do 96 | DatabaseCleaner::Cleaners.new({ 97 | active_record: active_record, 98 | data_mapper: data_mapper, 99 | }) 100 | end 101 | 102 | it "should proxy start to all cleaners" do 103 | expect(active_record).to receive(:start) 104 | expect(data_mapper).to receive(:start) 105 | 106 | cleaners.start 107 | end 108 | 109 | it "should proxy clean to all cleaners" do 110 | expect(active_record).to receive(:clean) 111 | expect(data_mapper).to receive(:clean) 112 | 113 | cleaners.clean 114 | end 115 | 116 | it "should proxy clean_with to all cleaners" do 117 | stratagem = double("stratgem") 118 | expect(active_record).to receive(:clean_with).with(stratagem) 119 | expect(data_mapper).to receive(:clean_with).with(stratagem) 120 | 121 | cleaners.clean_with stratagem 122 | end 123 | 124 | it "should initiate cleaning on each db, yield, and finish cleaning each db" do 125 | [active_record, data_mapper].each do |db| 126 | class << db 127 | attr_reader :started, :cleaned 128 | 129 | def cleaning &block 130 | @started = true 131 | block.call 132 | @cleaned = true 133 | end 134 | end 135 | end 136 | 137 | yielded = false 138 | cleaners.cleaning do 139 | expect(active_record.started).to eq(true) 140 | expect(data_mapper.started).to eq(true) 141 | expect(active_record.cleaned).to eq(nil) 142 | expect(data_mapper.cleaned).to eq(nil) 143 | yielded = true 144 | end 145 | 146 | expect(yielded).to eq(true) 147 | expect(active_record.cleaned).to eq(true) 148 | expect(data_mapper.cleaned).to eq(true) 149 | end 150 | end 151 | 152 | # ah now we have some difficulty, we mustn't allow duplicate cleaners to exist, but they could 153 | # plausably want to force strategy change on two sets of orm that differ only on db 154 | context "multiple orm proxy methods" do 155 | class FakeStrategy < Struct.new(:orm, :db, :strategy); end 156 | 157 | context "with same strategy but differing orms and dbs" do 158 | let(:active_record_1) { FakeStrategy.new(:active_record, nil, :truncation) } 159 | let(:active_record_2) { FakeStrategy.new(:active_record, :different, :truncation) } 160 | let(:data_mapper_1) { FakeStrategy.new(:data_mapper, nil, :truncation) } 161 | 162 | subject(:cleaners) do 163 | DatabaseCleaner::Cleaners.new({ 164 | active_record_1: active_record_1, 165 | active_record_2: active_record_2, 166 | data_mapper_1: data_mapper_1, 167 | }) 168 | end 169 | 170 | let(:result) do 171 | { 172 | :active_record_1 => :truncation, 173 | :active_record_2 => :truncation, 174 | :data_mapper_1 => :truncation 175 | } 176 | end 177 | 178 | it "should proxy #strategy to all cleaners and return a single result" do 179 | expect(cleaners.strategy).to eq(result) 180 | end 181 | end 182 | 183 | context "with differing strategies" do 184 | let(:active_record_1) { FakeStrategy.new(:active_record, :default, :truncation) } 185 | let(:active_record_2) { FakeStrategy.new(:active_record, :default, :transaction) } 186 | 187 | subject(:cleaners) do 188 | DatabaseCleaner::Cleaners.new({ 189 | active_record_1: active_record_1, 190 | active_record_2: active_record_2, 191 | }) 192 | end 193 | 194 | it "should proxy #strategy to all cleaners and return a hash of results" do 195 | expect(cleaners.strategy) 196 | .to eq({ 197 | active_record_1: active_record_1.strategy, 198 | active_record_2: active_record_2.strategy 199 | }) 200 | end 201 | 202 | it "should proxy #strategy= to all cleaners and remove duplicate cleaners" do 203 | expect { cleaners.strategy = :truncation } 204 | .to change { cleaners.values } 205 | .from([active_record_1,active_record_2]) 206 | .to([active_record_1]) 207 | end 208 | end 209 | end 210 | end 211 | end 212 | end 213 | -------------------------------------------------------------------------------- /spec/database_cleaner/null_strategy_spec.rb: -------------------------------------------------------------------------------- 1 | require "database_cleaner/null_strategy" 2 | 3 | module DatabaseCleaner 4 | RSpec.describe NullStrategy do 5 | subject(:strategy) { NullStrategy.new } 6 | 7 | it 'responds to .start' do 8 | expect { strategy.start }.not_to raise_error 9 | end 10 | 11 | it 'responds to .clean' do 12 | expect { strategy.clean }.not_to raise_error 13 | end 14 | 15 | describe '.cleaning' do 16 | it 'fails without a block' do 17 | expect { strategy.cleaning }.to raise_error(LocalJumpError) 18 | end 19 | 20 | it 'no-ops with a block' do 21 | effect = double 22 | expect(effect).to receive(:occur).once 23 | 24 | strategy.cleaning do 25 | effect.occur 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/database_cleaner/safeguard_spec.rb: -------------------------------------------------------------------------------- 1 | module DatabaseCleaner 2 | RSpec.describe Safeguard do 3 | let(:cleaner) { Cleaners.new } 4 | 5 | describe 'DATABASE_URL is set' do 6 | before { stub_const('ENV', 'DATABASE_URL' => database_url) } 7 | 8 | describe 'to any value' do 9 | let(:database_url) { 'postgres://remote.host' } 10 | 11 | it 'raises' do 12 | expect { cleaner.start }.to raise_error(Safeguard::Error::RemoteDatabaseUrl) 13 | end 14 | end 15 | 16 | describe 'to a localhost url' do 17 | let(:database_url) { 'postgres://localhost' } 18 | 19 | it 'does not raise' do 20 | expect { cleaner.start }.to_not raise_error 21 | end 22 | end 23 | 24 | describe 'to a local, empty-host url' do 25 | let(:database_url) { 'postgres:///' } 26 | 27 | it 'does not raise' do 28 | expect { cleaner.start }.to_not raise_error 29 | end 30 | end 31 | 32 | describe 'to a local tld url' do 33 | let(:database_url) { 'postgres://postgres.local' } 34 | 35 | it 'does not raise' do 36 | expect { cleaner.start }.to_not raise_error 37 | end 38 | end 39 | 40 | describe 'to a 127.0.0.1 url' do 41 | let(:database_url) { 'postgres://127.0.0.1' } 42 | 43 | it 'does not raise' do 44 | expect { cleaner.start }.to_not raise_error 45 | end 46 | end 47 | 48 | describe 'to a sqlite url' do 49 | let(:database_url) { 'sqlite://tmp/db.sqlite3' } 50 | 51 | it 'does not raise' do 52 | expect { cleaner.start }.to_not raise_error 53 | end 54 | end 55 | 56 | describe 'to a sqlite3 url' do 57 | let(:database_url) { 'sqlite3://tmp/db.sqlite3' } 58 | 59 | it 'does not raise' do 60 | expect { cleaner.start }.to_not raise_error 61 | end 62 | end 63 | 64 | describe 'to a sqlite3 url with no slashes after the scheme' do 65 | let(:database_url) { 'sqlite3:tmp/db.sqlite3' } 66 | 67 | it 'does not raise' do 68 | expect { cleaner.start }.to_not raise_error 69 | end 70 | end 71 | 72 | describe 'DATABASE_CLEANER_ALLOW_REMOTE_DATABASE_URL is set' do 73 | let(:database_url) { 'postgres://remote.host' } 74 | before { stub_const('ENV', 'DATABASE_CLEANER_ALLOW_REMOTE_DATABASE_URL' => true) } 75 | 76 | it 'does not raise' do 77 | expect { cleaner.start }.to_not raise_error 78 | end 79 | end 80 | 81 | describe 'DatabaseCleaner.allow_remote_database_url is true' do 82 | let(:database_url) { 'postgres://remote.host' } 83 | before { DatabaseCleaner.allow_remote_database_url = true } 84 | after { DatabaseCleaner.allow_remote_database_url = nil } 85 | 86 | it 'does not raise' do 87 | expect { cleaner.start }.to_not raise_error 88 | end 89 | end 90 | 91 | describe 'DatabaseCleaner.url_allowlist is set' do 92 | 93 | shared_examples 'allowlist assigned' do 94 | describe 'A remote url is on the allowlist' do 95 | let(:database_url) { 'postgres://foo.bar' } 96 | 97 | it 'does not raise' do 98 | expect { cleaner.start }.to_not raise_error 99 | end 100 | end 101 | 102 | describe 'A remote url is not on the allowlist' do 103 | let(:database_url) { 'postgress://bar.baz' } 104 | 105 | it 'raises a not allowed error' do 106 | expect { cleaner.start }.to raise_error(Safeguard::Error::UrlNotAllowed) 107 | end 108 | end 109 | 110 | describe 'A similar url not explicitly matched as a pattern' do 111 | let(:database_url) { 'postgres://foo.bar?pool=8' } 112 | 113 | it 'raises a not allowed error' do 114 | expect { cleaner.start }.to raise_error(Safeguard::Error::UrlNotAllowed) 115 | end 116 | end 117 | 118 | describe 'A remote url matches a pattern on the allowlist' do 119 | let(:database_url) { 'postgres://bar.baz?pool=16' } 120 | 121 | it 'does not raise' do 122 | expect { cleaner.start }.to_not raise_error 123 | end 124 | end 125 | 126 | describe 'A local url is on the allowlist' do 127 | let(:database_url) { 'postgres://postgres@localhost' } 128 | 129 | it 'does not raise' do 130 | expect { cleaner.start }.to_not raise_error 131 | end 132 | end 133 | 134 | describe 'A local url is not on the allowlist' do 135 | let(:database_url) { 'postgres://localhost' } 136 | 137 | it 'raises a not allowed error' do 138 | expect { cleaner.start }.to raise_error(Safeguard::Error::UrlNotAllowed) 139 | end 140 | end 141 | 142 | describe 'A url that matches a proc' do 143 | let(:database_url) { 'redis://test:test@foo.bar' } 144 | 145 | it 'does not raise' do 146 | expect { cleaner.start }.to_not raise_error 147 | end 148 | end 149 | end 150 | 151 | let(:url_allowlist) do 152 | [ 153 | 'postgres://postgres@localhost', 154 | 'postgres://foo.bar', 155 | %r{^postgres://bar.baz}, 156 | proc { |x| URI.parse(x).user == 'test' } 157 | ] 158 | end 159 | 160 | describe 'url_allowlist' do 161 | before { DatabaseCleaner.url_allowlist = url_allowlist } 162 | after { DatabaseCleaner.url_allowlist = nil } 163 | it_behaves_like 'allowlist assigned' 164 | end 165 | 166 | describe 'url_whitelist' do 167 | before { DatabaseCleaner.url_whitelist = url_allowlist } 168 | after { DatabaseCleaner.url_whitelist = nil } 169 | it_behaves_like 'allowlist assigned' 170 | end 171 | 172 | end 173 | end 174 | 175 | describe 'ENV is set to production' do 176 | %w(ENV APP_ENV RACK_ENV RAILS_ENV).each do |key| 177 | describe "on #{key}" do 178 | before { stub_const('ENV', key => "production") } 179 | 180 | it 'raises' do 181 | expect { cleaner.start }.to raise_error(Safeguard::Error::ProductionEnv) 182 | end 183 | end 184 | 185 | describe 'DATABASE_CLEANER_ALLOW_PRODUCTION is set' do 186 | before { stub_const('ENV', 'DATABASE_CLEANER_ALLOW_PRODUCTION' => true) } 187 | 188 | it 'does not raise' do 189 | expect { cleaner.start }.to_not raise_error 190 | end 191 | end 192 | 193 | describe 'DatabaseCleaner.allow_production is true' do 194 | before { DatabaseCleaner.allow_production = true } 195 | after { DatabaseCleaner.allow_production = nil } 196 | 197 | it 'does not raise' do 198 | expect { cleaner.start }.to_not raise_error 199 | end 200 | end 201 | end 202 | end 203 | end 204 | end 205 | -------------------------------------------------------------------------------- /spec/database_cleaner/strategy_spec.rb: -------------------------------------------------------------------------------- 1 | require "database_cleaner/strategy" 2 | 3 | RSpec.describe DatabaseCleaner::Strategy do 4 | describe "default implementation" do 5 | it "does not provide a default implementation for #clean" do 6 | expect { described_class.new.clean }.to raise_exception(NotImplementedError) 7 | end 8 | 9 | it "rejects options" do 10 | expect { described_class.new(only: %w[a b c]) }.to \ 11 | raise_exception(ArgumentError, "No options are available for the Strategy strategy.") 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/rcov.opts: -------------------------------------------------------------------------------- 1 | --exclude "spec/*,vendor/*,examples/*,features/*,Users/*/.rvm/gems/*/gems/*" -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "bundler/setup" 2 | require "byebug" 3 | 4 | if ENV['COVERAGE'] == 'true' 5 | require "simplecov" 6 | 7 | if ENV['CI'] == 'true' 8 | require 'codecov' 9 | SimpleCov.formatter = SimpleCov::Formatter::Codecov 10 | puts "required codecov" 11 | end 12 | 13 | SimpleCov.start 14 | puts "required simplecov" 15 | end 16 | 17 | require "database_cleaner-core" 18 | 19 | RSpec.configure do |config| 20 | # These two settings work together to allow you to limit a spec run 21 | # to individual examples or groups you care about by tagging them with 22 | # `:focus` metadata. When nothing is tagged with `:focus`, all examples 23 | # get run. 24 | config.filter_run :focus 25 | config.run_all_when_everything_filtered = true 26 | 27 | config.disable_monkey_patching! 28 | end 29 | -------------------------------------------------------------------------------- /spec/support/active_record_helper.rb: -------------------------------------------------------------------------------- 1 | require 'active_record' 2 | require 'database_cleaner/spec/database_helper' 3 | 4 | class ActiveRecordHelper < DatabaseCleaner::Spec::DatabaseHelper 5 | def setup 6 | patch_mysql_adapters 7 | 8 | Kernel.const_set "User", Class.new(ActiveRecord::Base) 9 | Kernel.const_set "Agent", Class.new(ActiveRecord::Base) 10 | 11 | super 12 | 13 | connection.execute "CREATE TABLE IF NOT EXISTS schema_migrations (version VARCHAR(255));" 14 | connection.execute "INSERT INTO schema_migrations VALUES (1), (2);" 15 | end 16 | 17 | def teardown 18 | connection.execute "DROP TABLE schema_migrations;" 19 | 20 | super 21 | 22 | Kernel.send :remove_const, "User" if defined?(User) 23 | Kernel.send :remove_const, "Agent" if defined?(Agent) 24 | end 25 | 26 | private 27 | 28 | def establish_connection(config = default_config) 29 | ActiveRecord::Base.establish_connection(config) 30 | @connection = ActiveRecord::Base.connection 31 | end 32 | 33 | def patch_mysql_adapters 34 | # remove DEFAULT NULL from column definition, which is an error on primary keys in MySQL 5.7.3+ 35 | primary_key_sql = "int(11) auto_increment PRIMARY KEY" 36 | ActiveRecord::ConnectionAdapters::MysqlAdapter::NATIVE_DATABASE_TYPES[:primary_key] = primary_key_sql 37 | ActiveRecord::ConnectionAdapters::Mysql2Adapter::NATIVE_DATABASE_TYPES[:primary_key] = primary_key_sql 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/support/data_mapper_helper.rb: -------------------------------------------------------------------------------- 1 | require 'dm-core' 2 | require 'dm-sqlite-adapter' 3 | require 'database_cleaner/spec/database_helper' 4 | 5 | class DataMapperHelper < DatabaseCleaner::Spec::DatabaseHelper 6 | def setup 7 | super 8 | 9 | Kernel.const_set "User", Class.new 10 | User.instance_eval do 11 | include DataMapper::Resource 12 | 13 | storage_names[:default] = 'users' 14 | 15 | property :id, User::Serial 16 | property :name, String 17 | end 18 | end 19 | 20 | def teardown 21 | Kernel.send :remove_const, "User" if defined?(User) 22 | 23 | super 24 | end 25 | 26 | def connection 27 | DataMapper.repository.adapter 28 | end 29 | 30 | private 31 | 32 | def establish_connection(config = default_config) 33 | DataMapper.setup(:default, config) 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/support/example.database.yml: -------------------------------------------------------------------------------- 1 | my_db: 2 | database: <%= "ONE".downcase %> 3 | -------------------------------------------------------------------------------- /spec/support/sample.config.yml: -------------------------------------------------------------------------------- 1 | mysql2: 2 | adapter: mysql2 3 | database: database_cleaner_test 4 | username: root 5 | password: 6 | host: 127.0.0.1 7 | port: 3306 8 | encoding: utf8 9 | 10 | trilogy: 11 | adapter: trilogy 12 | database: database_cleaner_test 13 | username: root 14 | password: 15 | host: 127.0.0.1 16 | port: 3306 17 | encoding: utf8 18 | 19 | postgres: 20 | adapter: postgresql 21 | database: database_cleaner_test 22 | username: postgres 23 | password: 24 | host: 127.0.0.1 25 | encoding: unicode 26 | template: template0 27 | 28 | sqlite3: 29 | adapter: sqlite3 30 | database: tmp/database_cleaner_test.sqlite3 31 | pool: 5 32 | timeout: 5000 33 | encoding: utf8 34 | -------------------------------------------------------------------------------- /spec/support/sequel_helper.rb: -------------------------------------------------------------------------------- 1 | require 'sequel' 2 | require 'database_cleaner/spec/database_helper' 3 | 4 | class SequelHelper < DatabaseCleaner::Spec::DatabaseHelper 5 | private 6 | 7 | def establish_connection(config = default_config) 8 | url = "#{db}:///" 9 | url = "sqlite:///" if db == :sqlite3 10 | @connection = ::Sequel.connect(url, config) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DatabaseCleaner/database_cleaner/f4780bcc119d666a75001261f3eae5423a00a530/tmp/.keep --------------------------------------------------------------------------------